Skip to content

feat: add device status read - #2

Merged
nonnil merged 10 commits into
mainfrom
fix/device-status-read
Apr 21, 2026
Merged

feat: add device status read#2
nonnil merged 10 commits into
mainfrom
fix/device-status-read

Conversation

@nonnil

@nonnil nonnil commented Apr 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Add DeviceStatus as a typed status snapshot for stick firmware, optional holder firmware, and optional battery voltage.
  • Add Iqos::read_device_status(model) to aggregate firmware reads and a non-fatal battery voltage read.
  • Update the debug CLI inspect flow to print GATT battery level plus the aggregated device status.
  • Cover one-piece, folder-type, battery failure, and firmware error paths with unit tests.

Validation

  • cargo fmt --all --check
  • cargo test --all-targets --all-features
  • cargo clippy --all-targets --all-features -- -D warnings

Notes

  • Local worktree has unrelated uncommitted changes in .gitignore and an untracked package-lock.json; they are not part of this pushed branch commit.

Summary by CodeRabbit

  • New Features

    • Device status read: aggregates stick firmware, optional holder firmware, and battery voltage; CLI now prints formatted device status and relabels GATT battery output.
    • Expanded device model capability mapping: more models report holder-related features (affects SmartGesture/AutoStart and charge-start vibration).
  • Tests

    • Added unit tests for model variations, request sequencing, and error-handling for device status reads.
  • Chores

    • Ignore docs/firmware_extraction in VCS.

- Add `src/protocol/status.rs` with `DeviceStatus` struct holding
  stick_firmware, holder_firmware (folder-type only), battery_voltage (non-fatal)
- Implement `Iqos::read_device_status(model)` — reads stick firmware always,
  holder firmware only when model.supports_holder_features(), battery voltage via .ok()
- Export `DeviceStatus` from protocol module and crate root
- Update `inspect_device` CLI to call read_device_status and print results
- Add `print_device_status` helper to main.rs
- Add 4 unit tests covering one-piece, folder-type, battery failure, and firmware error paths
@nonnil

nonnil commented Apr 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai

@nonnil
nonnil marked this pull request as ready for review April 20, 2026 05:28
@nonnil

nonnil commented Apr 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@nonnil

nonnil commented Apr 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@nonnil It looks like your message didn't include a specific request. How can I help you? Here are some things I can do:

  • Review the PR – trigger a full review of the changes
  • Answer questions – about the code, design decisions, or anything in the PR
  • Generate code – suggest fixes, improvements, or new code
  • Create issues – open a follow-up GitHub issue for anything that needs tracking
  • Search the codebase – dig into specific files or patterns

Just let me know what you need! 🐇

@nonnil

nonnil commented Apr 20, 2026

Copy link
Copy Markdown
Member Author

Tip

For best results, initiate chat on the files or code changes.

@nonnil It looks like your message didn't include a specific request. How can I help you? Here are some things I can do:

  • Review the PR – trigger a full review of the changes
  • Answer questions – about the code, design decisions, or anything in the PR
  • Generate code – suggest fixes, improvements, or new code
  • Create issues – open a follow-up GitHub issue for anything that needs tracking
  • Search the codebase – dig into specific files or patterns

Just let me know what you need! 🐇

review

@nonnil

nonnil commented Apr 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@nonnil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 19 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 52 minutes and 19 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56065093-057c-4548-b9c7-d91a4950e959

📥 Commits

Reviewing files that changed from the base of the PR and between 88fb7a1 and 92749a4.

📒 Files selected for processing (7)
  • debug/hardware_ble/suites.rs
  • src/lib.rs
  • src/main.rs
  • src/protocol/ble.rs
  • src/protocol/mod.rs
  • src/protocol/product.rs
  • src/protocol/status.rs
📝 Walkthrough

Walkthrough

Adds a new DeviceStatus type, re-exports it, and implements Iqos::read_device_status(&self, model: DeviceModel, device_info: DeviceInfo) -> Result<DeviceStatus> which reads stick firmware, conditionally reads holder firmware, and attempts battery voltage; updates CLI printing and model capability mappings/tests.

Changes

Cohort / File(s) Summary
Protocol status & re-export
src/protocol/status.rs, src/protocol/mod.rs
Add public DeviceStatus struct (model, device_info, stick_firmware, holder_firmware: Option<..>, battery_voltage: Option) and re-export it from protocol.
Iqos API & tests
src/lib.rs
Add pub async fn read_device_status(&self, model: DeviceModel, device_info: DeviceInfo) -> Result<DeviceStatus>: reads stick firmware (errors propagate), conditionally reads holder firmware when model.supports_holder_features() (errors propagate), reads battery voltage (on Error::Transport(_) set battery_voltage = None, other battery errors propagate). Add unit tests for request ordering, conditional holder read, and battery error semantics. Update public re-exports to include DeviceStatus.
BLE inspect CLI
src/main.rs
Switch BLE inspection flow to construct Iqos from session, call read_device_status(model, device_info), and print DeviceStatus via new print_device_status helper; adjust battery/GATT error messaging.
Device model capabilities & tests
src/protocol/types.rs
Expand DeviceModel::supports_holder_features() to include IlumaPrime/IlumaIPrime; update supports/capability logic and related tests (SmartGesture/AutoStart and charge-start vibration mapping).
Misc
.gitignore
Ignore docs/firmware_extraction directory.

Sequence Diagram

sequenceDiagram
    participant Client as BLE Inspection Flow
    participant Iqos as Iqos API
    participant Transport as Transport Layer
    participant Device as IQOS Device

    Client->>Iqos: read_device_status(model, device_info)
    Iqos->>Transport: read_stick_firmware()
    Transport->>Device: diagnostic request
    Device-->>Transport: firmware response
    Transport-->>Iqos: FirmwareVersion

    alt model.supports_holder_features()
        Iqos->>Transport: read_holder_firmware()
        Transport->>Device: diagnostic request
        Device-->>Transport: firmware response
        Transport-->>Iqos: FirmwareVersion
    else
        Iqos->>Iqos: holder_firmware = None
    end

    Iqos->>Transport: read_battery_voltage()
    Transport->>Device: diagnostic request
    alt Battery read succeeds
        Device-->>Transport: voltage response
        Transport-->>Iqos: f32
    else Battery read fails (transport)
        Transport-->>Iqos: Error::Transport -> battery_voltage = None
    end

    Iqos-->>Client: DeviceStatus { model, device_info, stick_firmware, holder_firmware?, battery_voltage? }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through bytes to fetch the news,
Stick first, holder next when models choose,
Voltage may whisper, or sometimes be shy,
Status bundled neat beneath my sky,
Tiny rabbit cheers — your device speaks nigh!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add device status read' clearly and concisely describes the main change: introducing a new device status reading capability. It directly matches the core objective of adding Iqos::read_device_status() method and the DeviceStatus struct.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/device-status-read

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/protocol/status.rs (1)

3-16: Consider decoupling the doc from specific model names.

The doc enumerates Iluma, IlumaI as the folder-type models, but the actual decision is made in Iqos::read_device_status via DeviceModel::supports_holder_features(). If that capability set changes, this comment will silently drift. Prefer phrasing it in terms of the capability (e.g. "models where supports_holder_features() returns true").

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/protocol/status.rs` around lines 3 - 16, The doc comment on DeviceStatus
ties behavior to specific models (Iluma, IlumaI); instead, rewrite the
documentation to describe behavior in terms of the model capability used in
Iqos::read_device_status (i.e., whether DeviceModel::supports_holder_features()
is true). Update the fields' docs for stick_firmware, holder_firmware and
battery_voltage to say "for models where supports_holder_features() is true"
rather than naming specific models, and confirm the narrative matches how
Iqos::read_device_status populates these fields.
src/lib.rs (2)

1100-1184: Good coverage; one gap worth adding.

The four new tests cover one-piece, folder, battery failure, and stick-firmware failure paths well. Consider adding a symmetric case for a folder-model holder-firmware error to lock in that holder-read errors do propagate (the doc comment explicitly states firmware reads are fatal). Optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib.rs` around lines 1100 - 1184, Add a test that exercises
read_device_status for DeviceModel::Iluma where the transport returns a
successful stick-firmware response then an Err for the holder firmware to assert
that the error is propagated (i.e., read_device_status returns Err). Use the
same test pattern and helpers (MockTransport::with_responses, Iqos::new,
block_on) and reference protocol::LOAD_STICK_FIRMWARE_VERSION_COMMAND and
protocol::LOAD_HOLDER_FIRMWARE_VERSION_COMMAND to ensure the holder-read error
path is hit and the returned error matches Error::Transport.

385-394: Battery .ok() swallows decode errors silently — consider narrowing or logging.

self.read_battery_voltage().await.ok() discards every error variant, including Error::ProtocolDecode (malformed frame) and Error::Unsupported, not just transient transport failures. Callers only see None and cannot distinguish "device offline" from "protocol regression". The CLI in main.rs already handles both the inner None and an outer Err, so at minimum it would be useful to emit a log::warn!/tracing::warn! when swallowing, or restrict the fallback to transport-class errors:

Optional narrower fallback
-        let battery_voltage = self.read_battery_voltage().await.ok();
+        let battery_voltage = match self.read_battery_voltage().await {
+            Ok(v) => Some(v),
+            Err(Error::Transport(_)) => None,
+            Err(e) => return Err(e),
+        };

Not blocking — the current behavior matches the documented contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib.rs` around lines 385 - 394, The call to
self.read_battery_voltage().await.ok() silently discards all error kinds
(including ProtocolDecode/Unsupported); change it to match the Result from
read_battery_voltage() in read_device_status so you only swallow transport-class
failures or at least log non-transport failures. For example, replace the .ok()
with a match on self.read_battery_voltage().await that returns Some(voltage) on
Ok, returns None for known transport errors, and for other errors calls
tracing::warn!(error = ?e, "failed to read battery voltage, swallowing") (or
process_logger.warn) before returning None; reference the read_device_status
method and the read_battery_voltage call and handle Error::ProtocolDecode /
Error::Unsupported specially.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib.rs`:
- Around line 1100-1184: Add a test that exercises read_device_status for
DeviceModel::Iluma where the transport returns a successful stick-firmware
response then an Err for the holder firmware to assert that the error is
propagated (i.e., read_device_status returns Err). Use the same test pattern and
helpers (MockTransport::with_responses, Iqos::new, block_on) and reference
protocol::LOAD_STICK_FIRMWARE_VERSION_COMMAND and
protocol::LOAD_HOLDER_FIRMWARE_VERSION_COMMAND to ensure the holder-read error
path is hit and the returned error matches Error::Transport.
- Around line 385-394: The call to self.read_battery_voltage().await.ok()
silently discards all error kinds (including ProtocolDecode/Unsupported); change
it to match the Result from read_battery_voltage() in read_device_status so you
only swallow transport-class failures or at least log non-transport failures.
For example, replace the .ok() with a match on self.read_battery_voltage().await
that returns Some(voltage) on Ok, returns None for known transport errors, and
for other errors calls tracing::warn!(error = ?e, "failed to read battery
voltage, swallowing") (or process_logger.warn) before returning None; reference
the read_device_status method and the read_battery_voltage call and handle
Error::ProtocolDecode / Error::Unsupported specially.

In `@src/protocol/status.rs`:
- Around line 3-16: The doc comment on DeviceStatus ties behavior to specific
models (Iluma, IlumaI); instead, rewrite the documentation to describe behavior
in terms of the model capability used in Iqos::read_device_status (i.e., whether
DeviceModel::supports_holder_features() is true). Update the fields' docs for
stick_firmware, holder_firmware and battery_voltage to say "for models where
supports_holder_features() is true" rather than naming specific models, and
confirm the narrative matches how Iqos::read_device_status populates these
fields.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e7f6922-0fb5-44e2-b71d-947f11e967e7

📥 Commits

Reviewing files that changed from the base of the PR and between 08a03dc and 25d24e5.

📒 Files selected for processing (4)
  • src/lib.rs
  • src/main.rs
  • src/protocol/mod.rs
  • src/protocol/status.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a typed “device status” snapshot API to the IQOS library (aggregating firmware versions plus an optional battery voltage reading), and wires it into the debug CLI for easier inspection.

Changes:

  • Introduce DeviceStatus and re-export it through protocol and crate root.
  • Add Iqos::read_device_status(model) to read stick firmware, optional holder firmware, and non-fatal battery voltage.
  • Expand holder-capability gating to include PRIME models and add unit tests covering sequencing and error semantics.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/protocol/types.rs Updates holder-capability detection and related capability gating; adds tests for the expanded model matrix.
src/protocol/status.rs Adds the new DeviceStatus snapshot type.
src/protocol/mod.rs Registers the new module and re-exports DeviceStatus.
src/lib.rs Exposes DeviceStatus at crate root and implements Iqos::read_device_status; adds unit tests.
src/main.rs Updates inspect flow to print GATT battery level and the aggregated device status.
.gitignore Adds docs/firmware_extraction ignore entry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@nonnil

nonnil commented Apr 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.rs`:
- Around line 275-286: print_device_status currently treats a None
holder_firmware as "no holder support", which can mislead if None could also
mean a read failure; update iqos::DeviceStatus and print_device_status to
distinguish unsupported vs read-failed: change DeviceStatus.holder_firmware from
Option<String> to an enum (e.g., HolderFirmware::Unsupported |
HolderFirmware::Present(String) | HolderFirmware::ReadFailed) or add a separate
status field, adjust the producer (iqos::read_device_status) to populate the new
variant on failures, and update print_device_status to match on the new enum (or
field) to print "n/a (no holder support)", the firmware string, or "read failed"
accordingly so the CLI output remains accurate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26ce48fe-01f8-403f-b6da-f851bd9cb66b

📥 Commits

Reviewing files that changed from the base of the PR and between 25d24e5 and 9162282.

📒 Files selected for processing (5)
  • .gitignore
  • src/lib.rs
  • src/main.rs
  • src/protocol/status.rs
  • src/protocol/types.rs
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/protocol/status.rs
  • src/lib.rs

Comment thread src/main.rs
Comment on lines +275 to +286
#[cfg(feature = "btleplug-support")]
fn print_device_status(status: &iqos::DeviceStatus) {
println!("Stick firmware: {}", status.stick_firmware);
match &status.holder_firmware {
Some(fw) => println!("Holder firmware: {fw}"),
None => println!("Holder firmware: n/a (no holder support)"),
}
match status.battery_voltage {
Some(v) => println!("Battery voltage: {v:.3} V"),
None => println!("Battery voltage: read failed"),
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: None holder firmware is reported as "no holder support" even on read failure.

print_device_status assumes status.holder_firmware == None means the model doesn't support a holder. If Iqos::read_device_status ever surfaces a holder-supported model with a failed/absent holder firmware read as None (rather than propagating an error), the CLI will misleadingly print n/a (no holder support). If holder-firmware errors are always propagated via the outer Result today this is fine, but consider distinguishing "unsupported" from "read failed" at the DeviceStatus level (e.g., keep Option for unsupported and return Err for failures, or use an enum) to keep the CLI output honest as the API evolves.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 275 - 286, print_device_status currently treats a
None holder_firmware as "no holder support", which can mislead if None could
also mean a read failure; update iqos::DeviceStatus and print_device_status to
distinguish unsupported vs read-failed: change DeviceStatus.holder_firmware from
Option<String> to an enum (e.g., HolderFirmware::Unsupported |
HolderFirmware::Present(String) | HolderFirmware::ReadFailed) or add a separate
status field, adjust the producer (iqos::read_device_status) to populate the new
variant on failures, and update print_device_status to match on the new enum (or
field) to print "n/a (no holder support)", the firmware string, or "read failed"
accordingly so the CLI output remains accurate.

nonnil added 2 commits April 20, 2026 16:23
DeviceStatus previously held only SCP-layer fields (firmware versions,
battery voltage). The iqos_cli reference shows that a full device info
snapshot should also include model, model number, serial number,
software revision, and manufacturer name — all available from GATT
at connection time.

Changes:
- Add model: DeviceModel and device_info: DeviceInfo to DeviceStatus
- Update read_device_status signature to accept device_info from caller
  (already available via IqosBle after connect_and_discover)
- Rewrite print_device_status to mirror iqos_cli output format:
  holder models show Stick/Holder sections; one-piece models show flat layout
- Simplify inspect_device: single print_device_status call covers all fields
- Update all unit tests to pass DeviceInfo::default() as second arg

Product number fields (stick and holder) remain unimplemented — SCP
response format is unknown and no packet captures exist.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.rs`:
- Around line 127-138: The GATT battery result stored in battery_level is only
printed inside the Ok branch of Iqos::read_device_status, so on device status
read failures you lose battery diagnostics; update the control flow around
Iqos::new(session) and read_device_status(model, device_info).await so that
battery_level is inspected and printed regardless of whether read_device_status
returns Ok or Err — e.g., after matching read_device_status (or by moving the
match on battery_level out of the Ok arm), always match battery_level and print
either "Battery level (GATT): {level}%" or "Battery level (GATT): read failed
({error})"; keep existing calls to Iqos::new, read_device_status, and
print_device_status unchanged except for relocating/duplicating the
battery_level printing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d6f29c3-d4fc-42fa-89d5-2fd89a8b46d9

📥 Commits

Reviewing files that changed from the base of the PR and between 9162282 and 88fb7a1.

📒 Files selected for processing (3)
  • src/lib.rs
  • src/main.rs
  • src/protocol/status.rs
✅ Files skipped from review due to trivial changes (1)
  • src/protocol/status.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib.rs

Comment thread src/main.rs

@nonnil nonnil left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nonnil
nonnil merged commit afaf752 into main Apr 21, 2026
5 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 21, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants