Compare commits

...

9 Commits

Author SHA1 Message Date
Francesco Valla f547082cad
Merge 31d4a750d8 into 224e840841 2026-03-03 11:28:51 -05:00
Kyle Schwarz 224e840841 Device: Increase DiskFormatProgress timeout 2026-02-17 17:16:49 -05:00
Max Brombach 174c0b80d4 Device: VCAN4-IND: Add chips and bootloader information 2026-02-17 12:00:34 -05:00
Max Brombach 19092bceb6 Device: RAD-MoonT1S: Add bootloader pipeline and chip 2026-02-13 13:17:59 -05:00
Kyle Schwarz 25b673075f All: Copyright 2026 2026-02-13 10:08:09 -05:00
Max Brombach 1a7bc4df47 Device: RADJupiter: Add bootloader pipeline 2026-02-12 17:05:39 -05:00
Max Brombach 20a2474508 Device: Settings: Add missing bit-packed variables to CAN_SETTINGS 2026-02-12 16:25:57 -05:00
Thomas Stoddard d18ca9e6eb Device: RAD-Galaxy: Add support for Analog Output 2026-02-11 18:43:30 -05:00
Francesco Valla 31d4a750d8 EthernetPacketizer: do a size check on incoming bytestream
An incoming bytestream can be less than 24 bytes, leading to exceptions
when accessing its data (or allocating the vector for its payload).
Perform a size check before trying to decode the bytestream and discard
invalid incoming streams.

Signed-off-by: Francesco Valla <francesco.valla@mta.it>
2025-07-04 10:57:28 +02:00
18 changed files with 463 additions and 5 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2018-2025 Intrepid Control Systems, Inc.
Copyright (c) 2018-2026 Intrepid Control Systems, Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

View File

@ -29,7 +29,7 @@ BEGIN
VALUE "FileDescription", "Intrepid Control Systems Open Device Communication C API"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", "icsneoc.dll"
VALUE "LegalCopyright", "Intrepid Control Systems, Inc. (C) 2018-2025"
VALUE "LegalCopyright", "Intrepid Control Systems, Inc. (C) 2018-2026"
VALUE "OriginalFilename", "icsneoc.dll"
VALUE "ProductName", "libicsneo"
VALUE "ProductVersion", VER_PRODUCTVERSION_STR

View File

@ -40,6 +40,14 @@ void init_idevicesettings(pybind11::module_& m) {
.value("Normal", LINMode::NORMAL_MODE)
.value("Fast", LINMode::FAST_MODE);
pybind11::enum_<MiscIOAnalogVoltage>(settings, "MiscIOAnalogVoltage")
.value("V0", MiscIOAnalogVoltage::V0)
.value("V1", MiscIOAnalogVoltage::V1)
.value("V2", MiscIOAnalogVoltage::V2)
.value("V3", MiscIOAnalogVoltage::V3)
.value("V4", MiscIOAnalogVoltage::V4)
.value("V5", MiscIOAnalogVoltage::V5);
pybind11::classh<IDeviceSettings>(m, "IDeviceSettings")
.def("apply", &IDeviceSettings::apply, pybind11::arg("temporary") = 0, pybind11::call_guard<pybind11::gil_scoped_release>())
.def("apply_defaults", &IDeviceSettings::applyDefaults, pybind11::arg("temporary") = 0, pybind11::call_guard<pybind11::gil_scoped_release>())
@ -97,6 +105,9 @@ void init_idevicesettings(pybind11::module_& m) {
.def("get_t1s_burst_timer", &IDeviceSettings::getT1SBurstTimerFor, pybind11::call_guard<pybind11::gil_scoped_release>())
.def("set_t1s_burst_timer", &IDeviceSettings::setT1SBurstTimerFor, pybind11::call_guard<pybind11::gil_scoped_release>())
.def("set_misc_io_analog_output_enabled", &IDeviceSettings::setMiscIOAnalogOutputEnabled, pybind11::call_guard<pybind11::gil_scoped_release>())
.def("set_misc_io_analog_output", &IDeviceSettings::setMiscIOAnalogOutput, pybind11::call_guard<pybind11::gil_scoped_release>())
// Status properties
.def_readonly("disabled", &IDeviceSettings::disabled)
.def_readonly("readonly", &IDeviceSettings::readonly);

View File

@ -133,6 +133,10 @@ EthernetPacketizer::EthernetPacket::EthernetPacket(const uint8_t* data, size_t s
int EthernetPacketizer::EthernetPacket::loadBytestream(const std::vector<uint8_t>& bytestream) {
errorWhileDecodingFromBytestream = 0;
if (bytestream.size() < 24) {
errorWhileDecodingFromBytestream = 1;
return errorWhileDecodingFromBytestream;
}
for(size_t i = 0; i < 6; i++)
destMAC[i] = bytestream[i];
for(size_t i = 0; i < 6; i++)

View File

@ -3817,7 +3817,7 @@ bool Device::formatDisk(const DiskDetails& config, const DiskFormatProgress& han
return com->sendCommand(ExtendedCommand::DiskFormatProgress, {});
},
std::make_shared<ExtendedResponseFilter>(ExtendedCommand::DiskFormatProgress),
std::chrono::milliseconds(200)
std::chrono::milliseconds(1000)
);
if(!response) {

View File

@ -946,3 +946,17 @@ template<typename T> bool IDeviceSettings::applyStructure(const T& newStructure)
memcpy(settings.data(), &newStructure, structSize);
return apply();
}
bool IDeviceSettings::setMiscIOAnalogOutputEnabled(uint8_t pin, bool enabled) {
(void)pin;
(void)enabled;
report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
return false;
}
bool IDeviceSettings::setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage voltage) {
(void)pin;
(void)voltage;
report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
return false;
}

View File

@ -12,7 +12,7 @@ subprocess.call('cd ..; doxygen docs/icsneoc/Doxyfile', shell=True)
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'libicsneo'
copyright = '2024-2025, Intrepid Control Systems, Inc.'
copyright = '2024-2026, Intrepid Control Systems, Inc.'
author = 'Intrepid Control Systems, Inc.'
# -- General configuration ---------------------------------------------------

View File

@ -91,3 +91,11 @@ SPI Example for 10BASE-T1S
.. literalinclude:: ../../examples/python/spi/spi_example.py
:language: python
Analog Output Control
=====================
:download:`Download example <../../examples/python/analog_out/analog_out_basic.py>`
.. literalinclude:: ../../examples/python/analog_out/analog_out_basic.py
:language: python

View File

@ -14,6 +14,7 @@ option(LIBICSNEO_BUILD_CPP_APP_ERROR_EXAMPLE "Build the macsec example" ON)
option(LIBICSNEO_BUILD_CPP_FLEXRAY_EXAMPLE "Build the FlexRay example." ON)
option(LIBICSNEO_BUILD_CPP_SPI_EXAMPLE "Build the SPI example." ON)
option(LIBICSNEO_BUILD_CPP_MUTEX_EXAMPLE "Build the NetworkMutex example." ON)
option(LIBICSNEO_BUILD_CPP_ANALOG_OUT_EXAMPLE "Build the analog output example." ON)
add_compile_options(${LIBICSNEO_COMPILER_WARNINGS})
@ -80,3 +81,7 @@ endif()
if(LIBICSNEO_BUILD_CPP_MUTEX_EXAMPLE)
add_subdirectory(cpp/mutex)
endif()
if(LIBICSNEO_BUILD_CPP_ANALOG_OUT_EXAMPLE)
add_subdirectory(cpp/analog_out)
endif()

View File

@ -0,0 +1,2 @@
add_executable(libicsneocpp-analog-out src/analog_out.cpp)
target_link_libraries(libicsneocpp-analog-out icsneocpp)

View File

@ -0,0 +1,157 @@
/**
* libicsneo Analog Output example
*
* Demonstrates how to configure and control analog outputs on supported devices
*
* Usage: libicsneo-analog-out <pin> <voltage> [deviceSerial] [--yes]
*
* Arguments:
* pin: Pin number (1-3 for RAD Galaxy)
* voltage: Voltage level (0-5)
* deviceSerial: 6 character string for device serial (optional)
* --yes: Skip confirmation prompt
*/
#include <iostream>
#include <thread>
#include <chrono>
#include <string_view>
#include <cstdlib>
#include "icsneo/icsneocpp.h"
static const std::string usage = "Usage: libicsneo-analog-out <pin> <voltage> [deviceSerial] [--yes]\n\n"
"Arguments:\n"
"pin: Pin number (1-3 for RAD Galaxy)\n"
"voltage: Voltage level (0-5)\n"
"deviceSerial: 6 character string for device serial (optional)\n"
"--yes: Skip confirmation prompt\n";
int main(int argc, const char** argv) {
std::vector<std::string_view> args(argv, argv + argc);
// Parse arguments
if(args.size() < 3) {
std::cerr << "Error: Missing required arguments\n" << std::endl;
std::cerr << usage;
return -1;
}
char* endPtr;
long pinNum = std::strtol(args[1].data(), &endPtr, 10);
if(endPtr != args[1].data() + args[1].size() || pinNum < 1 || pinNum > 3) {
std::cerr << "Error: Invalid pin number (must be 1-3)" << std::endl;
return -1;
}
long voltageLevel = std::strtol(args[2].data(), &endPtr, 10);
if(endPtr != args[2].data() + args[2].size() || voltageLevel < 0 || voltageLevel > 5) {
std::cerr << "Error: Invalid voltage level (must be 0-5)" << std::endl;
return -1;
}
icsneo::MiscIOAnalogVoltage voltage = static_cast<icsneo::MiscIOAnalogVoltage>(voltageLevel);
uint8_t pin = static_cast<uint8_t>(pinNum);
// Check for optional arguments
bool skipConfirm = false;
std::string_view serial;
for(size_t i = 3; i < args.size(); i++) {
if(args[i] == "--yes") {
skipConfirm = true;
} else if(serial.empty() && args[i].size() == 6) {
serial = args[i];
}
}
// Confirmation prompt
if(!skipConfirm) {
std::cout << "WARNING: This will set analog output pin " << static_cast<int>(pin)
<< " to " << voltageLevel << "V" << std::endl;
std::cout << "Make sure nothing sensitive is connected to this pin." << std::endl;
std::cout << "Continue? (yes/no): ";
std::string response;
std::getline(std::cin, response);
if(response != "yes") {
std::cout << "Aborted." << std::endl;
return 0;
}
}
std::shared_ptr<icsneo::Device> device = nullptr;
if(!serial.empty()) {
// Find device by serial
auto devices = icsneo::FindAllDevices();
for(auto& dev : devices) {
if(dev->getSerial() == serial) {
device = dev;
break;
}
}
if(!device) {
std::cerr << "Device with serial " << serial << " not found" << std::endl;
return -1;
}
} else {
// Use first available device
auto devices = icsneo::FindAllDevices();
if(devices.empty()) {
std::cerr << "No devices found" << std::endl;
return -1;
}
device = devices[0];
}
std::cout << "Using device: " << device->describe() << std::endl;
if(!device->open()) {
std::cerr << "Failed to open device" << std::endl;
return -1;
}
auto settings = device->settings;
if(!settings) {
std::cerr << "Device settings not available" << std::endl;
device->close();
return -1;
}
std::cout << "Refreshing device settings..." << std::endl;
if(!settings->refresh()) {
std::cerr << "Failed to refresh settings" << std::endl;
device->close();
return -1;
}
// Enable analog output on specified pin
std::cout << "Enabling analog output on pin " << static_cast<int>(pin) << "..." << std::endl;
if(!settings->setMiscIOAnalogOutputEnabled(pin, true)) {
std::cerr << "Failed to enable analog output on pin " << static_cast<int>(pin) << std::endl;
device->close();
return -1;
}
// Set pin to specified voltage
std::cout << "Setting pin " << static_cast<int>(pin) << " to " << voltageLevel << "V..." << std::endl;
if(!settings->setMiscIOAnalogOutput(pin, voltage)) {
std::cerr << "Failed to set voltage on pin " << static_cast<int>(pin) << std::endl;
device->close();
return -1;
}
// Apply settings
std::cout << "Applying settings..." << std::endl;
if(!settings->apply()) {
std::cerr << "Failed to apply settings" << std::endl;
device->close();
return -1;
}
std::cout << "Analog output configured successfully!" << std::endl;
std::cout << "Pin " << static_cast<int>(pin) << ": Enabled at " << voltageLevel << "V" << std::endl;
device->close();
return 0;
}

View File

@ -0,0 +1,107 @@
"""
Basic analog output control example using icsneopy library.
Demonstrates how to configure and control analog outputs on supported devices.
Usage: python analog_out_basic.py <pin> <voltage> [--yes]
Arguments:
pin: Pin number (1-3 for RAD Galaxy)
voltage: Voltage level (0-5)
--yes: Skip confirmation prompt
"""
import sys
import icsneopy
def analog_output_example(pin: int, voltage: int, skip_confirm: bool = False):
"""Configure and control analog outputs."""
# Confirmation prompt
if not skip_confirm:
print(f"WARNING: This will set analog output pin {pin} to {voltage}V")
print("Make sure nothing sensitive is connected to this pin.")
response = input("Continue? (yes/no): ")
if response.lower() != "yes":
print("Aborted.")
return
devices = icsneopy.find_all_devices()
if not devices:
raise RuntimeError("No devices found")
device = devices[0]
try:
if not device.open():
raise RuntimeError("Failed to open device")
settings = device.settings
if not settings:
raise RuntimeError("Device settings not available")
print("Refreshing device settings...")
if not settings.refresh():
raise RuntimeError("Failed to refresh settings")
# Enable analog output on specified pin
print(f"Enabling analog output on pin {pin}...")
if not settings.set_misc_io_analog_output_enabled(pin, True):
raise RuntimeError(f"Failed to enable analog output on pin {pin}")
# Map voltage level to enum
voltage_map = {
0: icsneopy.Settings.MiscIOAnalogVoltage.V0,
1: icsneopy.Settings.MiscIOAnalogVoltage.V1,
2: icsneopy.Settings.MiscIOAnalogVoltage.V2,
3: icsneopy.Settings.MiscIOAnalogVoltage.V3,
4: icsneopy.Settings.MiscIOAnalogVoltage.V4,
5: icsneopy.Settings.MiscIOAnalogVoltage.V5
}
voltage_enum = voltage_map[voltage]
# Set pin to specified voltage
print(f"Setting pin {pin} to {voltage}V...")
if not settings.set_misc_io_analog_output(pin, voltage_enum):
raise RuntimeError(f"Failed to set voltage on pin {pin}")
# Apply settings
print("Applying settings...")
if not settings.apply():
raise RuntimeError("Failed to apply settings")
print("Analog output configured successfully!")
print(f"Pin {pin}: Enabled at {voltage}V")
finally:
device.close()
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Error: Missing required arguments\n")
print("Usage: python analog_out_basic.py <pin> <voltage> [--yes]")
print("\nArguments:")
print(" pin: Pin number (1-3 for RAD Galaxy)")
print(" voltage: Voltage level (0-5)")
print(" --yes: Skip confirmation prompt")
sys.exit(1)
try:
pin = int(sys.argv[1])
if pin < 1 or pin > 3:
print("Error: Invalid pin number (must be 1-3)")
sys.exit(1)
voltage = int(sys.argv[2])
if voltage < 0 or voltage > 5:
print("Error: Invalid voltage level (must be 0-5)")
sys.exit(1)
skip_confirm = "--yes" in sys.argv
analog_output_example(pin, voltage, skip_confirm)
except ValueError:
print("Error: Pin and voltage must be integers")
sys.exit(1)

View File

@ -128,6 +128,7 @@ enum class ChipID : uint8_t {
RADGALAXY2_SYSMON_CHIP = 123,
RADCOMET3_ZCHIP = 125,
Connect_LINUX = 126,
RADMOONT1S_ZCHIP = 130,
RADGigastar2_ZYNQ = 131,
RADGemini_MCHIP = 135,
Invalid = 255

View File

@ -89,7 +89,10 @@ typedef struct
uint8_t TqSync;
uint16_t BRP;
uint8_t auto_baud;
uint8_t innerFrameDelay25us;
uint8_t innerFrameDelay25us : 4;
uint8_t rsvd : 1;
uint8_t disableRetransmission : 1;
uint8_t canClk : 2;
} CAN_SETTINGS;
#define CAN_SETTINGS_SIZE 12
static_assert(sizeof(CAN_SETTINGS) == CAN_SETTINGS_SIZE, "CAN_SETTINGS is the wrong size!");
@ -747,6 +750,16 @@ typedef struct
namespace icsneo {
enum class MiscIOAnalogVoltage : uint8_t
{
V0 = 0,
V1 = 1,
V2 = 2,
V3 = 3,
V4 = 4,
V5 = 5
};
class IDeviceSettings {
public:
using TerminationGroup = std::vector<Network>;
@ -1180,6 +1193,9 @@ public:
return false;
}
virtual bool setMiscIOAnalogOutputEnabled(uint8_t pin, bool enabled);
virtual bool setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage voltage);
const void* getRawStructurePointer() const { return settingsInDeviceRAM.data(); }
void* getMutableRawStructurePointer() { return settings.data(); }
template<typename T> const T* getStructurePointer() const { return reinterpret_cast<const T*>(getRawStructurePointer()); }

View File

@ -185,6 +185,92 @@ public:
return nullptr;
}
}
bool setMiscIOAnalogOutputEnabled(uint8_t pin, bool enabled) override {
if(!settingsLoaded) {
report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
return false;
}
if(disabled) {
report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
return false;
}
if(readonly) {
report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
return false;
}
if(pin < 1 || pin > 2) {
report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error);
return false;
}
auto cfg = getMutableStructurePointer<radgalaxy_settings_t>();
if(cfg == nullptr) {
report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
return false;
}
const uint16_t bitMask = 1 << (pin - 1);
if(enabled) {
// Set pin as output and enable analog mode
cfg->misc_io_initial_ddr |= bitMask;
cfg->misc_io_analog_enable |= bitMask;
} else {
// Disable analog mode (leave DDR as-is)
cfg->misc_io_analog_enable &= ~bitMask;
}
return true;
}
bool setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage voltage) override {
if(!settingsLoaded) {
report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
return false;
}
if(disabled) {
report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
return false;
}
if(readonly) {
report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
return false;
}
if(pin < 1 || pin > 2) {
report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error);
return false;
}
auto cfg = getMutableStructurePointer<radgalaxy_settings_t>();
if(cfg == nullptr) {
report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
return false;
}
const uint8_t dacValue = static_cast<uint8_t>(voltage);
if(dacValue > 5) {
report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error);
return false;
}
if(pin == 1) {
// Update low nibble of high byte (bits 8-11), preserve pin 2 value
cfg->misc_io_initial_latch = (cfg->misc_io_initial_latch & 0xF0FF) | (static_cast<uint16_t>(dacValue) << 8);
} else { // pin == 2
// Update high nibble of high byte (bits 12-15), preserve pin 1 value
cfg->misc_io_initial_latch = (cfg->misc_io_initial_latch & 0x0FFF) | (static_cast<uint16_t>(dacValue) << 12);
}
return true;
}
};
}

View File

@ -27,6 +27,21 @@ public:
return supportedNetworks;
}
const std::vector<ChipInfo>& getChipInfo() const override {
static std::vector<ChipInfo> chips = {
{ChipID::RADJupiter_MCHIP, true, "MCHIP", "jupiter_mchip_ief", 1, FirmwareType::IEF}
};
return chips;
}
BootloaderPipeline getBootloader() override {
return BootloaderPipeline()
.add<EnterBootloaderPhase>()
.add<FlashPhase>(ChipID::RADJupiter_MCHIP, BootloaderCommunication::RED)
.add<EnterApplicationPhase>(ChipID::RADJupiter_MCHIP)
.add<ReconnectPhase>();
}
bool getEthPhyRegControlSupported() const override { return true; }
ProductID getProductID() const override {

View File

@ -37,6 +37,22 @@ public:
ProductID getProductID() const override {
return ProductID::RADMoonT1S;
}
const std::vector<ChipInfo>& getChipInfo() const override {
static std::vector<ChipInfo> chips = {
{ChipID::RADMOONT1S_ZCHIP, true, "ZCHIP", "RADMoonT1S_SW_bin", 1, FirmwareType::Zip}
};
return chips;
}
BootloaderPipeline getBootloader() override {
return BootloaderPipeline()
.add<EnterBootloaderPhase>()
.add<FlashPhase>(ChipID::RADMOONT1S_ZCHIP, BootloaderCommunication::RAD)
.add<EnterApplicationPhase>(ChipID::RADMOONT1S_ZCHIP)
.add<WaitPhase>(std::chrono::milliseconds(3000))
.add<ReconnectPhase>();
}
protected:
RADMoonT1S(neodevice_t neodevice, const driver_factory_t& makeDriver) : Device(neodevice) {
initialize<RADMoonT1SSettings>(makeDriver);

View File

@ -30,6 +30,22 @@ public:
ProductID getProductID() const override {
return ProductID::ValueCAN4Industrial;
}
const std::vector<ChipInfo>& getChipInfo() const override {
static std::vector<ChipInfo> chips = {
{ChipID::ValueCAN4Industrial_MCHIP, true, "MCHIP", "vcan4_ind_mchip_ief", 0, FirmwareType::IEF}
};
return chips;
}
BootloaderPipeline getBootloader() override {
return BootloaderPipeline()
.add<EnterBootloaderPhase>()
.add<FlashPhase>(ChipID::ValueCAN4Industrial_MCHIP, BootloaderCommunication::RED)
.add<EnterApplicationPhase>(ChipID::ValueCAN4Industrial_MCHIP)
.add<WaitPhase>(std::chrono::milliseconds(3000))
.add<ReconnectPhase>();
}
protected:
ValueCAN4Industrial(neodevice_t neodevice, const driver_factory_t& makeDriver) : ValueCAN4(neodevice) {
initialize<ValueCAN4IndustrialSettings>(makeDriver);