Skip to content

I2CDeviceIdentificationAndPolling

Rob Dobson edited this page Oct 18, 2026 · 18 revisions

I2C Device Identification and Polling

Overview

After I2C device scanning detects that a device is present at an address (device responds to I2C transactions), the system performs three sequential operations:

  1. Device Type Identification: Determine what specific type of device is present
  2. Device Initialization: Configure the device for operation
  3. Device Polling: Regularly retrieve data from the device

This page covers these three distinct phases of device lifecycle management.

Device Type Records

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 and Extended Records

Base Device Type Records:

  • Compiled into the firmware from the device definition files (DeviceTypeRecords.json etc.). The records, strings and field tables are const data 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 typeTable settings (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

Adding Extended 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.

Device Type Detection

When scanning detects a device online, the DeviceIdentMgr attempts to identify its type.

Detection Process

  1. Get Candidate Types: Query DeviceTypeRecords for 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
  2. Iterate Candidates: Try each candidate type's detection sequence in that order. The first match wins — later candidates are not tried
  3. Execute Detection Commands: Send write/read sequences and check responses
  4. Match Results: Compare read data against expected values using masks
  5. Record Type: Store the identified device type index if a match is found
  6. 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 Record Format

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.

Detection Example: LSM6DS3 IMU

detectionValues: "0x0f=0x69"

This means:

  1. Write 0x0f (WHO_AM_I register address)
  2. Read 1 byte
  3. Check if result is 0x69 (LSM6DS3)
  4. 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.

Binary Mask Detection

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

Multiple Detection Sequences

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

Detection Timing

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.

When Detection Runs

  • 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 NotMine after 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 Deferred every 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 with DeviceTypeSources.json or read from a records folder). So a record whose detection compares nothing (e.g. one with no detectionValues) 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) in BusAddrStatus::identEvent: Identified, TypeChanged (re-identified as another type while online) or Lost (no longer identified, or gone offline), with prevDeviceTypeIndex. isNewlyIdentified is also set for Identified and TypeChanged. A listener that caches anything derived from the type (a decoder, a topic) must rebuild it on TypeChanged and drop it on Lost

  • Settings made with devman/devconfig are 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. With save=1 they 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

Device Initialization

After successful identification, devices are initialized using the initValues from their device type record.

Initialization Process

  1. Parse Init Values: Extract write/read pairs from initValues string
  2. Execute Init Commands: Send each command in sequence
  3. Wait for Completion: Allow bar access time between commands if specified
  4. Mark Initialized: Device is ready for polling

Init Values Format

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 bare pNN with no = is treated as bytes to write

Initialization Examples

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 Time

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

Init Failure Handling

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.

Extension Hooks

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.

Custom device identification

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 returns Handled in that window leaves the device permanently identified against the wrong state.

Running work on the bus task

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 is elemStateChange() 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.

Stuck-bus helpers

virtual bool isBusStuck() const;    // default false
virtual bool clearBusStuck();       // clock the bus until a device releases

A 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.

Device Polling

Polling retrieves data from identified, initialized devices at regular intervals.

Polling vs Scanning

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

Polling Configuration

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.

Poll Command Format

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")

Polling Examples

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 busHzSlots for 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 Result Storage

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 s poll 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

Polling Intervals

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

Setting polling configuration from API

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 which busHz applies (omit = keep current mask; empty or 0 = 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).

Partial Polling

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)

Poll Data Decoding

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:

  1. Extract timestamp from poll result
  2. Parse attribute data according to device schema
  3. Apply scaling factors (divisors)
  4. Convert to physical units
  5. Store in structured format

Poll Result Publishing

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.

Polling Integration with Scanning

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

I2C Device Lifecycle

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 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)

Dynamic Bus Speed

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:

  1. Save the current bus frequency before the poll
  2. Switch the I2C bus to the specified frequency
  3. Execute the poll commands
  4. 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.

Polling Rate Optimization

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)

Performance Considerations

Detection Performance

  • 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

Polling Performance

  • 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

Memory Management

  • 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

Debugging

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/busstatus gives identification timing, poll timing per device (interval statistics and missed polls), poll integrity and scan statistics
  • devman/types lists the device type table with each type's source and policy

Detection Issues

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_NAME

Common issues:

  • Device not at expected address
  • Wrong WHO_AM_I value (check datasheet)
  • Check value missing its 0x / 0b prefix (logged as getDetectionRecs ... 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)

Initialization Issues

Enable debug flags:

#define DEBUG_DEVICE_INIT_REQS

Common issues:

  • Wrong register addresses
  • Insufficient bar access time
  • Bit endianness (MSB vs LSB)
  • Device requires specific init sequence

Polling Issues

Enable debug flags:

#define DEBUG_POLL_REQUEST_REQS
#define DEBUG_DEVICE_INFO_PERFORMANCE

Common issues:

  • Poll interval too fast (bus saturation)
  • Wrong read size
  • Decode function issues
  • Timestamp wrap-around handling

API Access

Get Device Type Info

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"
}

Related Documentation

Clone this wiki locally