Skip to content

Commit 68a9640

Browse files
authored
refactor!: split into Tmp108 (blocking) and AsyncTmp108 (async) (0.7.0) (#48)
## Summary Architectural refactor — the single `maybe-async-cfg`-generated `Tmp108<I2C>` becomes two named driver types: **`Tmp108`** (blocking, `embedded-hal`) and **`AsyncTmp108`** (async, `embedded-hal-async`). Both are available simultaneously when both relevant features are enabled. Shared register-codec logic lives in a private `mod ops` of pure functions. The `maybe-async-cfg` dependency is dropped entirely. This is the architectural sequel to #47 — recommended by the `@architect` review at the same time as the reliability fixes, and deliberately deferred to a follow-up so each PR could be reviewed and hardware-verified independently. Design spec at [`docs/superpowers/specs/2026-06-04-tmp108-type-split-design.md`](https://github.com/felipebalbi/tmp108/blob/named-types/docs/superpowers/specs/2026-06-04-tmp108-type-split-design.md). ## What changes (per commit) | Commit | Scope | Breaking? | |---|---|---| | `docs: add design spec for type-split refactor (0.7.0)` | spec only | no | | `refactor: extract pure register codec to private mod ops` | new `mod ops` with `to_celsius` / `to_raw` / `snap_hysteresis` / `decode_config` / `apply_config` / constants. Method bodies become thin shells. | no | | `refactor!: split into Tmp108 (blocking) and AsyncTmp108 (async)` | the big mechanical step — drops `maybe-async-cfg`, splits the struct + every impl, splits `Interface` into `Interface` + `AsyncInterface`, kills the `# fn main()` doctest shim (AGENTS.md Gotcha 3), and adds first-class doctests on the async type. `embedded-sensors-hal-async` now implies `async` in `Cargo.toml`. | **yes** | | `feat!: make AlertTmp108::tmp108 private; add sensor/sensor_mut/into_inner` | hide the inner field; `sensor()` / `sensor_mut()` for access; `into_inner() -> (AsyncTmp108<I2C>, ALERT)` as the inverse of `into_alert` | **yes** | | `feat: add Eq to Config; add Clone/Copy/PartialEq/Eq to Error<E, P>` | architect's bonus point #3/#4 — derive on `Config`, conditional manual impls on `Error<E, P>` | no | | `docs(AGENTS): update for type-split architecture` | replace Gotcha 3 (maybe-async-cfg), update Gotchas 4/5 to mention `AsyncTmp108`, rewrite "Where to put new things", note matrix shrinkage | no | | `docs(README): update snippets and Cargo features for AsyncTmp108` | snippets switched to `AsyncTmp108` / `sensor_mut()`; features table updated; cancel-safety gotcha surfaced in the README too | no | | `chore: bump version to 0.7.0 and update CHANGELOG` | release | release | ## Why two named types The maintainer-facing complaint and the user-facing complaint about `maybe-async-cfg` are the same: the macro promises a "single source" that it doesn't actually deliver — every method body in 0.6.0 contained hand-written `#[cfg(feature = "async")]` arms duplicating the meaningful work, the trait impls (`embedded-sensors-hal*`, `AlertTmp108`) were hand-written twice already, and the test module had parallel `blocking` / `asynchronous` submodules. The macro *added* the doctest workaround (Gotcha 3, the `# fn main()` shim) *and* prevented async paths from getting any doctest coverage at all — for negative payoff. This PR captures the architect's verdict: do the duplication honestly at the struct level, share the *actual* shared logic (register encoding/decoding, hysteresis snapping, range validation) in a `mod ops`, and let each driver carry its own doctest in the right shape. Quantified wins: - Doctest count with `--all-features`: 18 → **47** (2.6×; async paths now covered for the first time). - `cargo hack --feature-powerset` matrix: 8 → **6** combinations (`embedded-sensors-hal-async` now implies `async`, the two previously-dead-code combos are gone). - `cargo vet --locked`: 177 → **170** audited (the `maybe-async-cfg` proc-macro subtree is dropped). - AGENTS.md gotchas: 10 → 10, but Gotcha 3 (the `# fn main()` shim that bit every new contributor) is replaced with a description of the parallel-types-share-`ops` architecture. - `src/lib.rs`: ~1700 → ~2850 lines. The increase is the doubled shells + doubled doctests; the meaningful work shrinks (it's in `mod ops` now). ## Migration (also in CHANGELOG) For most downstream code there is exactly one mechanical change: ```rust // Before use tmp108::Tmp108; let mut tmp = Tmp108::new_with_a0_gnd(i2c); let t = tmp.temperature().await?; // After use tmp108::AsyncTmp108; let mut tmp = AsyncTmp108::new_with_a0_gnd(i2c); let t = tmp.temperature().await?; ``` Blocking users keep `use tmp108::Tmp108` unchanged. Downstream code reaching into `AlertTmp108::tmp108` (the formerly public field) should switch to `tmp.sensor()` / `tmp.sensor_mut()` or use `into_inner()` to destructure. The `AlertTmp108` `Self::Error` is now `Error<I2C::Error, ALERT::Error>` (was `Error<I2C::Error>`); code that named the type explicitly needs the second parameter. `embedded-sensors-hal-async` now implies `async`, so feature specifications that listed both are unaffected; those who only listed the trait feature were silently building dead code and will now get the right behavior automatically. ## Test coverage The mock `Transaction` expectations in the existing test suite are **byte-for-byte identical** to PR #47's — proof that the wire protocol is unchanged. New tests cover the new surface: - `mod ops_tests` — 8 unit tests of the pure functions (to_celsius including non-zero-low-bits, to_raw rejection + boundary, snap_hysteresis acceptance/rejection, decode/apply round-trip, POR constant pin, `Config: Eq` compile-check, `Error<E,P>` trait derives). - All `tests::asynchronous` mocks updated to `AsyncTmp108` (purely type-renamed; expectations unchanged). - 18 new async doctests on `AsyncTmp108` methods, plus 3 on `AlertTmp108::sensor` / `sensor_mut` / `into_inner`. ## Local matrix verified Against today's `upstream/main` (post-merge of #47): - `cargo +nightly fmt --check` - `cargo clippy --all-features --all-targets -- -W clippy::suspicious -W clippy::correctness -W clippy::perf -W clippy::style` - `cargo doc --no-deps --all-features --locked` - `cargo test --locked` (24/18 unit/doctests) ; `-F async` (25/37) ; `-F async,embedded-sensors-hal-async` (31/47) - `cargo build --examples --locked` × 4 feature combos - `cargo hack --feature-powerset check --locked` (6/6) - `./scripts/check-readme-snippets.sh` (3/3) - `cargo vet --locked` (170 audited) `cargo-semver-checks` not installed locally; CI will flag the breaking changes and confirm the 0.7.0 bump. Hardware verification was **not** performed for this PR. The register-level interactions are unchanged (proven by the unmodified mock expectations) and `AlertTmp108`'s wire-level behavior is untouched, but a hands-on pass on `oneshot` / `continuous` / `sensor_trait` / `alert_interrupt` / `alert_comparator` against a Pico de Gallo before merge would be valuable per AGENTS.md. ## Risks called out in the spec 1. **`Tmp108 → AsyncTmp108` rename has wide blast radius** for async downstream code. The migration is mechanical, the CHANGELOG covers it, and the new name (`AsyncTmp108`) follows the `embedded-storage` / `embedded-storage-async` ecosystem convention. 2. **Big single commit for the type-split.** ~1100 lines of `src/lib.rs` are rewritten in `refactor!: split into Tmp108 (blocking) and AsyncTmp108 (async)`. Mitigation: shared logic was already extracted into `mod ops` in the prior commit, the wire-level mock expectations don't change (any test failure means a real bug), and each shell pair is small (~5 lines) and parallel — review can scan them as pairs. 3. **`embedded-sensors-hal-async` now requires `async`.** This is a *strengthening* of an existing implicit requirement (the old combo built dead code). CHANGELOG calls it out. 4. **Binary-breaking `Error` generic parameter** was added in PR #47; this PR only adds derives. No additional semver impact beyond the type renames called out above. ## Out of scope - Hardware verification of TLow/THigh resolution (H3 from #47's review) — still hardware-only. - Diagnostic counters / sticky flags (L5). - Probe-on-construct (`new() -> Result<...>`) — rejected in #47's review and not revisited. - Any new public methods beyond `AlertTmp108::sensor` / `sensor_mut` / `into_inner`. ## Conventional Commits compliance Every commit follows [Conventional Commits v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Breaking commits have `!` after the type and a `BREAKING CHANGE:` footer. Every commit ends with an `Assisted-by: opencode:claude-opus-4.7-1m-internal` trailer per AGENTS.md. Follows #47.
1 parent aad2141 commit 68a9640

13 files changed

Lines changed: 1861 additions & 680 deletions

AGENTS.md

Lines changed: 78 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,25 @@ I²C temperature sensor, built on `embedded-hal` 1.0 and
1919
device description (`tmp108.toml`) by the
2020
[`device-driver`](https://crates.io/crates/device-driver) crate.
2121

22-
Two public driver structs:
23-
24-
- `Tmp108<I2C>` — the bare driver. Available in both blocking
25-
(`embedded-hal`) and async (`embedded-hal-async`) flavors, gated by
26-
the `async` cargo feature, generated from a single source via
27-
[`maybe-async-cfg`](https://crates.io/crates/maybe-async-cfg).
28-
- `AlertTmp108<I2C, ALERT>` — wraps `Tmp108` with an `embedded-hal` /
29-
`embedded-hal-async` GPIO pin so it can implement
22+
Public driver types:
23+
24+
- `Tmp108<I2C>` — the blocking driver, built on
25+
[`embedded_hal::i2c::I2c`]. Always available.
26+
- `AsyncTmp108<I2C>` — the async driver, built on
27+
[`embedded_hal_async::i2c::I2c`]. Gated by the `async` cargo
28+
feature. Unlocks `AsyncTmp108::continuous`, which has no blocking
29+
equivalent (see README's "Gotchas").
30+
- `AlertTmp108<I2C, ALERT>` — wraps `AsyncTmp108` with an
31+
`embedded-hal` / `embedded-hal-async` GPIO pin so it can implement
3032
`embedded-sensors-hal-async::TemperatureThresholdWait`. Async-only.
31-
Gated by `cfg(all(feature = "embedded-sensors-hal-async", feature = "async"))`.
33+
Gated by `cfg(feature = "embedded-sensors-hal-async")`. Access the
34+
inner sensor via `sensor()` / `sensor_mut()` / `into_inner()`.
35+
36+
Since 0.7.0 both `Tmp108` and `AsyncTmp108` are available
37+
simultaneously when both features are enabled. The shared
38+
sync/async-agnostic register codec lives in a private `mod ops`; the
39+
two driver types are thin shells that perform I²C and delegate the
40+
meaningful work to `ops::*`.
3241

3342
[datasheet]: https://www.ti.com/lit/gpn/tmp108
3443

@@ -42,7 +51,14 @@ Two public driver structs:
4251
2. [`docs/superpowers/specs/2026-06-03-tmp108-agent-usage-docs-design.md`](docs/superpowers/specs/2026-06-03-tmp108-agent-usage-docs-design.md)
4352
if you're touching docs/examples — it captures the doctest +
4453
snippet-marker invariants.
45-
3. `CONTRIBUTING.md` for the commit-message convention (Conventional
54+
3. [`docs/superpowers/specs/2026-06-03-tmp108-reliability-fixes-design.md`](docs/superpowers/specs/2026-06-03-tmp108-reliability-fixes-design.md)
55+
for the rationale behind the current `Error<E, P>` shape, the
56+
`set_*_limit` rejection contract, the `continuous()` cleanup
57+
contract, and the hysteresis snapping band.
58+
4. [`docs/superpowers/specs/2026-06-04-tmp108-type-split-design.md`](docs/superpowers/specs/2026-06-04-tmp108-type-split-design.md)
59+
for the rationale behind the `Tmp108` / `AsyncTmp108` split and the
60+
`mod ops` shared codec.
61+
5. `CONTRIBUTING.md` for the commit-message convention (Conventional
4662
Commits v1.0.0) and the "each commit builds clean" rule.
4763

4864
---
@@ -53,17 +69,19 @@ Two public driver structs:
5369
.
5470
├── Cargo.toml # MSRV 1.90; edition 2024; deny-list lints
5571
├── tmp108.toml # device-driver register description
72+
├── CHANGELOG.md # Keep-a-Changelog v1.1.0 format
5673
├── src/
57-
│ ├── lib.rs # Tmp108 + AlertTmp108 + Config + Error + traits
74+
│ ├── lib.rs # Tmp108 + AsyncTmp108 + AlertTmp108 +
75+
│ │ # Config + Error + private mod ops + traits
5876
│ └── inner.rs # GENERATED by device-driver from tmp108.toml — do not hand-edit
5977
├── examples/ # All five examples run on Pico de Gallo (see below)
60-
│ ├── oneshot.rs
61-
│ ├── continuous.rs # async-only
78+
│ ├── oneshot.rs # blocking OR async, depending on features
79+
│ ├── continuous.rs # async-only (AsyncTmp108)
6280
│ ├── alert_interrupt.rs # async + embedded-sensors-hal-async
6381
│ ├── alert_comparator.rs # async + embedded-sensors-hal-async
6482
│ └── sensor_trait.rs # blocking OR async, depending on features
6583
├── tests/
66-
│ └── reexports.rs # compile-only: pins crate-root re-exports
84+
│ └── reexports.rs # compile-only: pins Tmp108 + AsyncTmp108
6785
├── scripts/
6886
│ └── check-readme-snippets.sh # CI gate: README in sync with examples/
6987
├── supply-chain/ # cargo-vet audits
@@ -80,19 +98,23 @@ Two public driver structs:
8098
| Feature | Effect | Implies |
8199
|---|---|---|
82100
| (none) | blocking `Tmp108` over `embedded-hal` ||
83-
| `async` | async `Tmp108` over `embedded-hal-async`; unlocks `Tmp108::continuous` ||
84-
| `embedded-sensors-hal` | blocking `TemperatureSensor` trait impl ||
85-
| `embedded-sensors-hal-async` | async `TemperatureSensor`, `TemperatureThresholdSet`, `TemperatureHysteresis`; `AlertTmp108` wrapper | **requires** `async` |
101+
| `async` | async `AsyncTmp108` over `embedded-hal-async`; unlocks `AsyncTmp108::continuous` ||
102+
| `embedded-sensors-hal` | blocking `TemperatureSensor` trait impl on `Tmp108` ||
103+
| `embedded-sensors-hal-async` | async `TemperatureSensor`, `TemperatureThresholdSet`, `TemperatureHysteresis` on `AsyncTmp108`; `AlertTmp108` wrapper | **`async`** (forced in Cargo.toml since 0.7.0) |
86104

87105
Things that follow from the matrix and trip up new contributors:
88106

107+
- `AsyncTmp108` only exists under `cfg(feature = "async")`. The
108+
blocking `Tmp108` is always available.
89109
- `AlertTmp108` and everything that takes an `ALERT` pin **only exist**
90-
under `cfg(all(feature = "embedded-sensors-hal-async", feature = "async"))`.
91-
- `Tmp108::continuous` only exists under `cfg(feature = "async")`. There
92-
is **no blocking equivalent**. The README's `Gotchas` section
110+
under `cfg(feature = "embedded-sensors-hal-async")` (which forces
111+
`async`).
112+
- `AsyncTmp108::continuous` only exists under `cfg(feature = "async")`.
113+
There is **no blocking equivalent**. The README's "Gotchas" section
93114
documents the workaround.
94115
- `cargo hack --feature-powerset check --locked` enumerates every legal
95-
combination and must stay green. Use it before pushing.
116+
combination (6 since 0.7.0, down from 8 because `embedded-sensors-hal-async`
117+
now implies `async`). All combos must stay green. Use it before pushing.
96118

97119
---
98120

@@ -181,44 +203,31 @@ build time (actually committed for now, but it's machine output).
181203
Modify `tmp108.toml` for register-layout changes; everything else
182204
flows from there.
183205

184-
### 3. The `maybe-async-cfg` pattern in `src/lib.rs`
185-
186-
Most methods on `Tmp108` are written **once** under
187-
`#[maybe_async_cfg::maybe(...)]` and the macro expands to either the
188-
blocking or the async variant based on the `async` feature. This means:
189-
190-
- A single function declaration produces *two* different real
191-
functions, only one of which is compiled at a time.
192-
- Doctests cannot satisfy both signatures with the same body — the
193-
blocking variant takes `.unwrap()`, the async variant needs
194-
`.await.unwrap()`.
195-
- Our doctests use a cfg-gated `# fn main()` trick to target the
196-
blocking signature under `cargo test --doc` (default features) and
197-
become inert under `-F async`:
198-
199-
```rust
200-
/// ```
201-
/// # use embedded_hal_mock::eh1::i2c::{Mock, Transaction};
202-
/// # #[cfg(feature = "async")] fn main() {}
203-
/// # #[cfg(not(feature = "async"))]
204-
/// # fn main() {
205-
/// use tmp108::Tmp108;
206-
/// let i2c = Mock::new(&[
207-
/// Transaction::write_read(0x48, vec![0x00], vec![0x32, 0x00]),
208-
/// ]);
209-
/// let mut tmp = Tmp108::new_with_a0_gnd(i2c);
210-
/// let temp = tmp.temperature().unwrap();
211-
/// assert!((temp - 50.0).abs() < 0.01);
212-
/// # let mut i2c = tmp.destroy();
213-
/// # i2c.done();
214-
/// # }
215-
/// ```
216-
```
217-
218-
Follow this exact pattern when adding doctests on any
219-
`maybe_async_cfg`'d method. Don't reinvent it.
220-
221-
### 4. `Tmp108::continuous` requires `async |t| { ... }`
206+
### 3. Two parallel driver types share a private `mod ops`
207+
208+
Since 0.7.0 the driver is implemented as two named types:
209+
210+
- `pub struct Tmp108<I2C: embedded_hal::i2c::I2c>` — always available.
211+
- `pub struct AsyncTmp108<I2C: embedded_hal_async::i2c::I2c>`
212+
`#[cfg(feature = "async")]`.
213+
214+
Both delegate all chip-specific logic (Celsius↔raw conversion,
215+
limit-range validation, hysteresis snapping, `Config` ↔ register-byte
216+
codec) to a private `mod ops` of pure functions. The two driver
217+
shells are otherwise mirrors of each other; when adding a new method
218+
on one, add the matching one on the other and route the shared logic
219+
through `ops::*`.
220+
221+
Doctests are independent per type: `Tmp108` doctests use the plain
222+
blocking shape, `AsyncTmp108` doctests wrap their body in
223+
`tokio::runtime::Runtime::new().unwrap().block_on(async { ... })`.
224+
Don't try to share doctest bodies — the macro shim from earlier
225+
versions is gone, and the architect's review (see
226+
`docs/superpowers/specs/2026-06-04-tmp108-type-split-design.md`)
227+
explicitly chose source-duplication-with-shared-codec over
228+
maybe-async-cfg precisely because of how cleanly this separates.
229+
230+
### 4. `AsyncTmp108::continuous` requires `async |t| { ... }`
222231

223232
The signature is `F: AsyncFnOnce(&mut Self) -> Result<(), I2C::Error>`
224233
— Rust's native async-closure syntax (`async |t| { ... }`, stable
@@ -234,11 +243,11 @@ correct templates. Copy from them, not from the old README.
234243
### 5. `Config` field-type re-exports are public surface
235244

236245
`tmp108::{Mode, Thermostat, ConversionRate, Hysteresis, Polarity}` are
237-
`pub use`'d at the crate root via `src/lib.rs:34`. Without them,
238-
downstream consumers cannot construct a non-default `Config`. There is
239-
a compile-only test in `tests/reexports.rs` that pins all five names
240-
removing or renaming any of them breaks that test and is a
241-
**breaking change**.
246+
`pub use`'d at the crate root. Without them, downstream consumers
247+
cannot construct a non-default `Config`. There is a compile-only test
248+
in `tests/reexports.rs` that pins all five names plus `Tmp108` and
249+
(under `-F async`) `AsyncTmp108`removing or renaming any of them
250+
breaks that test and is a **breaking change**.
242251

243252
### 6. Each commit must build clean and pass clippy/fmt independently
244253

@@ -405,22 +414,24 @@ certify the Developer Certificate of Origin.
405414

406415
| You're adding… | Put it here |
407416
|---|---|
408-
| A new public method on `Tmp108`/`AlertTmp108` | `src/lib.rs`, with a `# Examples` doctest |
417+
| A new public method that should exist on both driver flavors | Add it **twice**: once on `Tmp108` (blocking impl), once on `AsyncTmp108` (async impl). Put any sync/async-agnostic helper logic in `mod ops`; each method body should be a thin shell that performs I²C and calls into `ops::*`. Both methods need their own `# Examples` doctest in the right shape (blocking vs `tokio::block_on(async {…})`). |
418+
| A new public method on `AlertTmp108` | `src/lib.rs`, in the `AlertTmp108` impl block, with a `tokio::block_on`-wrapped doctest. Use `self.sensor()` / `self.sensor_mut()` to reach the inner `AsyncTmp108`; do not access the private `tmp108` field. |
409419
| A new register field | `tmp108.toml`, regenerate `src/inner.rs` if needed |
410420
| A new example | `examples/<name>.rs`, with `//!` block stating hardware + features + register interactions; add to the CI matrix in `.github/workflows/check.yml` |
411421
| A new doctest pattern | If it's reusable, document it in this file under "Gotchas" |
412-
| A new test (unit) | `src/lib.rs` `mod tests` |
422+
| A new test (unit) | `src/lib.rs` `mod tests` (pure-function tests go in `mod tests::ops_tests`; blocking-driver tests go in `mod tests::blocking`; async-driver tests go in `mod tests::asynchronous`) |
413423
| A new test (integration) | `tests/<topic>.rs` |
414424
| A new dev-dep | `Cargo.toml [dev-dependencies]` + a `[[audits.<crate>]]` entry |
415425
| A README usage snippet | Marker region in an example file + matching `<!-- snippet: NAME -->` block in `README.md`; register in `scripts/check-readme-snippets.sh` |
416426
| A design document | `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` |
417427
| An implementation plan | `docs/superpowers/plans/YYYY-MM-DD-<topic>.md` |
428+
| A user-visible change | An entry in `CHANGELOG.md` under the next-unreleased version's appropriate section |
418429

419430
---
420431

421432
## When in doubt
422433

423-
Read the source. `src/lib.rs` is ~1,700 lines (most of it `# Examples`
434+
Read the source. `src/lib.rs` is ~2,850 lines (most of it `# Examples`
424435
doctests and an `mod tests` that shows every register interaction the
425436
driver performs at the wire level). The doctests show how each public
426437
method is meant to be used. The examples show end-to-end workflows on

CHANGELOG.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,74 @@ All notable changes to this project are documented here. The format is
44
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
55
this project adheres to [Semantic Versioning](https://semver.org/).
66

7+
## [0.7.0] — 2026-06-04
8+
9+
An architectural refactor: the single `maybe-async-cfg`-generated
10+
`Tmp108<I2C>` is replaced by two named driver types, **`Tmp108`** for
11+
the blocking flavor and **`AsyncTmp108`** for the async flavor. Both
12+
are now available simultaneously when both relevant features are
13+
enabled. The shared register codec lives in a new private `mod ops`.
14+
15+
### Breaking
16+
17+
- The async driver type is now `AsyncTmp108<I2C>` (was `Tmp108<I2C>`
18+
under `feature = "async"`). The blocking `Tmp108<I2C>` is
19+
unchanged. **Migration:** in async code, replace `use tmp108::Tmp108;`
20+
with `use tmp108::AsyncTmp108;` and rename references accordingly.
21+
- `AlertTmp108::tmp108` is no longer a public field. **Migration:**
22+
use the new `sensor()` / `sensor_mut()` accessors, or `into_inner()`
23+
to destructure into `(AsyncTmp108<I2C>, ALERT)`.
24+
- The `embedded-sensors-hal-async` feature now implicitly enables
25+
`async` (it was previously possible to build dead code by enabling
26+
the trait feature without `async`). Callers who listed both
27+
explicitly are unaffected.
28+
- `Tmp108::into_alert` is removed; the equivalent now lives only on
29+
`AsyncTmp108::into_alert`, where it is the correct flavor (the
30+
bare blocking driver never had a coherent path to the async-only
31+
`AlertTmp108` wrapper).
32+
- `AsyncTmp108::continuous` is async-only and lives only on the
33+
async driver type (it previously lived on `Tmp108` under
34+
`feature = "async"` and was awkwardly cfg-gated within a
35+
maybe-async-cfg'd impl block).
36+
37+
### Added
38+
39+
- `AsyncTmp108<I2C>` driver type, parallel in surface to `Tmp108`.
40+
Both flavors expose: `new`, `new_with_a0_gnd/vplus/sda/scl`,
41+
`addr`, `destroy`, `probe`, `read_configuration`, `configure`,
42+
`temperature`, `one_shot`, `shutdown`, `wait_for_temperature`,
43+
`low_limit`, `set_low_limit`, `high_limit`, `set_high_limit`.
44+
`AsyncTmp108` additionally provides `continuous` and
45+
`into_alert`.
46+
- `AlertTmp108::sensor() -> &AsyncTmp108<I2C>` and
47+
`AlertTmp108::sensor_mut() -> &mut AsyncTmp108<I2C>` replace the
48+
former `pub tmp108` field.
49+
- `AlertTmp108::into_inner(self) -> (AsyncTmp108<I2C>, ALERT)`
50+
inverse of `AsyncTmp108::into_alert`.
51+
- `Eq` and `Hash` on `Config`.
52+
- Conditional `Clone`, `Copy`, `PartialEq`, `Eq` on `Error<E, P>`
53+
(when `E` and `P` implement the corresponding trait).
54+
- First-class doctest coverage for the async driver. Doctest count
55+
with `--all-features` rises from 18 to 47.
56+
57+
### Internal
58+
59+
- The driver no longer depends on `maybe-async-cfg`. The two driver
60+
types are written out directly and share their meaningful logic
61+
through a private `mod ops` of pure functions
62+
(`to_celsius`, `to_raw`, `snap_hysteresis`, `decode_config`,
63+
`apply_config`, plus the relevant constants).
64+
- The internal `Interface` is split into a blocking `Interface`
65+
(always available) and an async `AsyncInterface`
66+
(`#[cfg(feature = "async")]`). Each implements the matching
67+
`device-driver` register-interface trait.
68+
- The `# fn main()` doctest workaround (formerly Gotcha 3 in
69+
AGENTS.md) is gone — each type has its own doctests with the
70+
correct shape for its flavor.
71+
- `cargo hack --feature-powerset` matrix shrinks from 8 to 6 legal
72+
combinations (the two combos that disabled `async` while enabling
73+
`embedded-sensors-hal-async` are gone).
74+
775
## [0.6.0] — 2026-06-03
876

977
A reliability-focused release. Several `Error` paths and `Tmp108`

0 commit comments

Comments
 (0)