## [2.0.0] - 2026-09-03 #31

Merged
faicel merged 30 commits from fix/audit-security-lis2dh12 into dev 2026-09-03 20:00:16 +00:00
Owner

[2.0.0] - 2026-09-03

  • CI: record the repository owner's explicit decision to accept, for now, the supply-chain risk of the five reusable workflows (check-on-feature-branch, create-tag-on-dev, create-tag-on-main, publish-on-tag, validate-branch-flow) tracking the mutable faicel/central_ci/...@main ref — push-triggered jobs pass RUNNER_TOKEN to whatever @main contains at run time. The decision and its residual exposure are documented in .forgejo/README.md; the workflows are unchanged and the decision is to revisit pinning them to an immutable commit SHA when practical.

    • Breaking/fix: set_interrupt_threshold and set_interrupt_duration now reject values greater than 127 with Lis2dh12Error::InvalidConfiguration instead of silently truncating them to the 7-bit register width (INTx_THS.THS[6:0], INTx_DURATION.D[6:0]) — a caller passing e.g. 255 previously configured 127 without any error. configure_motion_detection and configure_free_fall validate the configured threshold and duration up front, before the first I2C transaction, so an invalid configuration can no longer leave partially-modified hardware behind (previously the error surfaced only after the power-mode/data-rate/range/filter/reference/interrupt writes on the motion path, and after the INT_CFG/THS writes on the free-fall path). In-range values (0–127) are unaffected.

    • Fix: the self-test could panic on legal bus data and validated responses against wrong limits. Per-axis deltas are now computed in i32 with the absolute value taken before any conversion (the datasheet defines the self-test change as the absolute output difference), so the full legal raw span (baseline 0x8000 vs tested 0x7FFF, a 65535-count difference) no longer overflows i16 — previously a debug-build panic (an abort on no_std targets) exactly when diagnosing broken hardware. Deltas are now reported in digits of the configured power mode (raw count difference ÷ 16/64/256 for high resolution/normal/low power, rounded to nearest; the maximum possible value is 4096 digits, so the i16 fields are unchanged) and SelfTestLimits::for_configuration converts the datasheet's physical self-test window (68–1440 mg, derived from the only published limits: 17–360 LSB at ±2g normal) into the configured mode's digit counts with ceil/floor instead of an inverted sensitivity ratio — the old limits were wrong for every configuration except ±2g normal (e.g. ±2g high resolution returned 4–90 instead of 68–1440 digits). Consumers asserting on SelfTestResult.delta_* values or custom SelfTestLimits must adapt to the configured-mode-digit unit.

    • Breaking/fix: acceleration readings were 16× (high resolution), 64× (normal) or 256× (low power) too large — the conversion applied the per-power-mode mg/LSB sensitivity directly to the left-justified raw 16-bit output without undoing the left-justification (datasheet Table 4). read_acceleration, try_read_acceleration and read_fifo now convert with the mode-independent g-per-raw16-LSB factor (g = raw16 × 6.25e-5 × range multiplier: 1/2/4/12 for ±2g/±4g/±8g/±16g), identical across the single-sample and FIFO paths. On ±16g extreme codes legitimately report up to ≈24.5 g (the conversion law is not clamped to nominal full scale). Consumers must recalibrate any g-based thresholds calibrated against the old (wrong) values.

    • Performance: data-ready polling is now paced by the output data rate — between status polls the driver sleeps one quarter of the two-sample-period window (1 ms floor) instead of a fixed 1 ms, so a timed-out wait at 100 Hz costs 5 status polls with 4 × 5 ms sleeps instead of 20 polls, and at 1 Hz 5 polls with 4 × 500 ms sleeps instead of ~2000. Data arriving exactly at the timeout boundary is now accepted (the timeout is raised only after a poll at the boundary itself); as the honest cost of that uniform boundary rule, the worst case at ODR ≥ 400 Hz becomes one poll and one 1 ms delay longer than before (6 polls / 5 delays at the 5 ms timeout floor, ~95 µs of extra bus time per timed-out read). Applies to both the acceleration and temperature wait loops.

    • Performance: the output data rate tables are merged into a single milli-Hz source of truth — the Hz accessor (sample_rate, timeouts) is now derived from the milli-Hz table instead of a parallel hand-maintained match, with identical outputs for every data rate and power mode (PowerDown → 0.0, 1.620 kHz low power → 1620.0, 5.376 kHz → 5376.0 low power / 1344.0 otherwise); a new unit test pins all data-rate × power-mode combinations exactly.

  • Performance: driver initialization now takes 19 I2C transactions instead of 42 (active-low variant: 20 instead of 44). After the BOOT reboot, the writable registers are zeroed with multi-byte write frames per the datasheet (Table 18 frame: ST, SAD+W, SUB, DATA…) over the contiguous register runs instead of 21 single-register writes, and CTRL_REG1/CTRL_REG4 (plus the CTRL_REG6 polarity bit when configured) are written once with their computed final values instead of a read-modify-write chain over registers the reset just cleared — the end state is unchanged (CTRL_REG1 = 0x57, CTRL_REG4 = 0x88); public setters keep read-modify-write semantics for runtime use. Note: the datasheet explicitly guarantees subaddress auto-increment for multi-byte reads only (§6.1), so the multi-byte write form was verified on real hardware: a board probe writing distinct per-position values over both burst ranges and reading back all 13 positions individually passed with every position matching — the burst-write initialization is confirmed as the final shipped form (no fallback to single-register writes applies).

  • CI: the local scripts/check-version-sync.sh tolerates a top-level ## [Unreleased] CHANGELOG section (reports sync OK without a version comparison) so audit changes can accumulate before the release is cut; the strict version-to-version comparison is unchanged for release flows.

  • Performance: clear_interrupt_flags now reads each latched source register (INT1_SRC, INT2_SRC, CLICK_SRC) once — 3 I2C transactions instead of 9: reading a source register already clears its own flags, so repeat passes only cleared re-triggers the caller cannot rely on; configure_motion_detection drops accordingly from 30 to 24 transactions.

  • CI: scripts/test.sh now runs the full local quality gate suite — cargo fmt --all -- --check, cargo clippy --locked --all-targets -- -D warnings, RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --locked, and cargo test --locked -- --nocapture — so formatting, lint, doc and test gates are enforced locally under the same reproducible --locked contract as CI; the README "Tests" section documents the new sequence.

  • Tests: add mock-I2C coverage for the remaining public API paths — sanity_check (success and invalid device id), device_id, suspend_motion_interrupt/resume_motion_interrupt on both interrupt pins, disable_all_axes, and is_data_ready — each asserting the exact bus transaction sequence and returned values.

  • Documentation: correct the FifoStatus::sample_count field documentation — the 5-bit counter spans 0-31; a full buffer of 32 unread samples is signaled by the overrun flag (datasheet FIFO_SRC_REG description).

  • Declare package metadata: rust-version = "1.73" (the floor actually imposed by u32::div_ceil, previously undeclared), repository, and readme in Cargo.toml.

  • Breaking change: remove the never-constructed Lis2dh12Error::DeviceNotResponding and Lis2dh12Error::SelfTestFailed variants (no library code path could ever produce them; a failed self-test is already reported through SelfTestResult.passed, and data-ready timeouts surface as DataReadyTimeout). Users pattern-matching on these variants must drop the dead arms.

  • Breaking change: the public API surface is consolidated to one canonical path per item — the driver, registers, types, and error modules are now private, so every public item is reachable only through the crate-root re-exports (lis2dh12::Range, lis2dh12::SlaveAddr, lis2dh12::Lis2dh12Error, …) plus lis2dh12::i2c::Lis2dh12 for the driver. The unused F32x3, Accelerometer, RawAccelerometer, Error, and ErrorKind re-exports from the accelerometer crate are removed (I16x3 stays: it appears in the read_fifo_raw signature).

  • Documentation: all 21 doc examples now compile as no_run doctests instead of being fenced ignore (several imported a path that did not exist); each example is type-checked against a mock I2C bus built from the embedded-hal-mock dev-dependency and stays hardware-neutral.

  • Performance: acceleration reads (read_acceleration, try_read_acceleration) no longer re-read CTRL_REG1/CTRL_REG4 on every sample — the driver's cached configuration (range, power mode, data rate, maintained by every setter) is authoritative, halving the per-read I2C traffic from 4 to 2 transactions (STATUS poll + OUT_X..Z burst).

  • Breaking change: rename the DataRate variant Hz_1600_LP to Hz_1620_LP so the variant name matches the actual low-power output data rate.

  • Fix: the low-power ODR code 0b1000 now reports its datasheet value of 1.620 kHz (sample_rate() returns 1620.0 Hz) instead of 1600 Hz; data-ready timeout behavior is unchanged (the 5 ms floor still dominates).

  • Breaking change: remove the unreachable Accelerometer/RawAccelerometer trait implementations (they existed only on the crate-internal attached driver, so no trait impl was reachable from outside the crate) and their dead conversion helpers; the crate documentation no longer claims to implement the accelerometer crate traits.

  • Breaking change: Lis2dh12::sample_rate now returns Result<f32, Lis2dh12Error<E>> (the same error type as every other method) instead of accelerometer::Error<E>, and reads the device configuration before computing the rate.

  • Add co-located register bit-layout tests (src/registers/tests/) pinning every register constant (addresses, masks, bit positions, free-fall preset) to its documented datasheet value.

  • Move the unit tests into per-module tests/ directories co-located with their sources (src/driver/tests/, src/types/tests/, src/error/tests/); the central src/tests/ directory is gone.

  • Restore the clippy-clean baseline in the shared test helper (use a vec! literal instead of Vec::new() + push, remove a no-op 0x00 | operation).

  • Fix README.md.

  • Update .gitignore

## [2.0.0] - 2026-09-03 - CI: record the repository owner's explicit decision to accept, for now, the supply-chain risk of the five reusable workflows (`check-on-feature-branch`, `create-tag-on-dev`, `create-tag-on-main`, `publish-on-tag`, `validate-branch-flow`) tracking the mutable `faicel/central_ci/...@main` ref — push-triggered jobs pass `RUNNER_TOKEN` to whatever `@main` contains at run time. The decision and its residual exposure are documented in `.forgejo/README.md`; the workflows are unchanged and the decision is to revisit pinning them to an immutable commit SHA when practical. - **Breaking/fix**: `set_interrupt_threshold` and `set_interrupt_duration` now reject values greater than 127 with `Lis2dh12Error::InvalidConfiguration` instead of silently truncating them to the 7-bit register width (`INTx_THS.THS[6:0]`, `INTx_DURATION.D[6:0]`) — a caller passing e.g. 255 previously configured 127 without any error. `configure_motion_detection` and `configure_free_fall` validate the configured threshold and duration up front, before the first I2C transaction, so an invalid configuration can no longer leave partially-modified hardware behind (previously the error surfaced only after the power-mode/data-rate/range/filter/reference/interrupt writes on the motion path, and after the `INT_CFG`/`THS` writes on the free-fall path). In-range values (0–127) are unaffected. - **Fix**: the self-test could panic on legal bus data and validated responses against wrong limits. Per-axis deltas are now computed in `i32` with the absolute value taken before any conversion (the datasheet defines the self-test change as the absolute output difference), so the full legal raw span (baseline 0x8000 vs tested 0x7FFF, a 65535-count difference) no longer overflows `i16` — previously a debug-build panic (an abort on `no_std` targets) exactly when diagnosing broken hardware. Deltas are now reported in digits of the configured power mode (raw count difference ÷ 16/64/256 for high resolution/normal/low power, rounded to nearest; the maximum possible value is 4096 digits, so the `i16` fields are unchanged) and `SelfTestLimits::for_configuration` converts the datasheet's physical self-test window (68–1440 mg, derived from the only published limits: 17–360 LSB at ±2g normal) into the configured mode's digit counts with ceil/floor instead of an inverted sensitivity ratio — the old limits were wrong for every configuration except ±2g normal (e.g. ±2g high resolution returned 4–90 instead of 68–1440 digits). Consumers asserting on `SelfTestResult.delta_*` values or custom `SelfTestLimits` must adapt to the configured-mode-digit unit. - **Breaking/fix**: acceleration readings were 16× (high resolution), 64× (normal) or 256× (low power) too large — the conversion applied the per-power-mode mg/LSB sensitivity directly to the left-justified raw 16-bit output without undoing the left-justification (datasheet Table 4). `read_acceleration`, `try_read_acceleration` and `read_fifo` now convert with the mode-independent g-per-raw16-LSB factor (g = raw16 × 6.25e-5 × range multiplier: 1/2/4/12 for ±2g/±4g/±8g/±16g), identical across the single-sample and FIFO paths. On ±16g extreme codes legitimately report up to ≈24.5 g (the conversion law is not clamped to nominal full scale). Consumers must recalibrate any g-based thresholds calibrated against the old (wrong) values. - Performance: data-ready polling is now paced by the output data rate — between status polls the driver sleeps one quarter of the two-sample-period window (1 ms floor) instead of a fixed 1 ms, so a timed-out wait at 100 Hz costs 5 status polls with 4 × 5 ms sleeps instead of 20 polls, and at 1 Hz 5 polls with 4 × 500 ms sleeps instead of ~2000. Data arriving exactly at the timeout boundary is now accepted (the timeout is raised only after a poll at the boundary itself); as the honest cost of that uniform boundary rule, the worst case at ODR ≥ 400 Hz becomes one poll and one 1 ms delay longer than before (6 polls / 5 delays at the 5 ms timeout floor, ~95 µs of extra bus time per timed-out read). Applies to both the acceleration and temperature wait loops. - Performance: the output data rate tables are merged into a single milli-Hz source of truth — the Hz accessor (`sample_rate`, timeouts) is now derived from the milli-Hz table instead of a parallel hand-maintained match, with identical outputs for every data rate and power mode (PowerDown → 0.0, 1.620 kHz low power → 1620.0, 5.376 kHz → 5376.0 low power / 1344.0 otherwise); a new unit test pins all data-rate × power-mode combinations exactly. - Performance: driver initialization now takes 19 I2C transactions instead of 42 (active-low variant: 20 instead of 44). After the BOOT reboot, the writable registers are zeroed with multi-byte write frames per the datasheet (Table 18 frame: ST, SAD+W, SUB, DATA…) over the contiguous register runs instead of 21 single-register writes, and CTRL_REG1/CTRL_REG4 (plus the CTRL_REG6 polarity bit when configured) are written once with their computed final values instead of a read-modify-write chain over registers the reset just cleared — the end state is unchanged (CTRL_REG1 = 0x57, CTRL_REG4 = 0x88); public setters keep read-modify-write semantics for runtime use. Note: the datasheet explicitly guarantees subaddress auto-increment for multi-byte reads only (§6.1), so the multi-byte write form was verified on real hardware: a board probe writing distinct per-position values over both burst ranges and reading back all 13 positions individually passed with every position matching — the burst-write initialization is confirmed as the final shipped form (no fallback to single-register writes applies). - CI: the local `scripts/check-version-sync.sh` tolerates a top-level `## [Unreleased]` CHANGELOG section (reports sync OK without a version comparison) so audit changes can accumulate before the release is cut; the strict version-to-version comparison is unchanged for release flows. - Performance: `clear_interrupt_flags` now reads each latched source register (INT1_SRC, INT2_SRC, CLICK_SRC) once — 3 I2C transactions instead of 9: reading a source register already clears its own flags, so repeat passes only cleared re-triggers the caller cannot rely on; `configure_motion_detection` drops accordingly from 30 to 24 transactions. - CI: `scripts/test.sh` now runs the full local quality gate suite — `cargo fmt --all -- --check`, `cargo clippy --locked --all-targets -- -D warnings`, `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --locked`, and `cargo test --locked -- --nocapture` — so formatting, lint, doc and test gates are enforced locally under the same reproducible `--locked` contract as CI; the README "Tests" section documents the new sequence. - Tests: add mock-I2C coverage for the remaining public API paths — `sanity_check` (success and invalid device id), `device_id`, `suspend_motion_interrupt`/`resume_motion_interrupt` on both interrupt pins, `disable_all_axes`, and `is_data_ready` — each asserting the exact bus transaction sequence and returned values. - Documentation: correct the `FifoStatus::sample_count` field documentation — the 5-bit counter spans 0-31; a full buffer of 32 unread samples is signaled by the `overrun` flag (datasheet FIFO_SRC_REG description). - Declare package metadata: `rust-version = "1.73"` (the floor actually imposed by `u32::div_ceil`, previously undeclared), `repository`, and `readme` in `Cargo.toml`. - **Breaking change**: remove the never-constructed `Lis2dh12Error::DeviceNotResponding` and `Lis2dh12Error::SelfTestFailed` variants (no library code path could ever produce them; a failed self-test is already reported through `SelfTestResult.passed`, and data-ready timeouts surface as `DataReadyTimeout`). Users pattern-matching on these variants must drop the dead arms. - **Breaking change**: the public API surface is consolidated to one canonical path per item — the `driver`, `registers`, `types`, and `error` modules are now private, so every public item is reachable only through the crate-root re-exports (`lis2dh12::Range`, `lis2dh12::SlaveAddr`, `lis2dh12::Lis2dh12Error`, …) plus `lis2dh12::i2c::Lis2dh12` for the driver. The unused `F32x3`, `Accelerometer`, `RawAccelerometer`, `Error`, and `ErrorKind` re-exports from the `accelerometer` crate are removed (`I16x3` stays: it appears in the `read_fifo_raw` signature). - Documentation: all 21 doc examples now compile as `no_run` doctests instead of being fenced `ignore` (several imported a path that did not exist); each example is type-checked against a mock I2C bus built from the `embedded-hal-mock` dev-dependency and stays hardware-neutral. - Performance: acceleration reads (`read_acceleration`, `try_read_acceleration`) no longer re-read CTRL_REG1/CTRL_REG4 on every sample — the driver's cached configuration (range, power mode, data rate, maintained by every setter) is authoritative, halving the per-read I2C traffic from 4 to 2 transactions (STATUS poll + OUT_X..Z burst). - **Breaking change**: rename the `DataRate` variant `Hz_1600_LP` to `Hz_1620_LP` so the variant name matches the actual low-power output data rate. - Fix: the low-power ODR code `0b1000` now reports its datasheet value of 1.620 kHz (`sample_rate()` returns 1620.0 Hz) instead of 1600 Hz; data-ready timeout behavior is unchanged (the 5 ms floor still dominates). - **Breaking change**: remove the unreachable `Accelerometer`/`RawAccelerometer` trait implementations (they existed only on the crate-internal attached driver, so no trait impl was reachable from outside the crate) and their dead conversion helpers; the crate documentation no longer claims to implement the `accelerometer` crate traits. - **Breaking change**: `Lis2dh12::sample_rate` now returns `Result<f32, Lis2dh12Error<E>>` (the same error type as every other method) instead of `accelerometer::Error<E>`, and reads the device configuration before computing the rate. - Add co-located register bit-layout tests (`src/registers/tests/`) pinning every register constant (addresses, masks, bit positions, free-fall preset) to its documented datasheet value. - Move the unit tests into per-module `tests/` directories co-located with their sources (`src/driver/tests/`, `src/types/tests/`, `src/error/tests/`); the central `src/tests/` directory is gone. - Restore the clippy-clean baseline in the shared test helper (use a `vec!` literal instead of `Vec::new()` + push, remove a no-op `0x00 |` operation). - Fix README.md. - Update .gitignore
ODR code 0b1000 is 1.620 kHz in low-power mode per the datasheet
(en.DM00091513), not 1.6 kHz: report 1620.0 Hz from sample_rate and
1_620_000 milli-Hz internally. Rename DataRate::Hz_1600_LP to
Hz_1620_LP (breaking). Data-ready timeout unchanged: two periods at
1.62 kHz round below the 5 ms floor, which still dominates. Add tests
for the 1620 Hz sample rate and the unchanged timeout floor.
read_acceleration and try_read_acceleration no longer re-read CTRL_REG1
and CTRL_REG4 on every sample: the cached range/power mode/data rate,
maintained by every setter and the with_attached write-back, is
authoritative. Each read now costs 2 I2C transactions (STATUS poll +
OUT_X..Z burst) instead of 4. sync_measurement_config stays for the
sample_rate path. Tests encoding the 4-transaction sequence are updated
to the 2-transaction sequence; the resync tests become cache-coherence
tests (set_range(G4) then read scales from the cached G4 with zero extra
config transactions).
Add rust-version = 1.73 (floor imposed by u32::div_ceil in the data-ready
timeout helper), repository and readme fields. Version stays 1.0.14 until
the 2.0.0 release.
The local entrypoint now executes, in order: version sync, CI helper
unit tests, cargo fmt --all -- --check, cargo clippy --locked
--all-targets -- -D warnings, RUSTDOCFLAGS="-D warnings" cargo doc
--no-deps --locked, and cargo test --locked -- --nocapture. Every
cargo invocation is locked so a run is reproducible from Cargo.lock
alone. The README Tests section documents the exact sequence.
- Compute self-test deltas in i32 with the absolute value taken before
  any conversion, so the full raw i16 span cannot overflow (removes the
  debug-build panic / no_std abort path when diagnosing broken hardware)
- Report deltas in digits of the configured power mode (raw difference
  divided by 16/64/256 for high resolution/normal/low power, rounded to
  nearest; max 4096 digits, i16 fields unchanged)
- Convert SelfTestLimits from the datasheet's physical 68-1440 mg
  self-test window (only the +-2g normal 17-360 LSB limits are
  published) with integer ceil/floor instead of the inverted sensitivity
  ratio; document datasheet provenance
- Add extreme no-panic, delta-symmetry and 12-combination limits-table
  tests; update pass/fail mocks to consistent units
set_interrupt_threshold and set_interrupt_duration now return
InvalidConfiguration for values greater than 127 instead of silently
truncating to the 7-bit register width (the 0x7F mask is kept before the
write as defense-in-depth). configure_motion_detection validates the
configured threshold and duration up front and configure_free_fall
extends its early check to the duration, so an invalid configuration
performs zero I2C transactions and can never leave partially-modified
hardware behind. In-range values are unaffected.
fix workflow
All checks were successful
Run checks on feature branches / rust-crate-checks (push) Successful in 22s
Run checks on feature branches / checks (push) Successful in 0s
Validate branch flow / validate-flow (pull_request_target) Successful in 1s
Validate branch flow / validate (pull_request_target) Successful in 0s
d9492cbc56
Merge branch 'dev' into fix/audit-security-lis2dh12
All checks were successful
Run checks on feature branches / rust-crate-checks (push) Successful in 17s
Run checks on feature branches / checks (push) Successful in 0s
Validate branch flow / validate-flow (pull_request_target) Successful in 1s
Validate branch flow / validate (pull_request_target) Successful in 0s
569fc4287c
faicel deleted branch fix/audit-security-lis2dh12 2026-09-03 20:00:17 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
homeiot/lis2dh12!31
No description provided.