Repository navigation
I2CDeviceIdentificationAndPolling
After I2C device scanning detects that a device is present at an address (device responds to I2C transactions), the system performs three sequential operations:
- Device Type Identification: Determine what specific type of device is present
- Device Initialization: Configure the device for operation
- Device Polling: Regularly retrieve data from the device
This page covers these three distinct phases of device lifecycle management.
The system maintains a database of known device types in DeviceTypeRecords. For a comprehensive reference on the full record format — including devInfoJson, binary response decoding and actions — see Device Type Record Format.
Each device type record contains:
| Field | Description | Example |
|---|---|---|
deviceType |
Name of the device type |
"LSM6DS3", "MT6701"
|
addresses |
I2C addresses where this device may be found | "0x6a,0x6b" |
detectionValues |
Commands to identify the device type |
"0x75=0x6a" (WHO_AM_I check) |
initValues |
Commands to initialize the device |
"0x1060" (write 0x60 to register 0x10) |
pollInfo |
Configuration for polling the device | See Polling section |
pollDataSizeBytes |
Size of data returned by each poll | 12 |
devInfoJson |
Device information, polled data response format and actions — see Device Type Record Format | Manufacturer, description, response decode schema, actions |
pollResultDecodeFn |
Function to decode poll results | C++ function pointer |
Base Device Type Records:
- Compiled into the firmware from the device definition files (
DeviceTypeRecords.jsonetc.). The records, strings and field tables areconstdata in flash - Covers common I2C devices (IMUs, sensors, displays, etc.)
- The address map (identification candidates per address), scan priority lists and hardware type ID lookup are built at runtime from all records, base and extended, and rebuilt when a record is added
Extended Device Type Records:
- Created dynamically at runtime
- Stored in RAM (with PSRAM support if available)
- Added by custom device classes or application code
- Limited by the DeviceManager
typeTablesettings (by default 32 records and 20 KB without PSRAM, 256 records and 256 KB with), shared by everything that registers them, including statically configured devices that supply a record - Identified and scanned like base records: candidates at their addresses (before base records), scanned at their
scanPriority - Allow applications to add support for new device types
Custom devices can register their own device type records:
DeviceTypeRecordDynamic devTypeRec(
"CUSTOM_SENSOR", // Device type key
"0x48,0x49", // I2C addresses
"0x00=0x1234", // Detection (WHO_AM_I)
"0x0180", // Initialization: write 0x80 to register 0x01
R"({"c":"0x01=r6","i":100,"s":10})", // Polling info - a JSON object (i is in milliseconds)
6, // Poll data size
"{\"name\":\"Custom\",\"desc\":\"Custom Sensor\"}", // Device info JSON
myDecodeFunction // Decode function
);
uint16_t deviceTypeIndex = 0;
deviceTypeRecords.addExtendedDeviceTypeRecord(devTypeRec, deviceTypeIndex);An extended record's addresses are scanned at its scanPriority; the scanner reloads its lists when records are added. Extended records are tried before compiled records at the same address.
Records can also be supplied without code, in a device type file loaded at startup - which can also override compiled records and disable types.
When scanning detects a device online, the DeviceIdentMgr attempts to identify its type.
-
Get Candidate Types: Query
DeviceTypeRecordsfor all device types registered at the detected address. Extended (runtime) records come first, then compiled records in the order they appear in the JSON files - Iterate Candidates: Try each candidate type's detection sequence in that order. The first match wins — later candidates are not tried
- Execute Detection Commands: Send write/read sequences and check responses
- Match Results: Compare read data against expected values using masks
- Record Type: Store the identified device type index if a match is found
- Fallback: If no candidate matched, a registered custom identification handler (if any) is called
Because the first match wins, a record whose detection is empty (or weak) at a shared address must come after the more specific records at that address. A record with an empty detectionValues matches any device that responds.
Detection records specify how to identify a device:
detectionValues: "writeHex=readCheck"
Write Data:
- Hex string:
"0x75"or"75"- register address to read - Can be multiple bytes:
"0x0F30"- write to register 0x0F, subregister 0x30
Read Check: Multiple formats supported. The number of bytes read is the length of the (first) check value.
| Format | Example | Description |
|---|---|---|
| Hex value | "0x6a" |
Expect exact value 0x6a. The 0x prefix is required — "6a" is rejected and the record will never match |
| Multi-byte hex | "0xA005" |
Read 2 bytes, expect 0xA0 then 0x05 |
| Multiple options | "0x6a,0x6c" |
Accept 0x6a OR 0x6c (all options must be the same length) |
| Binary mask | "0b11xx00xx" |
Check specific bits (x = don't care); 8 bits per byte read |
| Pause only |
"p25" (as in "0x00FF=p25") |
Write, then pause 25 ms; nothing is read or checked |
rN reads N bytes and compares nothing (the device must answer). A :crc8w suffix requires each 2-byte word read to be followed by a valid Sensirion CRC-8. See Command String Grammar for what the build reports.
detectionValues: "0x0f=0x69"
This means:
- Write
0x0f(WHO_AM_I register address) - Read 1 byte
- Check if result is
0x69(LSM6DS3) - If match → device identified as LSM6DS3 type
A list of alternatives such as 0x0f=0x69,0x6a matches any of them. The 0x6A parts (LSM6DS3TR-C, LSM6DSL, LSM6DSM) have their own record, LSM6DS3TRC, because their FIFO settings differ.
For devices without unique WHO_AM_I registers, binary masks allow checking specific bits:
detectionValues: "0x00=0b11110001"
-
1= must be 1 -
0= must be 0 -
x= don't care (bit ignored)
Example: "0b111xx001" matches 0b11100001, 0b11101001, 0b11110001, 0b11111001
Complex devices may require multiple read operations for identification:
detectionValues: "0x75=0x6a&0xD0=0x60,0x61"
This requires:
- Register 0x75 must return 0x6a, AND
- Register 0xD0 must return 0x60 OR 0x61
Pause between write and read:
Each detection step is a single write-then-read transaction, and a pNN in a step is applied after that transaction completes. So a device that needs time between being sent a command and returning its answer is described with two steps — a write followed by a pause, then a read with no write:
detectionValues: "0x00FF=p25&=0bXXXXXXXX00000100"
-
0x00FF=p25= write 0x00, 0xFF, then pause 25 ms (nothing read) -
=0bXXXXXXXX00000100= read 2 bytes (no write) and check the second byte is 0x04
This is the pattern used by the Trill touch sensors.
-
Devices are identified only once the device type records are complete: until the device type file (or another registered record source) has been loaded, an address found online stays unidentified, and is identified as soon as the records are complete
-
An address must respond on two consecutive scans before it is considered online; it is then identified at once
-
Each element is identified on its own schedule by the bus's identification scheduler (up to 8 elements per pass of the bus task, within
identBudgetUs). If an attempt doesn't identify it, it is tried again after a delay that depends on the outcome:Outcome Retried No record matched, and the identification hook (if any) said NotMineafter 3 s, then 6, 12, 24, 48 s and every 60 s after that - and at once when the device type records change (a record added, a policy changed) or a hook is registered The hook returned Deferredevery 3 s A bus error (other than a NACK) interrupted detection, or initialisation after 100 ms, doubling to 10 s An element is only tried when it answered its last scan, so a device that has just moved to another address isn't identified at its old one
-
Records at an address are tried in order of descending
identPriority, then descending detection strength (the number of bits the detection compares), then extended records before base records, then catalogue order (device type key order for records selected withDeviceTypeSources.jsonor read from a records folder). So a record whose detection compares nothing (e.g. one with nodetectionValues) is tried after records that check the device -
A NACK during a detection read means "no match" for that candidate, so identification moves on to the next candidate. Any other bus error ends the attempt (no weaker candidate or identification hook is tried) and it is retried later
-
A pause of 10 ms or more after a detection or initialisation step doesn't hold up the bus: the element's identification resumes when the pause is over, and the device's address is barred meanwhile (other devices are scanned and polled as usual)
-
Once identified, a device keeps its type until it goes offline (three consecutive failed polls). If a poll fails and a later one succeeds, the failure count is reset and the device is re-verified: its type's detection is run again; if it still matches it is re-initialised, otherwise it is identified again (and may get another type). Records identified by a hook (
probeOnly) and records whose detection has a long pause aren't re-verified -
Identity changes are reported to device status listeners (
DeviceManager::registerForDeviceStatusChange) inBusAddrStatus::identEvent:Identified,TypeChanged(re-identified as another type while online) orLost(no longer identified, or gone offline), withprevDeviceTypeIndex.isNewlyIdentifiedis also set forIdentifiedandTypeChanged. A listener that caches anything derived from the type (a decoder, a topic) must rebuild it onTypeChangedand drop it onLost -
Settings made with
devman/devconfigare applied again when the device is identified (or re-verified) again as the same type - after a re-identification, or after it has gone offline and come back. They are forgotten when it is identified as another type. Withsave=1they are also kept over a restart -
Devices can be identified again on request (
devman/reident) - all, those not identified, one device or the devices of a type - and changing a type's policy (devman/typepolicy) identifies the devices it affects again. An identified device keeps its type, and is polled, until a different result is known
After successful identification, devices are initialized using the initValues from their device type record.
-
Parse Init Values: Extract write/read pairs from
initValuesstring - Execute Init Commands: Send each command in sequence
- Wait for Completion: Allow bar access time between commands if specified
- Mark Initialized: Device is ready for polling
initValues: "writeHex=readSpec&writeHex=readSpec&..."
Separated by & (or ;) characters. Each step is split at its first =: everything before it is written, everything after it is the read spec. Data to write goes before the =, never after it.
Write Hex:
- Hex string of bytes to write: register address followed by data
- Example:
"0x1060"writes bytes 0x10, 0x60 (i.e. 0x60 to register 0x10) - Empty (as in
"=p20") writes nothing
Read Spec:
- Empty (or no
=at all): No read (write-only) -
rN: Read N bytes after the write (the data read is discarded) -
pNN: Pause NN milliseconds after this step before the next one. A pause on its own must be written=pNN— a barepNNwith no=is treated as bytes to write
Write-Only Init (Common):
initValues: "0x1060&0x1104"
- Write 0x60 to register 0x10
- Write 0x04 to register 0x11
Write then Read:
initValues: "0x00=r1"
- Write 0x00, then read 1 byte (e.g. to clear an interrupt flag by reading it). The value read is not checked
Init with Delays:
initValues: "0x1080=p100"
- Write 0x80 to register 0x10
- Wait 100ms (soft reset delay)
Complex Initialization:
initValues: "0x1080=p100&0x1160&0x1204&0x1310"
- Soft reset with 100ms delay
- Configure three registers
Pause as a separate step:
initValues: "0x8002&=p20&0x8001"
- Write 0x02 to register 0x80, wait 20 ms, then write 0x01 to register 0x80
Bar access (pNN) prevents the bus from being accessed for N milliseconds:
- Purpose: Allow device internal processing (soft reset, calibration, etc.)
- Implementation: the device's address is barred for the time after the step. During identification and initialisation a pause of 10 ms or more ends that element's work until the pause is over (shorter pauses are waited inline); in poll commands the rest of the poll is made after the pause
- Use Case: After power-on, soft reset, or mode changes
A step that is NACKed doesn't stop initialisation (some commands, such as a soft reset, are normally NACKed): the remaining steps are sent, and if any step was NACKed initialisation is repeated once; the device is then used, and a warning is logged. Any other bus error during initialisation means the device wasn't initialised: it isn't used, and identification is repeated after a backoff (100 ms doubling to 10 s). A device is initialised again when it is re-identified, re-verified, or goes offline and comes back.
Two hooks on RaftBusDevicesIF let application code take part in bus activity without
driving the bus from another task. Both are optional and default to no-ops.
enum class RaftDeviceIdentVerdict { NotMine, Handled, Deferred };
typedef RaftDeviceIdentVerdict (*RaftNewDeviceIdentFn)(
BusElemAddrType address, DeviceStatus& deviceStatus, void* pCtx);
virtual void registerNewDeviceIdentHandler(RaftNewDeviceIdentFn fn, void* pCtx);Called after the default address-based identification, and only when no device type record for the address matched (including addresses with no record at all). Use it for devices whose address does not indicate their type — the handler probes the device instead.
Note that this means the handler also runs for an address whose records exist but whose detection
didn't match, so it should only send probes that are safe for any device. It isn't run after a bus
error during detection, or at an address claimed by a record with policy disabledClaim.
| Verdict | Meaning |
|---|---|
NotMine |
Not this handler's device. The device stays unidentified; it is offered again after 3 s, backing off to every 60 s (and at once when the device type records change or a handler is registered) |
Handled |
Identified. The handler sets deviceStatus.deviceTypeIndex; the bus fills in the rest and the existing polling/decode pipeline is reused |
Deferred |
Not yet identifiable (e.g. mid-arbitration). Leave the device unidentified and unpolled; it is offered again every 3 s |
The handler runs on the bus task (in the identification scheduler) with the device's slot already selected, so it must not acquire a bus lease.
Registration order is load-bearing. Scanning is already running by the time
setup()executes, so anything the handler depends on must exist before the handler is registered. Registering first and constructing dependencies afterwards leaves a window in which the handler runs with them null — and a handler that returnsHandledin that window leaves the device permanently identified against the wrong state.
typedef bool (*RaftBusTaskServiceFn)(void* pCtx);
virtual void registerBusTaskServiceHandler(RaftBusTaskServiceFn fn, void* pCtx);Called once per bus-worker iteration, so application work that must drive the bus runs on the task that owns it. The alternative — driving the bus from another task — is unsafe: the bus has no lock, only a pause flag with no notion of an owner, so two such callers corrupt each other.
The handler must return promptly (a few ms). It shares the loop with polling, so long work must be a state machine that yields, exactly as scanning is.
Returning true means "I am mid-operation": the worker then skips device polling for
that iteration, so a timed sequence of transactions does not queue behind polling traffic. Use
it only for work whose timing matters, and make sure it becomes false again — existing devices
are not polled while it is true.
Scanning is deliberately not suspended by this, and must not be. Multiplexer recovery is driven by the scanner:
BusMultiplexers::taskService()is empty, and it iselemStateChange()from a scan that re-detects and re-initialises a mux wedged by a device reset. A handler that resets devices depends on that recovery running.
virtual bool isBusStuck() const; // default false
virtual bool clearBusStuck(); // clock the bus until a device releasesA stuck bus is not the same as a busy or faulty device. Every address appears to ACK — an ACK
is the line pulled low — and every read returns zeros, so transaction results alone cannot
tell a wedged bus from a device answering with garbage. A caller that sees "everything ACKs,
every read is bad" should ask isBusStuck() rather than infer.
Polling retrieves data from identified, initialized devices at regular intervals.
| Operation | Scanning | Polling |
|---|---|---|
| Purpose | Detect device presence | Retrieve sensor data |
| Target | All addresses | Known online devices only |
| Frequency | Periodic background | Regular, device-specific |
| Data Size | Minimal (ACK/NACK) | Full sensor readings |
| Processing | Lightweight | May include decoding |
Poll information is specified in the device type record's pollInfo field:
pollInfo is a JSON object (in DeviceTypeRecords.json it is written as an object; a runtime DeviceTypeRecordDynamic takes it as JSON text). A compact "c=...&i=..." string is not supported — the device would never be polled.
"pollInfo": { "c": "writeHex=readSpec&writeHex=readSpec&...", "i": intervalMs, "s": numSamples, "h": busHz }Parameters:
| Parameter | Format | Description | Example |
|---|---|---|---|
c |
Command specs | Write/read sequences | "0x01=r6" |
i |
Interval | Poll interval in milliseconds (see Poll Interval Units) |
"i": 100 (100 ms) |
s |
Samples | Number of poll results to store | "s": 10 |
The poll bus speed isn't part of the record: see Dynamic Bus Speed.
Each poll command specifies a write/read operation:
writeHex=readSpec
Write Hex:
- Hex string:
"01"- write 0x01 to device - Multiple bytes:
"0F30"- write 0x0F then 0x30
Read Spec:
-
rN: Read N bytes - Example:
r6= read 6 bytes -
=rN(empty write): read N bytes without writing first - Empty (
"0x00="): write only -
pNN: bar access for NN ms after this command (e.g."0x0002=p100")
Simple Polling:
"pollInfo": { "c": "0x01=r6", "i": 100, "s": 10 }- Command: Write 0x01, read 6 bytes
- Interval: 100ms
- Store: 10 poll results
Multiple Poll Commands:
"pollInfo": { "c": "0x22=r6&0x23=r6", "i": 50, "s": 20 }- Command 1: Write 0x22, read 6 bytes (gyro data)
- Command 2: Write 0x23, read 6 bytes (accel data)
- Interval: 50ms
- Store: 20 poll results, each holding both reads (12 bytes)
High-Speed Polling:
"pollInfo": { "c": "0x00=r2", "i": 1, "s": 100 }- Command: Write 0x00, read 2 bytes
- Interval: 1ms (the shortest interval expressible in
pollInfo.i) - Store: 100 poll results (for burst data collection)
Polling with Custom Bus Speed: the bus speed is a choice for the board, so it is set in the SysType's DevMan typeSettings, not in the record:
"typeSettings": { "MyIMU": { "busHz": 400000, "busHzSlots": "4,5,6" } }- Bus speed: 400 kHz for this type's polls (restored to the default after each poll), only on slots 4, 5 and 6 (leave out
busHzSlotsfor every slot) - This is useful when different slots have different electrical characteristics (e.g. cable length, capacitance)
- Slot numbers range from 0 to 63; the value is stored internally as a 64-bit bitmask
Poll results are stored with timestamps for efficient transmission:
Storage Format (one poll result):
[Timestamp][Data from command 1][Data from command 2]...
Timestamp:
- 2 bytes, wrap-around at 65536
- Resolution: 100 µs (
POLL_RESULT_RESOLUTION_US), so it wraps every 6.5536 s - Allows reconstruction of exact timing
Data:
- Raw bytes from device: the reads of all the poll commands, concatenated
- Each poll result has its own timestamp
- Up to
spoll results are held in a ring buffer
Example Storage (6-byte sensor, 3 poll results held):
[TS][6 bytes] [TS][6 bytes] [TS][6 bytes] = 3 x 8 = 24 bytes
Poll intervals are device-specific and optimized for:
Fast Polling (1-50ms):
- IMU sensors (accelerometer, gyroscope)
- High-speed position encoders
- Time-critical control applications
Medium Polling (50-500ms):
- Temperature sensors
- Pressure sensors
- Environmental sensors
Slow Polling (500ms-5s):
- Configuration registers
- Status checks
- Battery monitors
The REST API endpoint devman/devconfig can be used to configure polling parameters for a device at runtime:
-
intervalUs- sets the polling interval in microseconds -
numSamples- sets the number of buffered poll samples (changing this clears buffered data) -
busHz- sets the I2C bus speed in Hz for this device's polls (0 = revert to default) -
busHzSlots- comma-separated list of slot numbers to whichbusHzapplies (omit = keep current mask; empty or0= all slots)
Example: Set 400 kHz bus speed for a device, but only on slots 4, 5, 6:
GET /devman/devconfig?deviceid=1_6a&busHz=400000&busHzSlots=4,5,6
See DeviceManager REST API for details. Add save=1 to keep the settings over a restart.
Polling of one device can be stopped and resumed with devman/devenable (poll=0 / poll=1): the device stays identified and can still be sent commands, but isn't polled and publishes no data. The setting is kept while the device is offline. devman/reinit runs a device's initialisation again (e.g. after it has been reset by other means).
For devices with multiple poll commands, polling can be interrupted and resumed:
- Partial State: Tracks which command is next
- Resume: Continues from last command on next cycle
- Benefit: Allows other devices to be polled without waiting for completion
Use Case: Device with 4 poll commands taking 20ms total:
- Poll command 1 (5ms)
- Interrupted for other device
- Resume: Poll command 2 (5ms)
- Poll command 3 (5ms)
- Poll command 4 (5ms)
Raw poll data is decoded for publishing:
Device Type Decode Function:
typedef uint32_t (*DeviceTypeRecordDecodeFn)(
const uint8_t* pPollBuf, // Raw poll data
uint32_t pollBufLen, // Data length
void* pStructOut, // Output structure
uint32_t structOutSize, // Output size
uint16_t maxRecCount, // Max records to decode
RaftBusDeviceDecodeState& decodeState // Decode state
);Decode State:
- Handles timestamp wrap-around
- Tracks last timestamp for delta calculation
- Maintains device-specific decode context
Decoding Process:
- Extract timestamp from poll result
- Parse attribute data according to device schema
- Apply scaling factors (divisors)
- Convert to physical units
- Store in structured format
Decoded data is published via the StatePublisher system:
- devjson Topic: Human-readable JSON format
- devbin Topic: Compact binary format
See Device Data Publishing for details on published formats.
The scanner and poller coordinate to avoid conflicts:
- Scanner Check: Before scanning an address, check if it's being polled
- Skip if Polling: If address is being polled, scanner skips it
- No Interference: Polling operations (getting real data) are never interrupted by scanning
This ensures:
- No missed polls due to scanning
- No corrupt scan results due to polling
- Efficient bus utilization
The complete lifecycle of a device from detection to data delivery:
Bus Scanning
↓
Device Responds (Address detected online)
↓
Device Type Identification
├─ Get candidate types for address
├─ Try detection sequences
├─ Match against expected values
└─ Record device type index
↓
Device Initialization
├─ Parse init values
├─ Execute init commands
├─ Wait for bar access times
└─ Mark as initialized
↓
Device Polling Begins
├─ Parse poll configuration
├─ Schedule polls at specified interval
├─ Execute poll commands
├─ Store results with timestamps
└─ Decode and publish data
↓
Ongoing Polling
├─ Regular data collection
├─ Publish on change or time
├─ Monitor device health
└─ Handle offline transitions
| State | Description | Next States |
|---|---|---|
| Offline | Device not detected | Online (if responds to scan) |
| Online Unidentified | Responds but type unknown | Identified (if detection succeeds) |
| Identified | Type known | Initialized (after init commands) |
| Initialized | Ready for use | Polling (start data collection) |
| Polling | Actively collecting data | Offline (if stops responding) |
The bus speed can be changed on a per-device basis for polling. When a device has a busHz value set (for its type in DevMan typeSettings, or for the device via the REST API devconfig), the system will:
- Save the current bus frequency before the poll
- Switch the I2C bus to the specified frequency
- Execute the poll commands
- Restore the original bus frequency
This is useful when a device can operate at a higher (or lower) speed than other devices on the same bus. The speed change is done per-poll, so other devices continue to be polled at the default speed.
Slot Filtering: The busHzSlots parameter allows restricting the speed override to specific multiplexer slots. This is useful when:
- Different slots have different cable lengths or capacitance
- Some slots have level shifters with bandwidth limitations
- Only certain physical locations support higher speeds
When busHzSlots is not set (or is empty), the speed override applies to all slots.
Consider these factors when setting poll intervals:
Bus Bandwidth:
- Multiple devices share the I2C bus
- Fast polling of many devices can saturate bus
- Calculate total bandwidth:
Sum(DeviceSize / PollInterval)
Device Response Time:
- Some devices need time to prepare data
- Check device datasheet for conversion times
- Add margin for bus overhead
Application Requirements:
- Control loops need faster updates
- Monitoring can use slower updates
- Balance precision with efficiency
Example Calculation:
- 3 devices, 6 bytes each
- Bus speed: 400 kHz
- Per-device poll time: ~0.5ms
- Minimum interval: 2ms (to avoid 100% bus utilization)
- Recommended: 10ms (20% bus utilization)
- Priority Lists: Common devices detected faster
- Online Threshold: An address must respond on 2 consecutive scans before identification is attempted
- Parallel Scanning: Multiple addresses per scan cycle
- Smart Skip: Addresses that are online and being polled are not scanned; identified devices are not re-detected
- Burst Reading: Read multiple registers in one transaction
- Result Buffering: Store multiple samples before publishing
- Timestamp Compression: 2-byte timestamps reduce overhead
- Binary Format: Publish binary for bandwidth-constrained channels
- Device Records: Compiled in (base types; strings and field tables are const data, the record array is in RAM)
- Poll Buffers: Sized per device type
- Result Storage: Ring buffers for efficiency
- PSRAM Support: Large datasets moved to external RAM
Without rebuilding the firmware:
-
devman/identinfo?deviceid=<id>shows how a device is being identified: its state (pending,identified,deferred,noMatch,busError,initFailed), attempts, the time to the next attempt and the candidate types in the order they are tried -
devman/busstatusgives identification timing, poll timing per device (interval statistics and missed polls), poll integrity and scan statistics -
devman/typeslists the device type table with each type's source and policy
Enable debug flags:
// RaftI2C BusI2C/DeviceIdentMgr.cpp - logs each candidate tried, the bytes read and MATCH / NO MATCH
#define DEBUG_DEVICE_IDENT_MGR
// RaftCore DeviceTypes/DeviceTypeRecords.cpp
#define DEBUG_DEVICE_INFO_RECORDS
#define DEBUG_LOOKUP_DEVICE_TYPE_BY_INDEX
#define DEBUG_LOOKUP_DEVICE_TYPE_BY_NAMECommon issues:
- Device not at expected address
- Wrong WHO_AM_I value (check datasheet)
- Check value missing its
0x/0bprefix (logged asgetDetectionRecs ... invalid detection value; the record never matches) - Timing issues (split into a write-and-pause step followed by a read step — see Detection Timing)
- Multiple candidates at the same address (the first match wins — check record order and refine detection)
Enable debug flags:
#define DEBUG_DEVICE_INIT_REQSCommon issues:
- Wrong register addresses
- Insufficient bar access time
- Bit endianness (MSB vs LSB)
- Device requires specific init sequence
Enable debug flags:
#define DEBUG_POLL_REQUEST_REQS
#define DEBUG_DEVICE_INFO_PERFORMANCECommon issues:
- Poll interval too fast (bus saturation)
- Wrong read size
- Decode function issues
- Timestamp wrap-around handling
GET /devman/typeinfo?type=<typeName>
Returns complete device type information including poll schema.
Example:
GET /devman/typeinfo?type=LSM6DS3
Response:
{
"req": "/devman/typeinfo?type=LSM6DS3",
"devinfo": {
"name": "LSM6DS3",
"desc": "6-Axis IMU",
"manu": "ST",
"type": "LSM6DS3",
"resp": {
"b": 12,
"a": [
{"n": "gx", "t": "<h", "u": "dps", ...},
{"n": "gy", "t": "<h", "u": "dps", ...},
...
]
}
},
"dtIdx": 16,
"rslt": "ok"
}- I2C Bus - I2C bus configuration and bus worker task overview
- I2C Device Scanning - How devices are detected on the bus
- Device Data Publishing - How device data is published
- DeviceManager SysMod - DeviceManager features and API
- DeviceManager Settings - Configuration settings
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
Scaffolding & Building
- Raft CLI
- Web Apps (Device Types, Flasher)
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- WiFi Troubleshooting
- BLEManager
- WebServer
- MQTTManager
- SerialConsole
- CommandSerial
- CommandSocket
- CommandFile
- FileManager
- LogManager
- ESPOTAUpdate
- StatePublisher
- Remote Logging
- Data Source Registration
Comms & Protocols
- Stack Overview
- Comms Channels
- ProtocolExchange
- RICREST Protocol
- Real-Time Streams
- Adding REST Endpoints
- Built-in REST Endpoints
- File Download (OKTO)
- OTA Update Flow
Devices & Buses
- DeviceManager
- Device Manager REST API
- Device Factory & Classes
- Device Type Records
- Adding an I2C Device Type
- Device Data Publishing
- Data Logger
- I2C Bus
- I2C Device Scanning
- I2C ID & Polling
- MotorControl Overview
- MotorControl Config
- MotorControl Commands
- Camera
ROS 2
Helpers
Reference