Skip to content

Commit 366c73e

Browse files
committed
Merge remote-tracking branch 'origin/develop' into imx6_allan_var_test
2 parents 6ac400b + 3fd0e29 commit 366c73e

File tree

12 files changed

+498
-70
lines changed

12 files changed

+498
-70
lines changed

cltool/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ include_directories(
1818
# Link the InertialSenseSDK static library
1919
link_directories(${IS_SDK_DIR})
2020

21+
add_definitions(-DIS_LOG_LEVEL=IS_LOG_LEVEL_MORE_INFO)
22+
2123
if(WIN32)
2224
# yamlcpp needs to know it's a static lib
2325
add_definitions(-DYAML_CPP_STATIC_DEFINE)

cltool/src/cltool.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,10 @@ bool cltool_parseCommandLine(int argc, char* argv[])
658658
g_commandLineOptions.list_devices = true;
659659
g_commandLineOptions.displayMode = cInertialSenseDisplay::DMODE_QUIET;
660660
}
661+
else if (startsWith(a, "-use-mdns"))
662+
{
663+
g_commandLineOptions.useMdns = true;
664+
}
661665
else if (startsWith(a, "-lmf="))
662666
{
663667
g_commandLineOptions.maxLogFileSize = (uint32_t)strtoul(&a[5], NULL, 10);
@@ -927,6 +931,9 @@ bool cltool_parseCommandLine(int argc, char* argv[])
927931
}
928932
}
929933

934+
// Always apply the verboseLevel to the SDK log system
935+
IS_SET_LOG_LEVEL((eLogLevel)g_commandLineOptions.verboseLevel);
936+
930937
// We are either using a serial port or replaying data
931938
if ((g_commandLineOptions.comPort.length() == 0) && !g_commandLineOptions.replayDataLog)
932939
{
@@ -1175,6 +1182,7 @@ void cltool_outputUsage()
11751182
cout << " -dboc" << boldOff << " Send stop-broadcast command `$STPB` on close." << endlbOn;
11761183
cout << " -h --help" << boldOff << " Display this help menu." << endlbOn;
11771184
cout << " -list-devices" << boldOff << " Discovers and prints a list of discovered Inertial Sense devices and connected ports." << endlbOn;
1185+
cout << " -use-mdns" << boldOff << " Enable mDNS network discovery of remote devices (e.g., socat TCP-forwarded serial ports)." << endlbOn;
11781186
cout << " -lm" << boldOff << " Listen mode for ISB. Disables device verification (-vd) and does not send stop-broadcast command on start." << endlbOn;
11791187
cout << " -magRecal[n]" << boldOff << " Recalibrate magnetometers: 0=multi-axis, 1=single-axis" << endlbOn;
11801188
cout << " -nmea=[s]" << boldOff << " Send NMEA message s with added checksum footer. Display rx messages. (`-nmea=ASCE,0,GxGGA,1`)" << endlbOn;

cltool/src/cltool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ typedef struct cmd_options_s // we need to name this to make MSVC happy, since w
176176
uint32_t updateFirmwareSlot = 0;
177177
uint32_t runDurationMs = 0; // Run for this many millis before exiting (0 = indefinitely)
178178
bool list_devices = false; // if true, dumps results of findDevices() including port name.
179+
bool useMdns = false; // if true, registers ISmDnsPortFactory for mDNS network device discovery
179180
EVFContainer_t evFCont = {0};
180181
EVMContainer_t evMCont = {0};
181182
EVOContainer_t evOCont;

cltool/src/cltool_main.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
4141
#include "CorrectionService.h"
4242
#include "NtripCorrectionService.h"
4343
#include "TcpPortFactory.h"
44+
#include "ISmDnsPortFactory.h"
4445
#include "util/natsort.h"
4546
#include "util/uri.hpp"
4647

@@ -956,13 +957,39 @@ void getMemoryEvent(InertialSense& inertialSenseInterface, uint32_t addrs, const
956957

957958
static int cltool_dataStreaming()
958959
{
960+
IS_LOG_OUTPUT(stdout);
961+
959962
// [C++ COMM INSTRUCTION] STEP 1: Instantiate InertialSense Class
960963
// Create InertialSense object, passing in data callback function pointer.
961-
InertialSense inertialSenseInterface({}, {&CltoolDeviceFactory::getInstance()});
964+
// Build explicit port factory list — only include ISmDnsPortFactory when -use-mdns is specified
965+
SerialPortFactory& spf = SerialPortFactory::getInstance();
966+
spf.portOptions.defaultBaudRate = g_commandLineOptions.baudRate;
967+
spf.portOptions.defaultBlocking = false;
968+
TcpPortFactory& tpf = TcpPortFactory::getInstance();
969+
tpf.portOptions.defaultBlocking = false;
970+
971+
std::vector<PortFactory*> portFactories = {&spf, &tpf};
972+
if (g_commandLineOptions.useMdns) {
973+
ISmDnsPortFactory& mdpf = ISmDnsPortFactory::getInstance();
974+
mdpf.portOptions.defaultBlocking = false;
975+
portFactories.push_back(&mdpf);
976+
}
977+
978+
InertialSense inertialSenseInterface(portFactories, {&CltoolDeviceFactory::getInstance()});
962979
g_inertialSenseInterface = &inertialSenseInterface;
963980
inertialSenseInterface.setErrorHandler(cltool_errorCallback);
964981
inertialSenseInterface.EnableDeviceValidation(!g_commandLineOptions.disableDeviceValidation);
965982

983+
// Pre-warm mDNS cache so network devices are discoverable when Open() calls discoverPorts()
984+
if (g_commandLineOptions.useMdns) {
985+
printf("Discovering mDNS devices...\n");
986+
uint32_t deadline = current_timeMs() + 3000;
987+
while (current_timeMs() < deadline) {
988+
ISmDnsPortFactory::tick();
989+
SLEEP_MS(50);
990+
}
991+
}
992+
966993
// [C++ COMM INSTRUCTION] STEP 2: Open serial port
967994
if (!inertialSenseInterface.Open(g_commandLineOptions.comPort.c_str(), g_commandLineOptions.baudRate, g_commandLineOptions.disableBroadcastsOnClose))
968995
{

src/DeviceFactory.cpp

Lines changed: 103 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @file DeviceFactory.cpp
2+
* @file DeviceFactory.cpp
33
* @brief ${BRIEF_DESC}
44
*
55
* @author Kyle Mallory on 3/10/25.
@@ -24,63 +24,120 @@ void DeviceFactory::locateDevices(std::function<bool(DeviceFactory*, const dev_i
2424
}
2525

2626
/**
27-
* Attempts to identify a specific type of device on the specified port, within the timeout period.
28-
* @param deviceCallback a function to be called if this Factory identified a possible/viable Inertial Sense device on the specified port
29-
* @param port the port to check for an Inertial Sense device. In most uses, the port specified should already be opened, however if the
30-
* port is not opened, this function will attempt to open it in order for ensure discovery.
31-
* @param hdwId a hardware Id qualifier that can be used to narrow the type of device. There is no direct indication that a hardware type
32-
* failed to match.
33-
* @param timeout the maximum time to attempt to identify a device before giving up. There is no direct indication that a timeout occurred.
34-
* @return true if a device was detected, otherwise false. Note that a false can result for any number of reasons, including invalid port,
35-
* hdwId mismatch, or a timeout.
27+
* Blocking single-port discovery. Implemented as a wrapper around the three-phase validation protocol.
3628
*/
3729
bool DeviceFactory::locateDevice(std::function<bool(DeviceFactory*, const dev_info_t&, port_handle_t)>& deviceCallback, port_handle_t port, uint16_t hdwId, uint16_t timeoutMs) {
38-
if (!portIsValid(port))
39-
return false; // TODO: Should we do anything special if the port is invalid? Really, we should never get here with an invalid port...
30+
// Check for existing device on this port
31+
device_handle_t dev = DeviceManager::getInstance().getDevice(port);
32+
if (dev) {
33+
if (dev->matchesHdwId(hdwId)) {
34+
if (!dev->port)
35+
dev->assignPort(port);
36+
return dev->validate(timeoutMs);
37+
}
38+
return false;
39+
}
40+
41+
// Phase 1: begin validation
42+
auto ctx = beginValidation(port, hdwId, timeoutMs);
43+
if (!ctx)
44+
return false;
45+
46+
// Phase 2: blocking loop
47+
while (!ctx->complete) {
48+
stepValidation(*ctx);
49+
if (!ctx->complete)
50+
SLEEP_MS(2);
51+
}
4052

41-
// can we open the port?
42-
if (!portIsOpened(port)) {
43-
log_debug(IS_LOG_DEVICE_FACTORY, "Opening serial port '%s'", portName(port));
53+
// Phase 3: complete validation
54+
dev_info_t devInfo;
55+
if (completeValidation(*ctx, devInfo)) {
56+
return deviceCallback(this, devInfo, port);
57+
}
58+
return false;
59+
}
60+
61+
/**
62+
* Phase 1: Opens the port, validates it, creates a base ISDevice for probing, and calls the
63+
* onBeginValidation() hook to allow the factory to decline or do custom setup.
64+
*/
65+
std::unique_ptr<DeviceFactory::ValidationContext> DeviceFactory::beginValidation(port_handle_t port, uint16_t hdwId, uint32_t timeoutMs, std::shared_ptr<ISDevice> sharedDevice) {
66+
if (!portIsValid(port)) {
67+
log_more_debug(IS_LOG_DEVICE_FACTORY, "beginValidation: port '%s' is invalid, skipping.", portName(port));
68+
return nullptr;
69+
}
70+
71+
// Open port if needed (only when no shared device provided, i.e. standalone/blocking path)
72+
if (!sharedDevice && !portIsOpened(port)) {
4473
if (!portValidate(port) || (portOpen(port) != PORT_ERROR__NONE)) {
45-
log_debug(IS_LOG_DEVICE_FACTORY, "Error opening serial port '%s'. Ignoring. Error was: %s", portName(port), SERIAL_PORT(port)->error);
46-
portClose(port); // failed to open
74+
portClose(port);
4775
portInvalidate(port);
48-
return false;
49-
// m_ignoredPorts.push_back(curPortName); // record this port name as bad, so we don't try and reopen it again
76+
return nullptr;
5077
}
5178
}
5279

53-
// We can only validate devices connected on COMM ports, since ISComm is needed to parse/communicate with the device
54-
if (!(portType(port) & PORT_TYPE__COMM))
55-
return false;
80+
// Only COMM ports can be validated via ISComm protocol
81+
if (!(portType(port) & PORT_TYPE__COMM)) {
82+
log_more_debug(IS_LOG_DEVICE_FACTORY, "beginValidation: port '%s' is not a COMM port (type=0x%04X), skipping.", portName(port), portType(port));
83+
return nullptr;
84+
}
5685

5786
if (timeoutMs <= 0)
5887
timeoutMs = deviceTimeout;
5988

60-
// at this point, the port should be opened...
61-
device_handle_t dev = DeviceManager::getInstance().getDevice(port);
62-
if (!dev) {
63-
// no previous device exists, so identify the device and then register it with the manager
64-
int validationResult = 0;
65-
ISDevice localDev(hdwId, port);
66-
do {
67-
is_comm_port_parse_messages(port); // Read data directly into comm buffer and call callback functions
68-
validationResult = localDev.validateAsync(timeoutMs);
69-
SLEEP_MS(2);
70-
} while (!validationResult);
89+
// Let the factory decline this port
90+
if (!onBeginValidation(port, hdwId)) {
91+
log_more_debug(IS_LOG_DEVICE_FACTORY, "beginValidation: factory declined port '%s'.", portName(port));
92+
return nullptr;
93+
}
7194

72-
if (localDev.hasDeviceInfo() && localDev.matchesHdwId(hdwId)) {
73-
// note that the deviceHandler callback can still reject the device for reasons
74-
return deviceCallback(this, localDev.devInfo, port);
75-
}
76-
} else if (dev->matchesHdwId(hdwId)) {
77-
if (!dev->port)
78-
dev->assignPort(port);
79-
else if (dev->port != port) {
80-
// FIXME: this isn't good - it shouldn't happen, but it might... we should deal with it.
81-
}
82-
// a device exists associated with this port already, there isn't anything to do.
83-
return dev->validate(timeoutMs);
95+
log_more_debug(IS_LOG_DEVICE_FACTORY, "beginValidation: created validation context for port '%s' (hdwId=0x%04X, timeout=%dms)", portName(port), hdwId, timeoutMs);
96+
auto ctx = std::make_unique<ValidationContext>();
97+
ctx->port = port;
98+
ctx->device = sharedDevice ? sharedDevice : std::make_shared<ISDevice>(hdwId, port);
99+
ctx->hdwId = hdwId;
100+
ctx->timeoutMs = timeoutMs;
101+
return ctx;
102+
}
103+
104+
/**
105+
* Phase 2: Reads pending data from the port, then calls the onStepValidation() hook to
106+
* advance validation state.
107+
*/
108+
int DeviceFactory::stepValidation(ValidationContext& ctx) {
109+
if (ctx.complete)
110+
return ctx.result;
111+
112+
is_comm_port_parse_messages(ctx.port);
113+
ctx.result = onStepValidation(*ctx.device, ctx.timeoutMs);
114+
ctx.complete = (ctx.result != 0);
115+
if (ctx.result == 1) {
116+
log_debug(IS_LOG_DEVICE_FACTORY, "stepValidation: port '%s' validated successfully.", portName(ctx.port));
117+
} else if (ctx.result == -1) {
118+
log_debug(IS_LOG_DEVICE_FACTORY, "stepValidation: port '%s' timed out.", portName(ctx.port));
84119
}
85-
return false;
120+
return ctx.result;
86121
}
122+
123+
/**
124+
* Phase 3: Checks that validation succeeded and the device has info, then calls the
125+
* onCompleteValidation() hook to let the factory accept or reject.
126+
*/
127+
bool DeviceFactory::completeValidation(ValidationContext& ctx, dev_info_t& devInfoOut) {
128+
if (ctx.result <= 0)
129+
return false;
130+
if (!ctx.device || !ctx.device->hasDeviceInfo()) {
131+
log_debug(IS_LOG_DEVICE_FACTORY, "completeValidation: port '%s' validated but has no device info.", portName(ctx.port));
132+
return false;
133+
}
134+
if (!onCompleteValidation(ctx.device->devInfo, ctx.hdwId)) {
135+
log_debug(IS_LOG_DEVICE_FACTORY, "completeValidation: factory rejected device on port '%s' (hdwId=0x%04X).", portName(ctx.port), ctx.hdwId);
136+
return false;
137+
}
138+
139+
devInfoOut = ctx.device->devInfo;
140+
log_debug(IS_LOG_DEVICE_FACTORY, "completeValidation: factory accepted device %s on port '%s'.",
141+
ISDevice::getIdAsString(devInfoOut).c_str(), portName(ctx.port));
142+
return true;
143+
}

src/DeviceFactory.h

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,30 @@
2222

2323
/**
2424
* An interface, to be implemented as a singleton, that is responsible for identifying various types of ISDevice and allocating and releasing them.
25+
*
26+
* Supports both blocking single-port discovery (locateDevice) and concurrent multi-port discovery
27+
* via a three-phase protocol (beginValidation / stepValidation / completeValidation) driven by
28+
* DeviceManager::discoverDevices(). Subclasses customize behavior by overriding the virtual hooks
29+
* (onBeginValidation, onStepValidation, onCompleteValidation) without managing ValidationContext.
2530
*/
2631
class DeviceFactory {
2732
public:
2833
virtual ~DeviceFactory() = default;
2934

35+
/**
36+
* Per-port, per-factory state during concurrent validation. Managed by the base class
37+
* beginValidation/stepValidation/completeValidation methods. Subclasses should not need
38+
* to interact with this directly — override the onXxx hooks instead.
39+
*/
40+
struct ValidationContext {
41+
port_handle_t port = nullptr;
42+
std::shared_ptr<ISDevice> device;
43+
uint16_t hdwId = IS_HARDWARE_ANY;
44+
uint32_t timeoutMs = 3000;
45+
bool complete = false;
46+
int result = 0; // -1 = failed/timeout, 0 = in-progress, 1 = success
47+
};
48+
3049
/**
3150
* A function to be implemented in the factory responsible for allocating the underlying device type and returning a pointer to it
3251
* This function should NOT manipulate the underlying port, such as opening, etc.
@@ -66,6 +85,7 @@ class DeviceFactory {
6685

6786
/**
6887
* Attempts to identify a specific type of device on the specified port, within the timeout period.
88+
* Implemented as a blocking wrapper around beginValidation/stepValidation/completeValidation.
6989
* @param deviceCallback a function to be called if this Factory identified a possible/viable Inertial Sense device on the specified port
7090
* @param port the port to check for an Inertial Sense device. In most uses, the port specified should already be opened, however if the
7191
* port is not opened, this function will attempt to open it in order for ensure discovery.
@@ -77,14 +97,71 @@ class DeviceFactory {
7797
*/
7898
virtual bool locateDevice(std::function<bool(DeviceFactory*, const dev_info_t&, port_handle_t)>& deviceCallback, port_handle_t port, uint16_t hdwId, uint16_t timeout);
7999

100+
/**
101+
* Phase 1: Prepare a ValidationContext for concurrent validation on this port.
102+
* If a shared ISDevice probe is provided, it will be used (concurrent discovery shares one
103+
* ISDevice per port across all factories). Otherwise, opens the port and creates a new probe.
104+
* Calls onBeginValidation() to allow the factory to decline or do custom setup.
105+
* @return a ValidationContext, or nullptr if this port cannot be validated
106+
*/
107+
std::unique_ptr<ValidationContext> beginValidation(port_handle_t port, uint16_t hdwId, uint32_t timeoutMs, std::shared_ptr<ISDevice> sharedDevice = nullptr);
108+
109+
/**
110+
* Phase 2: Non-blocking validation step. Reads pending data from the port, then calls
111+
* onStepValidation() to advance validation state. Called repeatedly during the concurrent
112+
* loop, only when this factory is the active round-robin slot for this port.
113+
* @return -1 = failed/timeout, 0 = still in progress, 1 = validated successfully
114+
*/
115+
int stepValidation(ValidationContext& ctx);
116+
117+
/**
118+
* Phase 3: After stepValidation returns 1, checks that the device has valid info and calls
119+
* onCompleteValidation() to let the factory accept or reject the device.
120+
* @param ctx the completed validation context
121+
* @param devInfoOut populated with the validated device info on success
122+
* @return true if this factory claims the device, false otherwise
123+
*/
124+
bool completeValidation(ValidationContext& ctx, dev_info_t& devInfoOut);
125+
80126
/**
81127
* Assigns a Factory-specific timeout period for each port
82128
* @param timeout
83129
*/
84130
void setPerDeviceTimeout(uint32_t timeout) { deviceTimeout = timeout; };
85131

132+
uint32_t getPerDeviceTimeout() const { return deviceTimeout; };
133+
134+
protected:
135+
/**
136+
* Virtual hook called during Phase 1. Override to decline certain ports or perform
137+
* custom port setup before validation begins.
138+
* @param port the port about to be validated
139+
* @param hdwId the hardware Id filter
140+
* @return true to proceed with validation, false to skip this port
141+
*/
142+
virtual bool onBeginValidation(port_handle_t port, uint16_t hdwId) { (void)port; (void)hdwId; return true; }
143+
144+
/**
145+
* Virtual hook called during Phase 2. Override to inject custom validation logic
146+
* (e.g., factory-specific queries or handshakes). The base class has already called
147+
* is_comm_port_parse_messages() before invoking this hook.
148+
* @param device the ISDevice probe to validate against
149+
* @param timeoutMs the timeout for this validation attempt
150+
* @return -1 = failed/timeout, 0 = still in progress, 1 = validated successfully
151+
*/
152+
virtual int onStepValidation(ISDevice& device, uint32_t timeoutMs) { return device.validateAsync(timeoutMs); }
153+
154+
/**
155+
* Virtual hook called during Phase 3. Override to accept or reject a validated device
156+
* based on factory-specific criteria (e.g., hardware type, firmware version).
157+
* @param devInfo the validated device info
158+
* @param hdwId the hardware Id filter from the discovery request
159+
* @return true to claim this device, false to pass to the next factory
160+
*/
161+
virtual bool onCompleteValidation(const dev_info_t& devInfo, uint16_t hdwId) { (void)devInfo; (void)hdwId; return true; }
162+
86163
private:
87-
uint32_t deviceTimeout = 3000;
164+
uint32_t deviceTimeout = 3000; // should match DeviceManager::DISCOVERY__DEFAULT_TIMEOUT
88165
};
89166

90167
class ImxDeviceFactory : public DeviceFactory {

0 commit comments

Comments
 (0)