From 0186467feb62fccfbab9c3bab3f9b11d5655dfc4 Mon Sep 17 00:00:00 2001 From: lkasso Date: Sat, 13 Jun 2026 16:54:53 -0700 Subject: [PATCH] Spec consistency follow-ups: discovery set, read routing, temp scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up review pass closing the remaining cross-references that still disagreed after the contradiction batch: - Module discovery: renamed to the "SDK discovery set" and stated it is not "all active modules" — it legacy-probes the deprecated iBeacon (0x07) and Humidity (0x16) opcodes while omitting NeoPixel/ANCS/GSR/ Color/Proximity; added a matching note under the discovery-order list. - Board-init step now distinguishes present (full info) from absent (bare [opcode, 0x80]) responses, consistent with the discovery rule. - Data-processor src_reg: "(not OR'd with 0x80)" was wrong for read-based sources; now spells out streaming = plain register, read-based = loud-read form (0x81/0x86/0x87). - Bit-6 packet note: silent reads feed LOGGER TRIGGERS only; data processors are fed by loud reads (was "loggers and data processors"). - Temperature: removed the unjustified "data format may be backend-driver specific" hedge — all channels use channel + int16/8 (0.125 C); corrected the introductory table's "2 byte Address" to one byte. - Macro ADD_PARTIAL/ADD_COMMAND split: clarified the ambiguous cmd_byte2..cmd_byteN-2 notation to "all remaining bytes." mkdocs build --strict passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/api-specification.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/api-specification.md b/docs/api-specification.md index 12adc49..039a772 100644 --- a/docs/api-specification.md +++ b/docs/api-specification.md @@ -125,7 +125,7 @@ When a host connects to a MetaWear for the first time, it should discover which The discovery process is: 1. Subscribe to the **Notification Characteristic** (`326A9006`) -2. For each known module opcode (`0x01` through `0x19`, and `0xFE`), send a read command: \[`opcode, 0x80`\] (reading register `0x00`) +2. For each opcode in the **SDK discovery set** — the `MODULE_DISCOVERY_CMDS` list under *Module discovery order* below — send a read command: \[`opcode, 0x80`\] (reading register `0x00`). Probe only this set, not every numeric value in the `0x01`–`0x19` range. The set is *not* simply "all active modules": it still includes the **deprecated** iBeacon (`0x07`) and Humidity (`0x16`) opcodes, which the SDK legacy-probes for backward compatibility, while the other deprecated opcodes — NeoPixel (`0x06`), ANCS (`0x0E`), GSR (`0x10`), Color (`0x17`), Proximity (`0x18`) — are omitted entirely. 3. The MetaWear responds with: \[`opcode, 0x80, impl_id, revision, ...`\] 4. **Every opcode responds.** A **present** module returns `[opcode, 0x80, impl_id, revision, ...]`; an **absent** module returns only the 2-byte header `[opcode, 0x80]` with no implementation or revision byte. Detect absence by the response carrying no implementation byte (length 2) — not by a missing reply, which would force a per-opcode timeout. @@ -140,7 +140,7 @@ The **Implementation ID** identifies which hardware variant is present (e.g., BM ``` [module_id, 0x80] <- READ_REGISTER(0x00) = info register ``` - The board responds with `[module_id, 0x80, implementation_byte, revision_byte, ...]`. + A **present** module responds with `[module_id, 0x80, implementation_byte, revision_byte, ...]`; an **absent** module responds with just `[module_id, 0x80]` (no implementation/revision byte — see step 4 of *Module Discovery* above). 4. After all modules respond, call `init_*_module()` for each present module. 5. Read logging time signal (module 0x0B, register 0x84) and set reference epoch. @@ -151,6 +151,7 @@ DATA_PROCESSOR, EVENT, LOGGING, TIMER, I2C, MACRO, SETTINGS, BAROMETER, GYRO, AMBIENT_LIGHT, MAGNETOMETER, HUMIDITY, SENSOR_FUSION, DEBUG ``` +`IBEACON` (`0x07`) and `HUMIDITY` (`0x16`) appear in this set even though the [Modules](#modules) table marks them **Deprecated** — the SDK still probes them for backward compatibility, and a board that doesn't implement one simply returns the empty 2-byte Module Info. Model numbers (from `module_number` string in Device Info "model number" characteristic): ``` @@ -277,7 +278,7 @@ To understand the **Addresses** and **Values**, users must refer to the tables i | Setting Address | Mode | Wlen | Rlen | Value | | :-------------------------------------- | :------------------------------------------------------------------------------------------------ | :------------------------------------- | :--------------------------------------------------- | :--------------------------------------------------- | -| The 2 byte **Address** for the register | The modes supported:
R = Readable register
W = Writable register
N = Notifiable register | Length of **Value** bytes when writing | Length of **Value** bytes when reading and notifying | Byte or bit level detail of what the **Value** means | +| The 1 byte **Address** for the register (Byte 1 of the command) | The modes supported:
R = Readable register
W = Writable register
N = Notifiable register | Length of **Value** bytes when writing | Length of **Value** bytes when reading and notifying | Byte or bit level detail of what the **Value** means | Let’s revisit the data packet sent to the **Command Characteristic** in the last example `[0x04, 0x81, 0x00`\]: @@ -354,7 +355,7 @@ The register byte's **two high bits are flags**, which is why `CLEAR_READ` masks with `0x3f` to recover the bare 6-bit address: * **Bit 7 (`0x80`)** — read request/response. OR it into the address for a one-time read. -* **Bit 6 (`0x40`)** — set *together with* bit 7 on reads that carry a data-id byte in the response and/or route the read **silently** to on-board consumers instead of the host: I2C/SPI reads (`0xC1` / `0xC2`) and the silent sensor reads that feed loggers and data processors (see [Read-signal routing](#read-signal-routing-hardware-observed-firmware-172)). +* **Bit 6 (`0x40`)** — set *together with* bit 7 on reads that carry a data-id byte in the response and/or route the read **silently** to on-board consumers instead of the host: I2C/SPI reads (`0xC1` / `0xC2`) and the silent sensor reads that feed **logger triggers** (see [Read-signal routing](#read-signal-routing-hardware-observed-firmware-172)). Note that *data processors* are fed by **loud** reads, not silent ones — only loggers consume the silent form. Macros are the only commands written **with** response; everything else uses write-without-response. @@ -804,7 +805,7 @@ Each channel is addressed by its **channel index** (its position in the Module I | Setting | Address | Mode | Wlen | Rlen | Value | | :---------- | :------ | :--- | :--- | :--- | :--------------------------------------------------------------------------------------------------------------------------------------------- | | Module Info | `0x00` | R | | 2+ | Byte 0: Module Implementation ID: 1 (uint8\_t) Byte 1: Module Revision: 0 (uint8\_t) Byte 2+: Array of Driver (source) IDs, one per channel | -| Temperature | `0x01` | RN | 0 | 3 | Byte 0: Channel Index (uint8\_t) Byte 1-2: int16\_t in units of 0.125°C. Data format may be backend-driver specific. | +| Temperature | `0x01` | RN | 0 | 3 | Byte 0: Channel Index (uint8\_t) Byte 1-2: int16\_t in units of 0.125°C (raw ÷ 8). All channels use this same wire format and scale regardless of the underlying driver; only the physical resolution differs (e.g. the nRF on-die sensor resolves ~0.25°C but is still reported in 0.125°C units). | | Mode | `0x02` | RW | 1+ | 1+ | Byte 0: Driver (channel) Index (uint8\_t) Byte 1+: Mode settings, driver specific | For example, to read the temperature from channel 0, the command is \[`0x04, 0x81, 0x00`\]: @@ -1028,7 +1029,7 @@ each ADD response assigns an ID that can be used as the source for subsequent pr | 0 | module | 0x09 | | 1 | register | 0x02 (ADD) | | 2 | src_module | Source module ID | -| 3 | src_reg | Source register ID (not OR'd with 0x80) | +| 3 | src_reg | Source register ID. **Streaming** signals use the plain register (no read bit); **read-based** signals (temperature, GPIO analog) use the **loud-read** form, register OR'd with `0x80` (e.g. temperature `0x81`, GPIO ADC `0x87`, GPIO absolute `0x86`). See *Streaming vs read-based sources* below. | | 4 | src_data_id | Source data ID, or 0xFF for "any" | | 5 | src_config | Encodes sample length and offset (see below) | | 6 | proc_type | Processor type ID | @@ -1708,10 +1709,10 @@ For commands <= 13 bytes (MW_CMD_MAX_LENGTH - 2): [0x0F, 0x03, ...command_bytes...] ``` -For commands >= 14 bytes: +For commands >= 14 bytes, split into the first 2 bytes and everything after: ``` -[0x0F, 0x09, cmd_byte0, cmd_byte1] <- ADD_PARTIAL (first 2 bytes) -[0x0F, 0x03, cmd_byte2..cmd_byteN-2] <- ADD_COMMAND (remaining) +[0x0F, 0x09, cmd_byte0, cmd_byte1] <- ADD_PARTIAL (first 2 bytes) +[0x0F, 0x03, cmd_byte2, cmd_byte3, ... last byte] <- ADD_COMMAND (all remaining bytes) ``` **End macro:**