Skip to main content

pico_de_gallo_internal/
lib.rs

1//! Shared wire-protocol types for the Pico de Gallo USB bridge.
2//!
3//! This crate defines the [postcard-rpc](https://docs.rs/postcard-rpc) endpoints,
4//! request/response types, and shared constants used by both the firmware
5//! ([`pico-de-gallo-firmware`]) and the host-side library ([`pico-de-gallo-lib`]).
6//!
7//! # Wire Compatibility
8//!
9//! All types are serialized with [postcard](https://docs.rs/postcard). Postcard
10//! encodes enum variants by **index** (0, 1, 2, …), not by discriminant value.
11//! Reordering variants in any `enum` in this crate is a **breaking wire change**
12//! that will silently corrupt communication between mismatched firmware and host
13//! versions.
14//!
15//! # Feature Flags
16//!
17//! - **`use-std`** — Enables `Vec<u8>` response types for the host side. Without
18//!   this feature (the default for firmware), responses use borrowed `&[u8]` slices.
19//!
20//! # Crate Organization
21//!
22//! - **Constants**: [`MICROSOFT_VID`], [`PICO_DE_GALLO_PID`], [`MAX_TRANSFER_SIZE`]
23//! - **Endpoints**: Defined via the [`postcard_rpc::endpoints!`] macro — see
24//!   [`ENDPOINT_LIST`] for the full table.
25//! - **I2C types**: [`I2cReadRequest`], [`I2cWriteRequest`], [`I2cWriteReadRequest`],
26//!   [`I2cScanRequest`], and their shared error type [`I2cError`].
27//! - **SPI types**: [`SpiReadRequest`], [`SpiWriteRequest`], [`SpiTransferRequest`]
28//!   and their shared error type [`SpiError`].
29//! - **GPIO types**: [`GpioGetRequest`], [`GpioPutRequest`], [`GpioWaitRequest`],
30//!   [`GpioState`], [`GpioDirection`], [`GpioPull`], [`GpioSetConfigurationRequest`],
31//!   and their shared error type [`GpioError`].
32//! - **UART types**: [`UartReadRequest`], [`UartWriteRequest`],
33//!   [`UartSetConfigurationRequest`], [`UartConfigurationInfo`],
34//!   and their shared error type [`UartError`].
35//! - **PWM types**: [`PwmSetDutyCycleRequest`], [`PwmGetDutyCycleRequest`],
36//!   [`PwmEnableRequest`], [`PwmDisableRequest`], [`PwmSetConfigurationRequest`],
37//!   [`PwmGetConfigurationRequest`], [`PwmDutyCycleInfo`], [`PwmConfigurationInfo`],
38//!   and their shared error type [`PwmError`].
39//! - **ADC types**: [`AdcReadRequest`], [`AdcChannel`], [`AdcConfigurationInfo`],
40//!   and their shared error type [`AdcError`].
41//! - **1-Wire types**: [`OneWireReadRequest`], [`OneWireWriteRequest`],
42//!   [`OneWireWritePullupRequest`], and their shared error type [`OneWireError`].
43//! - **Batch types**: [`I2cBatchRequest`], [`I2cBatchError`], [`I2cBatchOp`],
44//!   [`SpiBatchRequest`], [`SpiBatchError`], [`SpiBatchOp`], encoding helpers
45//!   [`encode_i2c_batch_ops`], [`encode_spi_batch_ops`], and response-length
46//!   helpers [`i2c_batch_response_len`], [`spi_batch_response_len`].
47//! - **Configuration**: [`I2cSetConfigurationRequest`], [`SpiSetConfigurationRequest`],
48//!   [`GpioSetConfigurationRequest`], [`UartSetConfigurationRequest`],
49//!   [`PwmSetConfigurationRequest`],
50//!   [`I2cFrequency`], [`SpiPhase`], [`SpiPolarity`],
51//!   [`GpioDirection`], [`GpioPull`], [`SpiConfigurationInfo`],
52//!   [`UartConfigurationInfo`].
53//! - **Version**: [`VersionInfo`].
54//! - **Device Info**: [`DeviceInfo`], [`Capabilities`].
55
56#![cfg_attr(not(feature = "use-std"), no_std)]
57
58use postcard_rpc::{TopicDirection, endpoints, topics};
59use postcard_schema::Schema;
60use serde::{Deserialize, Serialize};
61
62// Auto-generated schema version constants from Cargo.toml
63include!(concat!(env!("OUT_DIR"), "/schema_version.rs"));
64
65/// USB Vendor ID (Microsoft Corporation).
66pub const MICROSOFT_VID: u16 = 0x045e;
67
68/// USB Product ID assigned to Pico de Gallo.
69pub const PICO_DE_GALLO_PID: u16 = 0x067d;
70
71/// Maximum number of bytes the firmware can handle in a single I2C or SPI
72/// transaction. Requests exceeding this limit will be rejected by the
73/// firmware with an error.
74pub const MAX_TRANSFER_SIZE: usize = 4096;
75
76// ---
77
78/// Response type for I2C write operations.
79pub type I2cWriteResponse = Result<(), I2cError>;
80
81/// Response type for I2C read operations.
82/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
83#[cfg(feature = "use-std")]
84pub type I2cReadResponse<'a> = Result<Vec<u8>, I2cError>;
85/// Response type for I2C read operations.
86/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
87#[cfg(not(feature = "use-std"))]
88pub type I2cReadResponse<'a> = Result<&'a [u8], I2cError>;
89
90/// Response type for I2C write-read operations.
91/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
92#[cfg(feature = "use-std")]
93pub type I2cWriteReadResponse<'a> = Result<Vec<u8>, I2cError>;
94/// Response type for I2C write-read operations.
95/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
96#[cfg(not(feature = "use-std"))]
97pub type I2cWriteReadResponse<'a> = Result<&'a [u8], I2cError>;
98
99/// Response type for SPI write operations.
100pub type SpiWriteResponse = Result<(), SpiError>;
101
102/// Response type for SPI read operations.
103/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
104#[cfg(feature = "use-std")]
105pub type SpiReadResponse<'a> = Result<Vec<u8>, SpiError>;
106/// Response type for SPI read operations.
107/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
108#[cfg(not(feature = "use-std"))]
109pub type SpiReadResponse<'a> = Result<&'a [u8], SpiError>;
110
111/// Response type for SPI flush operations.
112pub type SpiFlushResponse = Result<(), SpiError>;
113
114/// Response type for SPI transfer operations.
115/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
116#[cfg(feature = "use-std")]
117pub type SpiTransferResponse<'a> = Result<Vec<u8>, SpiError>;
118/// Response type for SPI transfer operations.
119/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
120#[cfg(not(feature = "use-std"))]
121pub type SpiTransferResponse<'a> = Result<&'a [u8], SpiError>;
122
123/// Response type for GPIO get operations.
124pub type GpioGetResponse = Result<GpioState, GpioError>;
125/// Response type for GPIO put operations.
126pub type GpioPutResponse = Result<(), GpioError>;
127/// Response type for GPIO wait operations.
128pub type GpioWaitResponse = Result<(), GpioError>;
129/// Response type for GPIO set-configuration operations.
130pub type GpioSetConfigurationResponse = Result<(), GpioError>;
131/// Response type for GPIO subscribe operations.
132pub type GpioSubscribeResponse = Result<(), GpioError>;
133/// Response type for GPIO unsubscribe operations.
134pub type GpioUnsubscribeResponse = Result<(), GpioError>;
135/// Response type for I2C bus configuration operations.
136pub type I2cSetConfigurationResponse = Result<(), I2cConfigError>;
137/// Response type for I2C bus scan operations.
138/// On the host (`use-std`), returns `Vec<u8>` of responding addresses;
139/// on firmware, returns `&[u8]`.
140#[cfg(feature = "use-std")]
141pub type I2cScanResponse<'a> = Result<Vec<u8>, I2cError>;
142/// Response type for I2C bus scan operations.
143/// On the host (`use-std`), returns `Vec<u8>` of responding addresses;
144/// on firmware, returns `&[u8]`.
145#[cfg(not(feature = "use-std"))]
146pub type I2cScanResponse<'a> = Result<&'a [u8], I2cError>;
147/// Response type for SPI bus configuration operations.
148pub type SpiSetConfigurationResponse = Result<(), SpiConfigError>;
149
150/// Response type for I2C get-configuration queries.
151///
152/// Returns the currently active I2C bus frequency.
153pub type I2cGetConfigurationResponse = I2cFrequency;
154
155/// Response type for SPI get-configuration queries.
156///
157/// Returns the currently active SPI bus parameters.
158pub type SpiGetConfigurationResponse = SpiConfigurationInfo;
159
160/// Response type for UART write operations.
161pub type UartWriteResponse = Result<(), UartError>;
162
163/// Response type for UART read operations.
164/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
165#[cfg(feature = "use-std")]
166pub type UartReadResponse<'a> = Result<Vec<u8>, UartError>;
167/// Response type for UART read operations.
168/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
169#[cfg(not(feature = "use-std"))]
170pub type UartReadResponse<'a> = Result<&'a [u8], UartError>;
171
172/// Response type for UART flush operations.
173pub type UartFlushResponse = Result<(), UartError>;
174
175/// Response type for UART bus configuration operations.
176pub type UartSetConfigurationResponse = Result<(), UartConfigError>;
177
178/// Response type for UART get-configuration queries.
179///
180/// Returns the currently active UART parameters.
181pub type UartGetConfigurationResponse = Result<UartConfigurationInfo, UartError>;
182
183/// Response type for PWM set-duty-cycle operations.
184pub type PwmSetDutyCycleResponse = Result<(), PwmError>;
185/// Response type for PWM get-duty-cycle queries.
186pub type PwmGetDutyCycleResponse = Result<PwmDutyCycleInfo, PwmError>;
187/// Response type for PWM enable operations.
188pub type PwmEnableResponse = Result<(), PwmError>;
189/// Response type for PWM disable operations.
190pub type PwmDisableResponse = Result<(), PwmError>;
191/// Response type for PWM set-configuration operations.
192pub type PwmSetConfigurationResponse = Result<(), PwmConfigError>;
193/// Response type for PWM get-configuration queries.
194pub type PwmGetConfigurationResponse = Result<PwmConfigurationInfo, PwmError>;
195
196/// Response type for ADC read operations.
197pub type AdcReadResponse = Result<u16, AdcError>;
198/// Response type for ADC get-configuration queries.
199pub type AdcGetConfigurationResponse = Result<AdcConfigurationInfo, AdcError>;
200
201/// Response type for 1-Wire reset operations.
202/// Returns `true` if at least one device is present on the bus.
203pub type OneWireResetResponse = Result<bool, OneWireError>;
204
205/// Response type for 1-Wire read operations.
206/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
207#[cfg(feature = "use-std")]
208pub type OneWireReadResponse<'a> = Result<Vec<u8>, OneWireError>;
209/// Response type for 1-Wire read operations.
210/// On the host (`use-std`), returns `Vec<u8>`; on firmware, returns `&[u8]`.
211#[cfg(not(feature = "use-std"))]
212pub type OneWireReadResponse<'a> = Result<&'a [u8], OneWireError>;
213
214/// Response type for 1-Wire write operations.
215pub type OneWireWriteResponse = Result<(), OneWireError>;
216
217/// Response type for 1-Wire write-with-pullup operations.
218pub type OneWireWritePullupResponse = Result<(), OneWireError>;
219
220/// Response type for 1-Wire ROM search operations.
221/// Returns `Some(rom_id)` for the next device found, or `None` if the
222/// search is complete.
223pub type OneWireSearchResponse = Result<Option<u64>, OneWireError>;
224
225/// Response type for I2C batch transaction operations.
226/// On the host (`use-std`), returns `Vec<u8>` of concatenated read data;
227/// on firmware, returns `&[u8]`.
228#[cfg(feature = "use-std")]
229pub type I2cBatchResponse<'a> = Result<Vec<u8>, I2cBatchError>;
230/// Response type for I2C batch transaction operations.
231/// On the host (`use-std`), returns `Vec<u8>` of concatenated read data;
232/// on firmware, returns `&[u8]`.
233#[cfg(not(feature = "use-std"))]
234pub type I2cBatchResponse<'a> = Result<&'a [u8], I2cBatchError>;
235
236/// Response type for SPI batch transaction operations.
237/// On the host (`use-std`), returns `Vec<u8>` of concatenated read/transfer data;
238/// on firmware, returns `&[u8]`.
239#[cfg(feature = "use-std")]
240pub type SpiBatchResponse<'a> = Result<Vec<u8>, SpiBatchError>;
241/// Response type for SPI batch transaction operations.
242/// On the host (`use-std`), returns `Vec<u8>` of concatenated read/transfer data;
243/// on firmware, returns `&[u8]`.
244#[cfg(not(feature = "use-std"))]
245pub type SpiBatchResponse<'a> = Result<&'a [u8], SpiBatchError>;
246
247endpoints! {
248    list = ENDPOINT_LIST;
249    | EndpointTy          | RequestTy                  | ResponseTy                  | Path                |
250    | ----------          | ---------                  | ----------                  | ----                |
251    | PingEndpoint        | u32                        | u32                         | "ping"              |
252    | I2cRead             | I2cReadRequest             | I2cReadResponse<'a>         | "i2c/read"          |
253    | I2cWrite            | I2cWriteRequest<'a>        | I2cWriteResponse            | "i2c/write"         |
254    | I2cWriteRead        | I2cWriteReadRequest<'a>    | I2cWriteReadResponse<'b>    | "i2c/write-read"    |
255    | SpiRead             | SpiReadRequest             | SpiReadResponse<'a>         | "spi/read"          |
256    | SpiWrite            | SpiWriteRequest<'a>        | SpiWriteResponse            | "spi/write"         |
257    | SpiFlush            | ()                         | SpiFlushResponse            | "spi/flush"         |
258    | SpiTransfer         | SpiTransferRequest<'a>     | SpiTransferResponse<'b>     | "spi/transfer"      |
259    | GpioGet             | GpioGetRequest             | GpioGetResponse             | "gpio/get"          |
260    | GpioPut             | GpioPutRequest             | GpioPutResponse             | "gpio/put"          |
261    | GpioWaitForHigh     | GpioWaitRequest            | GpioWaitResponse            | "gpio/wait-high"    |
262    | GpioWaitForLow      | GpioWaitRequest            | GpioWaitResponse            | "gpio/wait-low"     |
263    | GpioWaitForRising   | GpioWaitRequest            | GpioWaitResponse            | "gpio/wait-rising"  |
264    | GpioWaitForFalling  | GpioWaitRequest            | GpioWaitResponse            | "gpio/wait-falling" |
265    | GpioWaitForAny      | GpioWaitRequest            | GpioWaitResponse            | "gpio/wait-any"     |
266    | I2cSetConfiguration | I2cSetConfigurationRequest | I2cSetConfigurationResponse | "i2c/set-config"    |
267    | I2cScan             | I2cScanRequest             | I2cScanResponse<'a>         | "i2c/scan"          |
268    | SpiSetConfiguration  | SpiSetConfigurationRequest  | SpiSetConfigurationResponse  | "spi/set-config"    |
269    | GpioSetConfiguration | GpioSetConfigurationRequest | GpioSetConfigurationResponse | "gpio/set-config"   |
270    | I2cGetConfiguration  | ()                          | I2cGetConfigurationResponse  | "i2c/get-config"    |
271    | SpiGetConfiguration  | ()                          | SpiGetConfigurationResponse  | "spi/get-config"    |
272    | UartRead             | UartReadRequest             | UartReadResponse<'a>         | "uart/read"         |
273    | UartWrite            | UartWriteRequest<'a>        | UartWriteResponse            | "uart/write"        |
274    | UartFlush            | ()                          | UartFlushResponse            | "uart/flush"        |
275    | UartSetConfiguration | UartSetConfigurationRequest | UartSetConfigurationResponse | "uart/set-config"   |
276    | UartGetConfiguration  | ()                            | UartGetConfigurationResponse  | "uart/get-config"    |
277    | PwmSetDutyCycle       | PwmSetDutyCycleRequest        | PwmSetDutyCycleResponse       | "pwm/set-duty-cycle" |
278    | PwmGetDutyCycle       | PwmGetDutyCycleRequest        | PwmGetDutyCycleResponse       | "pwm/get-duty-cycle" |
279    | PwmEnable             | PwmEnableRequest              | PwmEnableResponse             | "pwm/enable"         |
280    | PwmDisable            | PwmDisableRequest             | PwmDisableResponse            | "pwm/disable"        |
281    | PwmSetConfiguration   | PwmSetConfigurationRequest    | PwmSetConfigurationResponse   | "pwm/set-config"     |
282    | PwmGetConfiguration   | PwmGetConfigurationRequest    | PwmGetConfigurationResponse   | "pwm/get-config"     |
283    | AdcRead               | AdcReadRequest                | AdcReadResponse               | "adc/read"           |
284    | AdcGetConfiguration   | ()                            | AdcGetConfigurationResponse   | "adc/get-config"     |
285    | GpioSubscribe         | GpioSubscribeRequest          | GpioSubscribeResponse         | "gpio/subscribe"     |
286    | GpioUnsubscribe       | GpioUnsubscribeRequest        | GpioUnsubscribeResponse       | "gpio/unsubscribe"   |
287    | I2cBatch              | I2cBatchRequest<'a>           | I2cBatchResponse<'b>          | "i2c/batch"          |
288    | SpiBatch              | SpiBatchRequest<'a>           | SpiBatchResponse<'b>          | "spi/batch"          |
289    | OneWireReset          | ()                            | OneWireResetResponse          | "onewire/reset"      |
290    | OneWireRead           | OneWireReadRequest            | OneWireReadResponse<'a>       | "onewire/read"       |
291    | OneWireWrite          | OneWireWriteRequest<'a>       | OneWireWriteResponse          | "onewire/write"      |
292    | OneWireWritePullup    | OneWireWritePullupRequest<'a> | OneWireWritePullupResponse    | "onewire/write-pullup" |
293    | OneWireSearch         | ()                            | OneWireSearchResponse         | "onewire/search"     |
294    | OneWireSearchNext     | ()                            | OneWireSearchResponse         | "onewire/search-next" |
295    | Version               | ()                            | VersionInfo                   | "version"            |
296    | GetDeviceInfo         | ()                            | DeviceInfo                    | "device/info"        |
297}
298
299topics! {
300    list = TOPICS_IN_LIST;
301    direction = TopicDirection::ToServer;
302    | TopicTy | MessageTy | Path |
303    | ------- | --------- | ---- |
304}
305
306topics! {
307    list = TOPICS_OUT_LIST;
308    direction = TopicDirection::ToClient;
309    | TopicTy         | MessageTy  | Path              | Cfg |
310    | -------         | --------- | ----               | --- |
311    | GpioEventTopic    | GpioEvent   | "gpio/event"    |     |
312}
313
314// --- I2C
315
316/// Request to write bytes to an I2C device, then read back.
317///
318/// The firmware performs a write followed by a repeated-start read in a
319/// single I2C transaction, which is the standard pattern for reading
320/// registers from most I2C devices.
321#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
322pub struct I2cWriteReadRequest<'a> {
323    /// 7-bit I2C slave address.
324    pub address: u8,
325    /// Bytes to write (typically a register address).
326    pub contents: &'a [u8],
327    /// Number of bytes to read back (max [`MAX_TRANSFER_SIZE`]).
328    pub count: u16,
329}
330
331/// Error from I2C operations, propagated from firmware.
332///
333/// Maps directly to [`embedded_hal::i2c::ErrorKind`] variants on the host side.
334///
335/// # Wire Compatibility
336///
337/// Variants are serialized by **index** (0, 1, 2, …). Do **not** reorder,
338/// rename, or insert variants in the middle — only append new variants at
339/// the end. Removing or reordering is a breaking wire change.
340#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
341pub enum I2cError {
342    /// Bus error (unexpected condition on the I2C bus).
343    Bus,
344    /// No acknowledge received from the target device.
345    NoAcknowledge,
346    /// Arbitration lost to another controller on the bus.
347    ArbitrationLoss,
348    /// Data overrun — firmware could not keep up with the bus clock.
349    Overrun,
350    /// Request exceeds the firmware buffer limit ([`MAX_TRANSFER_SIZE`]).
351    BufferTooLong,
352    /// I2C address is outside the valid 7-bit range (0x00–0x7F).
353    AddressOutOfRange,
354    /// An unspecified error occurred in the firmware.
355    Other,
356}
357
358impl core::fmt::Display for I2cError {
359    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
360        match self {
361            Self::Bus => write!(f, "I2C bus error"),
362            Self::NoAcknowledge => write!(f, "no acknowledge from target"),
363            Self::ArbitrationLoss => write!(f, "I2C arbitration loss"),
364            Self::Overrun => write!(f, "I2C data overrun"),
365            Self::BufferTooLong => write!(f, "buffer exceeds firmware limit"),
366            Self::AddressOutOfRange => write!(f, "I2C address out of range"),
367            Self::Other => write!(f, "I2C error"),
368        }
369    }
370}
371
372/// Request to read bytes from an I2C device.
373#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
374pub struct I2cReadRequest {
375    /// 7-bit I2C slave address.
376    pub address: u8,
377    /// Number of bytes to read (max [`MAX_TRANSFER_SIZE`]).
378    pub count: u16,
379}
380
381/// Request to write bytes to an I2C device.
382#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
383pub struct I2cWriteRequest<'a> {
384    /// 7-bit I2C slave address.
385    pub address: u8,
386    /// Bytes to write.
387    pub contents: &'a [u8],
388}
389
390/// Request to scan the I2C bus for responding devices.
391///
392/// The firmware probes addresses by attempting a 1-byte read at each
393/// 7-bit address. Addresses that ACK are included in the response.
394#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
395pub struct I2cScanRequest {
396    /// When `true`, also probe reserved addresses (0x00–0x07 and 0x78–0x7F).
397    /// When `false`, only probe the standard range 0x08–0x77.
398    pub include_reserved: bool,
399}
400
401// --- SPI
402
403/// Error from SPI operations, propagated from firmware.
404///
405/// # Wire Compatibility
406///
407/// Variants are serialized by **index**. Do **not** reorder or insert
408/// variants in the middle — only append at the end.
409#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
410pub enum SpiError {
411    /// Request exceeds the firmware buffer limit ([`MAX_TRANSFER_SIZE`]).
412    BufferTooLong,
413    /// An unspecified error occurred in the firmware.
414    Other,
415}
416
417impl core::fmt::Display for SpiError {
418    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
419        match self {
420            Self::BufferTooLong => write!(f, "buffer exceeds firmware limit"),
421            Self::Other => write!(f, "SPI error"),
422        }
423    }
424}
425
426/// Request to read bytes from the SPI bus.
427#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
428pub struct SpiReadRequest {
429    /// Number of bytes to read (max [`MAX_TRANSFER_SIZE`]).
430    pub count: u16,
431}
432
433/// Request to write bytes to the SPI bus.
434#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
435pub struct SpiWriteRequest<'a> {
436    /// Bytes to write.
437    pub contents: &'a [u8],
438}
439
440/// Request for a full-duplex SPI transfer.
441///
442/// The firmware simultaneously transmits `contents` and receives the same
443/// number of bytes. This is a true full-duplex operation using DMA.
444#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
445pub struct SpiTransferRequest<'a> {
446    /// Bytes to transmit. The response will contain the same number of received bytes.
447    pub contents: &'a [u8],
448}
449
450/// Error returned when an SPI transfer operation fails.
451///
452/// This is a convenience alias — SPI transfers share the same error type
453/// as other SPI operations.
454pub type SpiTransferError = SpiError;
455
456// --- GPIO
457
458/// Request to read the current level of a GPIO pin.
459#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
460pub struct GpioGetRequest {
461    /// GPIO pin index (0–7).
462    pub pin: u8,
463}
464
465/// Error from GPIO operations, propagated from firmware.
466///
467/// # Wire Compatibility
468///
469/// Variants are serialized by **index**. Do **not** reorder or insert
470/// variants in the middle — only append at the end.
471#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
472pub enum GpioError {
473    /// The requested pin number is invalid (outside 0–7 range).
474    InvalidPin,
475    /// An unspecified error occurred in the firmware.
476    Other,
477    /// The pin is configured in a direction that does not support this operation.
478    WrongDirection,
479    /// The pin is currently being monitored for events and cannot be used
480    /// for regular GPIO operations. Unsubscribe first.
481    PinMonitored,
482    /// The pin is not currently monitored — cannot unsubscribe.
483    PinNotMonitored,
484}
485
486impl core::fmt::Display for GpioError {
487    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
488        match self {
489            Self::InvalidPin => write!(f, "invalid GPIO pin number"),
490            Self::Other => write!(f, "GPIO error"),
491            Self::WrongDirection => write!(f, "GPIO pin configured in wrong direction"),
492            Self::PinMonitored => write!(f, "GPIO pin is being monitored for events"),
493            Self::PinNotMonitored => write!(f, "GPIO pin is not monitored"),
494        }
495    }
496}
497
498/// Request to set a GPIO pin to a specific level.
499#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
500pub struct GpioPutRequest {
501    /// GPIO pin index (0–7).
502    pub pin: u8,
503    /// Desired output level.
504    pub state: GpioState,
505}
506
507/// Logic level of a GPIO pin.
508//
509// WARNING: Do not reorder enum variants — postcard serializes by
510// variant index, not by discriminant. Reordering breaks wire compat.
511#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq, Hash)]
512pub enum GpioState {
513    /// Logic low (0V).
514    Low,
515    /// Logic high (3.3V on RP2350).
516    High,
517}
518
519impl From<bool> for GpioState {
520    fn from(value: bool) -> Self {
521        if value {
522            GpioState::High
523        } else {
524            GpioState::Low
525        }
526    }
527}
528
529impl From<GpioState> for bool {
530    fn from(state: GpioState) -> Self {
531        matches!(state, GpioState::High)
532    }
533}
534
535/// Request to wait for a GPIO pin to reach a specific state or edge.
536#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
537pub struct GpioWaitRequest {
538    /// GPIO pin index (0–7).
539    pub pin: u8,
540}
541
542/// GPIO pin direction.
543//
544// WARNING: Do not reorder enum variants — postcard serializes by
545// variant index, not by discriminant. Reordering breaks wire compat.
546#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq, Hash)]
547#[repr(u8)]
548pub enum GpioDirection {
549    /// Configure the pin as a digital input.
550    Input = 0,
551    /// Configure the pin as a digital output.
552    Output = 1,
553}
554
555/// GPIO internal pull resistor configuration.
556//
557// WARNING: Do not reorder enum variants — postcard serializes by
558// variant index, not by discriminant. Reordering breaks wire compat.
559#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq, Hash)]
560#[repr(u8)]
561pub enum GpioPull {
562    /// No internal pull resistor.
563    None = 0,
564    /// Internal pull-up resistor enabled.
565    Up = 1,
566    /// Internal pull-down resistor enabled.
567    Down = 2,
568}
569
570/// Request to configure a GPIO pin's direction and pull resistor.
571///
572/// After configuration, the pin retains its explicit mode until the
573/// firmware is reset. See [`GpioDirection`] and [`GpioPull`] for
574/// available options.
575#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
576pub struct GpioSetConfigurationRequest {
577    /// GPIO pin index (0–7).
578    pub pin: u8,
579    /// Desired pin direction.
580    pub direction: GpioDirection,
581    /// Internal pull resistor setting.
582    pub pull: GpioPull,
583}
584
585// --- GPIO event monitoring
586
587/// Edge type for GPIO event monitoring.
588///
589/// Selects which transitions trigger a [`GpioEvent`] notification.
590///
591/// # Wire Compatibility
592///
593/// Variants are serialized by **index**. Do **not** reorder or insert
594/// variants in the middle — only append at the end.
595#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq, Hash)]
596#[repr(u8)]
597pub enum GpioEdge {
598    /// Trigger on low-to-high transitions.
599    Rising = 0,
600    /// Trigger on high-to-low transitions.
601    Falling = 1,
602    /// Trigger on any transition (rising or falling).
603    Any = 2,
604}
605
606impl core::fmt::Display for GpioEdge {
607    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
608        match self {
609            Self::Rising => write!(f, "rising"),
610            Self::Falling => write!(f, "falling"),
611            Self::Any => write!(f, "any"),
612        }
613    }
614}
615
616/// A GPIO event notification published by the firmware.
617///
618/// Sent as a [`GpioEventTopic`] message whenever a monitored pin detects
619/// an edge matching its subscription. The host receives these via
620/// [`postcard_rpc::host_client::HostClient::subscribe`].
621///
622/// **Note:** Event delivery is best-effort. Edges faster than the
623/// firmware's monitor loop may be coalesced or missed.
624#[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
625pub struct GpioEvent {
626    /// GPIO pin index (0–3) that triggered the event.
627    pub pin: u8,
628    /// The edge type that was detected.
629    pub edge: GpioEdge,
630    /// Pin level sampled immediately after the event (may differ from
631    /// the triggering edge if the signal bounced).
632    pub state: GpioState,
633    /// Monotonic timestamp in microseconds since firmware boot.
634    pub timestamp_us: u64,
635}
636
637/// Request to subscribe a GPIO pin to edge-event monitoring.
638///
639/// Once subscribed, the pin is exclusively owned by the monitor task.
640/// Regular GPIO operations ([`GpioGet`], [`GpioPut`], wait, set-config)
641/// on this pin will return [`GpioError::PinMonitored`] until
642/// [`GpioUnsubscribe`] is called.
643#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
644pub struct GpioSubscribeRequest {
645    /// GPIO pin index (0–3).
646    pub pin: u8,
647    /// Which edges to monitor.
648    pub edge: GpioEdge,
649}
650
651/// Request to unsubscribe a GPIO pin from edge-event monitoring.
652///
653/// The pin is returned to normal GPIO mode and can be used for regular
654/// operations again.
655#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
656pub struct GpioUnsubscribeRequest {
657    /// GPIO pin index (0–3).
658    pub pin: u8,
659}
660
661// --- Set config
662
663/// Request to reconfigure I2C bus parameters.
664///
665/// Takes effect immediately. The firmware applies the new frequency before
666/// processing the next I2C operation.
667#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
668pub struct I2cSetConfigurationRequest {
669    /// I2C bus clock frequency.
670    pub frequency: I2cFrequency,
671}
672
673// WARNING: do not reorder variants — postcard encodes by index, not discriminant.
674/// I2C bus clock frequency.
675///
676/// The RP2350 supports Standard (100 kHz), Fast (400 kHz), and Fast+ (1 MHz)
677/// modes. Ultra-Fast mode is defined by the specification but not supported by
678/// the RP2350 hardware.
679#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
680#[repr(u8)]
681pub enum I2cFrequency {
682    /// Standard mode — 100 kHz.
683    Standard = 0,
684    /// Fast mode — 400 kHz.
685    Fast = 1,
686    /// Fast+ mode — 1 MHz.
687    FastPlus = 2,
688}
689
690/// Error returned when I2C configuration fails.
691///
692/// This is a convenience alias — I2C configuration shares the same error
693/// type as other I2C operations.
694pub type I2cConfigError = I2cError;
695
696/// Request to reconfigure SPI bus parameters.
697///
698/// Takes effect immediately. The firmware applies the new settings before
699/// processing the next SPI operation.
700#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
701pub struct SpiSetConfigurationRequest {
702    /// SPI bus clock frequency in Hz.
703    pub spi_frequency: u32,
704    /// SPI clock phase.
705    pub spi_phase: SpiPhase,
706    /// SPI clock polarity.
707    pub spi_polarity: SpiPolarity,
708}
709
710/// SPI clock phase setting.
711//
712// WARNING: Do not reorder enum variants — postcard serializes by
713// variant index, not by discriminant. Reordering breaks wire compat.
714#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq, Hash)]
715pub enum SpiPhase {
716    /// Data captured on the leading (first) clock edge.
717    CaptureOnFirstTransition = 0,
718    /// Data captured on the trailing (second) clock edge.
719    CaptureOnSecondTransition = 1,
720}
721
722/// SPI clock polarity setting.
723//
724// WARNING: Do not reorder enum variants — postcard serializes by
725// variant index, not by discriminant. Reordering breaks wire compat.
726#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq, Hash)]
727pub enum SpiPolarity {
728    /// Clock idles at logic low (CPOL=0).
729    IdleLow = 0,
730    /// Clock idles at logic high (CPOL=1).
731    IdleHigh = 1,
732}
733
734/// Error returned when SPI configuration fails.
735///
736/// This is a convenience alias — SPI configuration shares the same error
737/// type as other SPI operations.
738pub type SpiConfigError = SpiError;
739
740// --- UART
741
742/// Error from UART operations, propagated from firmware.
743///
744/// # Wire Compatibility
745///
746/// Variants are serialized by **index**. Do **not** reorder or insert
747/// variants in the middle — only append at the end.
748#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
749pub enum UartError {
750    /// Request exceeds the firmware buffer limit ([`MAX_TRANSFER_SIZE`]).
751    BufferTooLong,
752    /// UART receiver FIFO overrun — data arrived faster than the firmware
753    /// could process it.
754    Overrun,
755    /// A break condition was detected on the UART RX line.
756    Break,
757    /// Parity mismatch between received data and configured parity setting.
758    Parity,
759    /// The received character did not have a valid stop bit.
760    Framing,
761    /// The requested baud rate is invalid (zero or unsupported by hardware).
762    InvalidBaudRate,
763    /// An unspecified error occurred in the firmware.
764    Other,
765    /// The peripheral is not available on this hardware revision.
766    // WARNING: Do not reorder — postcard encodes by variant index.
767    Unsupported,
768}
769
770impl core::fmt::Display for UartError {
771    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
772        match self {
773            Self::BufferTooLong => write!(f, "buffer exceeds firmware limit"),
774            Self::Overrun => write!(f, "UART receiver overrun"),
775            Self::Break => write!(f, "UART break condition"),
776            Self::Parity => write!(f, "UART parity error"),
777            Self::Framing => write!(f, "UART framing error"),
778            Self::InvalidBaudRate => write!(f, "invalid baud rate"),
779            Self::Other => write!(f, "UART error"),
780            Self::Unsupported => write!(f, "UART not supported on this hardware"),
781        }
782    }
783}
784
785/// Request to read bytes from the UART bus.
786///
787/// The firmware reads up to `count` bytes from the UART receive buffer.
788/// If no data is immediately available, the firmware waits up to
789/// `timeout_ms` milliseconds for at least one byte. Returns whatever
790/// bytes are available (1 to `count`), or an empty result on timeout.
791#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
792pub struct UartReadRequest {
793    /// Maximum number of bytes to read (max [`MAX_TRANSFER_SIZE`]).
794    pub count: u16,
795    /// Maximum time to wait for data, in milliseconds.
796    /// Use 0 for a non-blocking poll (return only already-buffered data).
797    pub timeout_ms: u32,
798}
799
800/// Request to write bytes to the UART bus.
801#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
802pub struct UartWriteRequest<'a> {
803    /// Bytes to write.
804    pub contents: &'a [u8],
805}
806
807/// Request to reconfigure UART bus parameters.
808///
809/// Takes effect immediately. The firmware applies the new baud rate before
810/// processing the next UART operation.
811///
812/// **Note:** In v1, only `baud_rate` is configurable at runtime. Data bits,
813/// parity, and stop bits are set to 8N1 at boot and cannot be changed
814/// dynamically. These fields are reserved for future use and must be set
815/// to their default values (`Eight`, `None`, `One`).
816#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
817pub struct UartSetConfigurationRequest {
818    /// UART baud rate in bits per second.
819    pub baud_rate: u32,
820}
821
822/// Error returned when UART configuration fails.
823///
824/// This is a convenience alias — UART configuration shares the same error
825/// type as other UART operations.
826pub type UartConfigError = UartError;
827
828/// Current UART bus configuration as reported by the firmware.
829///
830/// Returned by `uart/get-config`. Reflects the last successfully applied
831/// configuration.
832#[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
833pub struct UartConfigurationInfo {
834    /// UART baud rate in bits per second.
835    pub baud_rate: u32,
836}
837
838/// Current SPI bus configuration as reported by the firmware.
839///
840/// Returned by `spi/get-config`. The field names mirror
841/// [`SpiSetConfigurationRequest`] for consistency.
842#[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
843pub struct SpiConfigurationInfo {
844    /// SPI bus clock frequency in Hz.
845    pub spi_frequency: u32,
846    /// SPI clock phase.
847    pub spi_phase: SpiPhase,
848    /// SPI clock polarity.
849    pub spi_polarity: SpiPolarity,
850}
851
852// --- PWM
853
854/// Number of PWM output channels exposed by the firmware.
855///
856/// Channels 0–3 map to physical pins GPIO12–GPIO15 on the Pico 2 header.
857/// Channels 0–1 share PWM slice 6; channels 2–3 share PWM slice 7.
858pub const NUM_PWM_CHANNELS: usize = 4;
859
860/// Error from PWM operations, propagated from firmware.
861///
862/// # Wire Compatibility
863///
864/// Variants are serialized by **index**. Do **not** reorder or insert
865/// variants in the middle — only append at the end.
866#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
867pub enum PwmError {
868    /// The requested channel index exceeds the available PWM channels
869    /// ([`NUM_PWM_CHANNELS`]).
870    InvalidChannel,
871    /// The requested duty cycle exceeds the maximum for the current
872    /// configuration (i.e., `duty > top`).
873    InvalidDutyCycle,
874    /// The requested configuration is invalid (e.g., zero frequency or
875    /// unsupported divider value).
876    InvalidConfiguration,
877    /// An unspecified error occurred in the firmware.
878    Other,
879}
880
881impl core::fmt::Display for PwmError {
882    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
883        match self {
884            Self::InvalidChannel => write!(f, "invalid PWM channel"),
885            Self::InvalidDutyCycle => write!(f, "duty cycle exceeds maximum"),
886            Self::InvalidConfiguration => write!(f, "invalid PWM configuration"),
887            Self::Other => write!(f, "PWM error"),
888        }
889    }
890}
891
892/// Request to set the duty cycle of a PWM channel.
893///
894/// The `duty` value is a raw compare value (0 to `top`). Use
895/// [`PwmGetDutyCycle`] to query the current `max_duty` (top) before
896/// computing a duty cycle from a percentage.
897#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
898pub struct PwmSetDutyCycleRequest {
899    /// PWM channel index (0–3).
900    pub channel: u8,
901    /// Raw duty cycle value (0 to top).
902    pub duty: u16,
903}
904
905/// Request to query the current duty cycle of a PWM channel.
906#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
907pub struct PwmGetDutyCycleRequest {
908    /// PWM channel index (0–3).
909    pub channel: u8,
910}
911
912/// Information about a PWM channel's current duty cycle.
913///
914/// The `max_duty` field corresponds to the slice's `top` register value.
915/// The `current_duty` field is the raw compare value for the channel.
916#[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
917pub struct PwmDutyCycleInfo {
918    /// Maximum duty cycle value (the `top` value of the PWM slice).
919    pub max_duty: u16,
920    /// Current duty cycle value (raw compare register).
921    pub current_duty: u16,
922}
923
924/// Request to enable a PWM channel's slice.
925///
926/// **Note:** Channels 0–1 share a slice and channels 2–3 share a slice.
927/// Enabling one channel enables the entire slice (both channels).
928#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
929pub struct PwmEnableRequest {
930    /// PWM channel index (0–3). The parent slice is enabled.
931    pub channel: u8,
932}
933
934/// Request to disable a PWM channel's slice.
935///
936/// **Note:** Channels 0–1 share a slice and channels 2–3 share a slice.
937/// Disabling one channel disables the entire slice (both channels).
938#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
939pub struct PwmDisableRequest {
940    /// PWM channel index (0–3). The parent slice is disabled.
941    pub channel: u8,
942}
943
944/// Request to reconfigure the PWM slice behind a channel.
945///
946/// Sets the output frequency and phase-correct mode. The firmware
947/// computes `top` and `divider` from the requested `frequency_hz`.
948///
949/// **Note:** Channels 0–1 share a slice and channels 2–3 share a slice.
950/// Configuring one channel reconfigures the entire slice.
951#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
952pub struct PwmSetConfigurationRequest {
953    /// PWM channel index (0–3). Identifies the target slice.
954    pub channel: u8,
955    /// Desired PWM output frequency in Hz.
956    pub frequency_hz: u32,
957    /// Enable phase-correct mode. When `true`, the output frequency is
958    /// halved and the pulse is centered.
959    pub phase_correct: bool,
960}
961
962/// Request to query the current configuration of a PWM channel's slice.
963#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
964pub struct PwmGetConfigurationRequest {
965    /// PWM channel index (0–3).
966    pub channel: u8,
967}
968
969/// Error returned when PWM configuration fails.
970///
971/// This is a convenience alias — PWM configuration shares the same error
972/// type as other PWM operations.
973pub type PwmConfigError = PwmError;
974
975/// Current PWM slice configuration as reported by the firmware.
976///
977/// Returned by `pwm/get-config`. Reflects the last successfully applied
978/// configuration.
979#[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
980pub struct PwmConfigurationInfo {
981    /// Actual PWM output frequency in Hz (may differ slightly from
982    /// the requested value due to divider/top quantization).
983    pub frequency_hz: u32,
984    /// Whether phase-correct mode is active.
985    pub phase_correct: bool,
986    /// Whether the slice is currently enabled.
987    pub enabled: bool,
988}
989
990// --- ADC (Analog-to-Digital Converter)
991
992/// Number of external (GPIO-based) ADC channels exposed by the firmware.
993///
994/// Channels [`AdcChannel::Adc0`] through [`AdcChannel::Adc3`] map to physical
995/// pins GPIO26–GPIO29 on the Pico 2 header.
996pub const NUM_ADC_GPIO_CHANNELS: usize = 4;
997
998/// ADC resolution in bits.
999///
1000/// The RP2350 has a 12-bit SAR ADC; raw readings are in the range 0–4095.
1001pub const ADC_RESOLUTION_BITS: u8 = 12;
1002
1003/// Nominal ADC reference voltage in millivolts.
1004///
1005/// On RP2350, ADC readings are referenced to ADC_AVDD (nominally 3.3 V).
1006/// This is **not** a precision reference — actual voltage may vary with
1007/// supply quality.
1008pub const ADC_NOMINAL_REFERENCE_MV: u16 = 3300;
1009
1010/// ADC channel selector.
1011///
1012/// Identifies which ADC input to sample. GPIO-based channels (`Adc0`–`Adc3`)
1013/// read external analog voltages on GPIO26–GPIO29.
1014///
1015/// # Wire Compatibility
1016///
1017/// Variants are serialized as discriminant integers (0–3). **Do not**
1018/// reorder or insert variants before existing ones — that changes the
1019/// wire encoding and breaks backward compatibility.
1020#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
1021pub enum AdcChannel {
1022    /// ADC channel 0 — GPIO26.
1023    Adc0 = 0,
1024    /// ADC channel 1 — GPIO27.
1025    Adc1 = 1,
1026    /// ADC channel 2 — GPIO28.
1027    Adc2 = 2,
1028    /// ADC channel 3 — GPIO29.
1029    Adc3 = 3,
1030}
1031
1032impl core::fmt::Display for AdcChannel {
1033    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1034        match self {
1035            Self::Adc0 => write!(f, "ADC0 (GPIO26)"),
1036            Self::Adc1 => write!(f, "ADC1 (GPIO27)"),
1037            Self::Adc2 => write!(f, "ADC2 (GPIO28)"),
1038            Self::Adc3 => write!(f, "ADC3 (GPIO29)"),
1039        }
1040    }
1041}
1042
1043/// Error from ADC operations, propagated from firmware.
1044///
1045/// # Wire Compatibility
1046///
1047/// Variants are serialized as discriminant integers. **Do not** reorder or
1048/// insert variants before existing ones — that changes the wire encoding
1049/// and breaks backward compatibility.
1050#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
1051pub enum AdcError {
1052    /// The ADC hardware signaled a conversion error.
1053    ConversionFailed,
1054    /// Catch-all for unexpected ADC errors.
1055    Other,
1056    /// The peripheral is not available on this hardware revision.
1057    // WARNING: Do not reorder — postcard encodes by variant index.
1058    Unsupported,
1059}
1060
1061impl core::fmt::Display for AdcError {
1062    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1063        match self {
1064            Self::ConversionFailed => write!(f, "ADC conversion failed"),
1065            Self::Other => write!(f, "ADC error"),
1066            Self::Unsupported => write!(f, "ADC not supported on this hardware"),
1067        }
1068    }
1069}
1070
1071/// Error returned when ADC configuration fails.
1072///
1073/// This is a convenience alias — ADC configuration shares the same error
1074/// type as other ADC operations.
1075pub type AdcConfigError = AdcError;
1076
1077/// Request to perform a single-shot ADC read on a specific channel.
1078///
1079/// Returns a raw 12-bit value (0–4095). The host can convert to voltage
1080/// using `V = raw * nominal_reference_mv / 4096`.
1081#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
1082pub struct AdcReadRequest {
1083    /// Which ADC channel to sample.
1084    pub channel: AdcChannel,
1085}
1086
1087/// Current ADC configuration as reported by the firmware.
1088///
1089/// Returned by `adc/get-config`. Values are fixed for the RP2350 ADC
1090/// but exposed for host discovery and consistency with other peripherals.
1091#[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
1092pub struct AdcConfigurationInfo {
1093    /// ADC resolution in bits (always 12 for RP2350).
1094    pub resolution_bits: u8,
1095    /// Nominal ADC reference voltage in millivolts (typically 3300).
1096    ///
1097    /// **Note:** This is the nominal ADC_AVDD voltage, not a precision
1098    /// reference. Actual voltage may vary.
1099    pub nominal_reference_mv: u16,
1100    /// Number of external (GPIO-based) ADC channels.
1101    pub num_gpio_channels: u8,
1102}
1103
1104// --- 1-Wire
1105
1106/// Error from 1-Wire operations, propagated from firmware.
1107///
1108/// # Wire Compatibility
1109///
1110/// Variants are serialized by **index** (0, 1, 2, …). Do **not** reorder,
1111/// rename, or remove existing variants — only append new ones at the end.
1112#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
1113pub enum OneWireError {
1114    /// No device responded to reset (no presence pulse detected).
1115    NoPresence,
1116    /// Bus communication error (short circuit, stuck bus, etc.).
1117    BusError,
1118    /// Requested transfer exceeds [`MAX_TRANSFER_SIZE`].
1119    BufferTooLong,
1120    /// Catch-all for unexpected 1-Wire errors.
1121    Other,
1122    /// The peripheral is not available on this hardware revision.
1123    // WARNING: Do not reorder — postcard encodes by variant index.
1124    Unsupported,
1125}
1126
1127impl core::fmt::Display for OneWireError {
1128    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1129        match self {
1130            Self::NoPresence => write!(f, "no device present on 1-Wire bus"),
1131            Self::BusError => write!(f, "1-Wire bus error"),
1132            Self::BufferTooLong => write!(f, "buffer exceeds firmware limit"),
1133            Self::Other => write!(f, "1-Wire error"),
1134            Self::Unsupported => write!(f, "1-Wire not supported on this hardware"),
1135        }
1136    }
1137}
1138
1139/// Request to read bytes from the 1-Wire bus.
1140///
1141/// The firmware reads `len` bytes from the bus after the host has already
1142/// issued the appropriate ROM and function commands via [`OneWireWrite`].
1143#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
1144pub struct OneWireReadRequest {
1145    /// Number of bytes to read (max [`MAX_TRANSFER_SIZE`]).
1146    pub len: u16,
1147}
1148
1149/// Request to write bytes to the 1-Wire bus.
1150///
1151/// Writes raw bytes (ROM commands, function commands, data) to the bus.
1152/// The host is responsible for issuing the correct 1-Wire command
1153/// sequences (e.g., Skip ROM `0xCC` + Convert T `0x44`).
1154#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
1155pub struct OneWireWriteRequest<'a> {
1156    /// Bytes to write to the bus.
1157    pub data: &'a [u8],
1158}
1159
1160/// Request to write bytes to the 1-Wire bus with strong pullup.
1161///
1162/// After writing, the firmware drives the data line high for
1163/// `pullup_duration_ms` milliseconds to supply parasitic power.
1164/// This is required for devices like DS18B20 that draw power from
1165/// the data line during temperature conversion.
1166#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
1167pub struct OneWireWritePullupRequest<'a> {
1168    /// Bytes to write to the bus.
1169    pub data: &'a [u8],
1170    /// Duration in milliseconds to hold the strong pullup after writing.
1171    pub pullup_duration_ms: u16,
1172}
1173
1174// --- Transaction Batching
1175///
1176/// This limits stack usage on the firmware side. Each operation requires
1177/// a small amount of bookkeeping during execution.
1178pub const MAX_BATCH_OPS: usize = 64;
1179
1180/// A single I2C operation for building a batch request.
1181///
1182/// The typed ops are serialized by postcard into the `ops` byte stream
1183/// of [`I2cBatchRequest`]. Use [`encode_i2c_batch_ops`] on the host
1184/// side to produce it, and [`postcard::take_from_bytes`] on the firmware
1185/// side to iterate.
1186///
1187/// WARNING: do not reorder variants — postcard encodes by index, not discriminant.
1188#[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq)]
1189pub enum I2cBatchOp<'a> {
1190    /// Read `len` bytes from the device.
1191    Read { len: u16 },
1192    /// Write `data` to the device.
1193    Write {
1194        #[serde(borrow)]
1195        data: &'a [u8],
1196    },
1197}
1198
1199/// A single SPI operation for building a batch request.
1200///
1201/// The typed ops are serialized by postcard into the `ops` byte stream
1202/// of [`SpiBatchRequest`]. Use [`encode_spi_batch_ops`] on the host
1203/// side to produce it, and [`postcard::take_from_bytes`] on the firmware
1204/// side to iterate.
1205///
1206/// WARNING: do not reorder variants — postcard encodes by index, not discriminant.
1207#[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq)]
1208pub enum SpiBatchOp<'a> {
1209    /// Read `len` bytes from the bus (MISO only).
1210    Read { len: u16 },
1211    /// Write `data` to the bus (MOSI only).
1212    Write {
1213        #[serde(borrow)]
1214        data: &'a [u8],
1215    },
1216    /// Full-duplex transfer: send `data` on MOSI, receive same number of bytes on MISO.
1217    Transfer {
1218        #[serde(borrow)]
1219        data: &'a [u8],
1220    },
1221    /// Delay for `ns` nanoseconds (best-effort, firmware resolution).
1222    DelayNs { ns: u32 },
1223}
1224
1225/// Request to execute a batch of I2C operations as a single transaction.
1226///
1227/// The `ops` field contains a sequence of postcard-serialized
1228/// [`I2cBatchOp`] values. Use [`encode_i2c_batch_ops`] on the host
1229/// side to build it from a typed slice.
1230///
1231/// ## Response
1232///
1233/// On success, the response contains the concatenated read data from all
1234/// Read operations in order. The host already knows the expected lengths
1235/// from the request, so it can split the response accordingly.
1236///
1237/// ## Limitations
1238///
1239/// - Total read data must not exceed [`MAX_TRANSFER_SIZE`]
1240/// - Total write data is limited by USB packet size
1241/// - Maximum [`MAX_BATCH_OPS`] operations per batch
1242/// - Operations execute sequentially with STOP between each (not using
1243///   I2C repeated-start). For write-then-read to the same device, prefer
1244///   the existing [`I2cWriteRead`] endpoint.
1245#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
1246pub struct I2cBatchRequest<'a> {
1247    /// 7-bit I2C slave address.
1248    pub address: u8,
1249    /// Number of operations encoded in `ops`.
1250    pub count: u16,
1251    /// Postcard-serialized [`I2cBatchOp`] sequence.
1252    pub ops: &'a [u8],
1253}
1254
1255/// Error returned when an I2C batch transaction fails.
1256///
1257/// Includes the zero-based index of the operation that failed, so the
1258/// host can identify exactly which step caused the error. Operations
1259/// before `failed_op` completed successfully; their read data is NOT
1260/// included in the response.
1261#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
1262pub struct I2cBatchError {
1263    /// Zero-based index of the operation that failed.
1264    pub failed_op: u16,
1265    /// The I2C error that occurred.
1266    pub kind: I2cError,
1267}
1268
1269impl core::fmt::Display for I2cBatchError {
1270    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1271        write!(
1272            f,
1273            "I2C batch operation {} failed: {}",
1274            self.failed_op, self.kind
1275        )
1276    }
1277}
1278
1279/// Request to execute a batch of SPI operations as a single transaction.
1280///
1281/// The firmware asserts CS on the specified pin before executing the
1282/// operations, and deasserts CS after completion (even on error). This
1283/// provides atomic [`SpiDevice::transaction`] semantics.
1284///
1285/// The `ops` field contains a sequence of postcard-serialized
1286/// [`SpiBatchOp`] values. Use [`encode_spi_batch_ops`] on the host
1287/// side to build it from a typed slice.
1288///
1289/// ## Response
1290///
1291/// On success, the response contains concatenated data from Read and
1292/// Transfer operations in order. The host knows expected lengths from
1293/// the request.
1294///
1295/// [`SpiDevice::transaction`]: embedded_hal::spi::SpiDevice::transaction
1296#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
1297pub struct SpiBatchRequest<'a> {
1298    /// GPIO pin index (0–3) to use as chip select. The firmware asserts
1299    /// it low before the first operation and deasserts it high after the
1300    /// last (or on error).
1301    pub cs_pin: u8,
1302    /// Number of operations encoded in `ops`.
1303    pub count: u16,
1304    /// Postcard-serialized [`SpiBatchOp`] sequence.
1305    pub ops: &'a [u8],
1306}
1307
1308/// Error returned when an SPI batch transaction fails.
1309///
1310/// See [`I2cBatchError`] for the general pattern. For SPI batches,
1311/// the firmware always deasserts CS before returning, even on error.
1312#[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
1313pub struct SpiBatchError {
1314    /// Zero-based index of the operation that failed.
1315    pub failed_op: u16,
1316    /// The SPI error that occurred.
1317    pub kind: SpiError,
1318}
1319
1320impl core::fmt::Display for SpiBatchError {
1321    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1322        write!(
1323            f,
1324            "SPI batch operation {} failed: {}",
1325            self.failed_op, self.kind
1326        )
1327    }
1328}
1329
1330/// Encode a sequence of I2C batch operations into the postcard wire format.
1331///
1332/// Returns the serialized byte stream suitable for [`I2cBatchRequest::ops`].
1333///
1334/// # Panics
1335///
1336/// Panics if `ops.len()` exceeds [`MAX_BATCH_OPS`].
1337#[cfg(feature = "use-std")]
1338pub fn encode_i2c_batch_ops(ops: &[I2cBatchOp<'_>]) -> Vec<u8> {
1339    assert!(ops.len() <= MAX_BATCH_OPS, "too many batch operations");
1340    let mut buf = Vec::new();
1341    let mut tmp = [0u8; 128];
1342    for op in ops {
1343        let encoded = postcard::to_slice(op, &mut tmp).expect("I2cBatchOp encode failed");
1344        buf.extend_from_slice(encoded);
1345    }
1346    buf
1347}
1348
1349/// Encode a sequence of SPI batch operations into the postcard wire format.
1350///
1351/// Returns the serialized byte stream suitable for [`SpiBatchRequest::ops`].
1352///
1353/// # Panics
1354///
1355/// Panics if `ops.len()` exceeds [`MAX_BATCH_OPS`].
1356#[cfg(feature = "use-std")]
1357pub fn encode_spi_batch_ops(ops: &[SpiBatchOp<'_>]) -> Vec<u8> {
1358    assert!(ops.len() <= MAX_BATCH_OPS, "too many batch operations");
1359    let mut buf = Vec::new();
1360    let mut tmp = [0u8; 128];
1361    for op in ops {
1362        let encoded = postcard::to_slice(op, &mut tmp).expect("SpiBatchOp encode failed");
1363        buf.extend_from_slice(encoded);
1364    }
1365    buf
1366}
1367
1368/// Compute the total number of bytes that will appear in the response for
1369/// an I2C batch — the sum of all Read lengths.
1370pub fn i2c_batch_response_len(ops: &[I2cBatchOp<'_>]) -> usize {
1371    ops.iter()
1372        .map(|op| match op {
1373            I2cBatchOp::Read { len } => *len as usize,
1374            I2cBatchOp::Write { .. } => 0,
1375        })
1376        .sum()
1377}
1378
1379/// Compute the total number of bytes that will appear in the response for
1380/// an SPI batch — the sum of Read and Transfer lengths.
1381pub fn spi_batch_response_len(ops: &[SpiBatchOp<'_>]) -> usize {
1382    ops.iter()
1383        .map(|op| match op {
1384            SpiBatchOp::Read { len } => *len as usize,
1385            SpiBatchOp::Transfer { data } => data.len(),
1386            _ => 0,
1387        })
1388        .sum()
1389}
1390
1391// --- Version
1392/// Firmware version information.
1393#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
1394pub struct VersionInfo {
1395    /// Major version number.
1396    pub major: u16,
1397    /// Minor version number.
1398    pub minor: u16,
1399    /// Patch version number.
1400    pub patch: u32,
1401}
1402
1403// --- Device Info
1404
1405/// Hardware capabilities — a bitflag set describing which peripherals are
1406/// available on the connected device.
1407///
1408/// Each capability is a single bit. New capabilities can be added by
1409/// defining new constants without changing the wire format — existing
1410/// bits remain stable.
1411///
1412/// Use [`BitOr`](core::ops::BitOr) to combine flags and
1413/// [`contains`](Capabilities::contains) to test them:
1414///
1415/// ```
1416/// use pico_de_gallo_internal::Capabilities;
1417///
1418/// let caps = Capabilities::I2C | Capabilities::SPI;
1419/// assert!(caps.contains(Capabilities::I2C));
1420/// assert!(!caps.contains(Capabilities::UART));
1421/// ```
1422#[derive(Serialize, Deserialize, Schema, Debug, PartialEq, Eq, Clone, Copy)]
1423pub struct Capabilities(pub u64);
1424
1425impl Capabilities {
1426    /// No capabilities.
1427    pub const NONE: Self = Self(0);
1428    /// I2C bus support (bit 0).
1429    pub const I2C: Self = Self(1 << 0);
1430    /// SPI bus support (bit 1).
1431    pub const SPI: Self = Self(1 << 1);
1432    /// UART support (bit 2).
1433    pub const UART: Self = Self(1 << 2);
1434    /// GPIO support (bit 3).
1435    pub const GPIO: Self = Self(1 << 3);
1436    /// PWM output support (bit 4).
1437    pub const PWM: Self = Self(1 << 4);
1438    /// ADC input support (bit 5).
1439    pub const ADC: Self = Self(1 << 5);
1440    /// 1-Wire bus support (bit 6).
1441    pub const ONEWIRE: Self = Self(1 << 6);
1442
1443    /// Returns `true` if all bits in `other` are set in `self`.
1444    pub const fn contains(self, other: Self) -> bool {
1445        (self.0 & other.0) == other.0
1446    }
1447
1448    /// Returns the raw `u64` bitfield value.
1449    pub const fn bits(self) -> u64 {
1450        self.0
1451    }
1452}
1453
1454impl core::ops::BitOr for Capabilities {
1455    type Output = Self;
1456
1457    fn bitor(self, rhs: Self) -> Self {
1458        Self(self.0 | rhs.0)
1459    }
1460}
1461
1462impl core::ops::BitAnd for Capabilities {
1463    type Output = Self;
1464
1465    fn bitand(self, rhs: Self) -> Self {
1466        Self(self.0 & rhs.0)
1467    }
1468}
1469
1470/// Extended device information including firmware version, schema version,
1471/// hardware version, and peripheral capabilities.
1472///
1473/// This is returned by a separate endpoint from [`VersionInfo`] so that
1474/// the existing `version` endpoint remains wire-stable for older hosts
1475/// to parse.
1476#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)]
1477pub struct DeviceInfo {
1478    /// Firmware version — major.
1479    pub fw_major: u16,
1480    /// Firmware version — minor.
1481    pub fw_minor: u16,
1482    /// Firmware version — patch.
1483    pub fw_patch: u32,
1484    /// Schema (wire protocol) version — major.
1485    pub schema_major: u16,
1486    /// Schema (wire protocol) version — minor.
1487    pub schema_minor: u16,
1488    /// Schema (wire protocol) version — patch.
1489    pub schema_patch: u32,
1490    /// Hardware revision number (1 = original Pico 2 board).
1491    pub hw_version: u8,
1492    /// Peripheral capabilities of the connected device.
1493    pub capabilities: Capabilities,
1494}
1495
1496#[cfg(test)]
1497mod tests {
1498    use super::*;
1499    use postcard::{from_bytes, to_allocvec};
1500
1501    // --- I2C round-trip tests ---
1502
1503    #[test]
1504    fn i2c_read_request_round_trip() {
1505        let req = I2cReadRequest {
1506            address: 0x48,
1507            count: 4,
1508        };
1509        let bytes = to_allocvec(&req).unwrap();
1510        let decoded: I2cReadRequest = from_bytes(&bytes).unwrap();
1511        assert_eq!(req, decoded);
1512    }
1513
1514    #[test]
1515    fn i2c_write_request_round_trip() {
1516        let data = [0xDE, 0xAD, 0xBE, 0xEF];
1517        let req = I2cWriteRequest {
1518            address: 0x50,
1519            contents: &data,
1520        };
1521        let bytes = to_allocvec(&req).unwrap();
1522        let decoded: I2cWriteRequest = from_bytes(&bytes).unwrap();
1523        assert_eq!(req, decoded);
1524    }
1525
1526    #[test]
1527    fn i2c_write_read_request_round_trip() {
1528        let data = [0x01, 0x02];
1529        let req = I2cWriteReadRequest {
1530            address: 0x68,
1531            contents: &data,
1532            count: 6,
1533        };
1534        let bytes = to_allocvec(&req).unwrap();
1535        let decoded: I2cWriteReadRequest = from_bytes(&bytes).unwrap();
1536        assert_eq!(req, decoded);
1537    }
1538
1539    #[test]
1540    fn i2c_read_request_max_count() {
1541        let req = I2cReadRequest {
1542            address: 0x7F,
1543            count: u16::MAX,
1544        };
1545        let bytes = to_allocvec(&req).unwrap();
1546        let decoded: I2cReadRequest = from_bytes(&bytes).unwrap();
1547        assert_eq!(req, decoded);
1548    }
1549
1550    // --- SPI round-trip tests ---
1551
1552    #[test]
1553    fn spi_read_request_round_trip() {
1554        let req = SpiReadRequest { count: 128 };
1555        let bytes = to_allocvec(&req).unwrap();
1556        let decoded: SpiReadRequest = from_bytes(&bytes).unwrap();
1557        assert_eq!(req, decoded);
1558    }
1559
1560    #[test]
1561    fn spi_write_request_round_trip() {
1562        let data = [0xCA, 0xFE];
1563        let req = SpiWriteRequest { contents: &data };
1564        let bytes = to_allocvec(&req).unwrap();
1565        let decoded: SpiWriteRequest = from_bytes(&bytes).unwrap();
1566        assert_eq!(req, decoded);
1567    }
1568
1569    #[test]
1570    fn spi_transfer_request_round_trip() {
1571        let data = [0x01, 0x02, 0x03, 0x04];
1572        let req = SpiTransferRequest { contents: &data };
1573        let bytes = to_allocvec(&req).unwrap();
1574        let decoded: SpiTransferRequest = from_bytes(&bytes).unwrap();
1575        assert_eq!(req, decoded);
1576    }
1577
1578    #[test]
1579    #[cfg(feature = "use-std")]
1580    fn spi_transfer_request_max_size() {
1581        let data = vec![0xAA; MAX_TRANSFER_SIZE];
1582        let req = SpiTransferRequest { contents: &data };
1583        let bytes = to_allocvec(&req).unwrap();
1584        let decoded: SpiTransferRequest = from_bytes(&bytes).unwrap();
1585        assert_eq!(req, decoded);
1586    }
1587
1588    // --- GPIO round-trip tests ---
1589
1590    #[test]
1591    fn gpio_get_request_round_trip() {
1592        for pin in 0..8u8 {
1593            let req = GpioGetRequest { pin };
1594            let bytes = to_allocvec(&req).unwrap();
1595            let decoded: GpioGetRequest = from_bytes(&bytes).unwrap();
1596            assert_eq!(req, decoded);
1597        }
1598    }
1599
1600    #[test]
1601    fn gpio_put_request_round_trip() {
1602        for state in [GpioState::Low, GpioState::High] {
1603            let req = GpioPutRequest { pin: 3, state };
1604            let bytes = to_allocvec(&req).unwrap();
1605            let decoded: GpioPutRequest = from_bytes(&bytes).unwrap();
1606            assert_eq!(req, decoded);
1607        }
1608    }
1609
1610    #[test]
1611    fn gpio_wait_request_round_trip() {
1612        let req = GpioWaitRequest { pin: 7 };
1613        let bytes = to_allocvec(&req).unwrap();
1614        let decoded: GpioWaitRequest = from_bytes(&bytes).unwrap();
1615        assert_eq!(req, decoded);
1616    }
1617
1618    #[test]
1619    fn gpio_state_round_trip() {
1620        for state in [GpioState::Low, GpioState::High] {
1621            let bytes = to_allocvec(&state).unwrap();
1622            let decoded: GpioState = from_bytes(&bytes).unwrap();
1623            assert_eq!(state, decoded);
1624        }
1625    }
1626
1627    #[test]
1628    fn gpio_state_from_bool() {
1629        assert_eq!(GpioState::from(true), GpioState::High);
1630        assert_eq!(GpioState::from(false), GpioState::Low);
1631    }
1632
1633    #[test]
1634    fn bool_from_gpio_state() {
1635        assert!(bool::from(GpioState::High));
1636        assert!(!bool::from(GpioState::Low));
1637    }
1638
1639    // --- Config round-trip tests ---
1640
1641    #[test]
1642    fn i2c_set_configuration_request_round_trip() {
1643        let req = I2cSetConfigurationRequest {
1644            frequency: I2cFrequency::Fast,
1645        };
1646        let bytes = to_allocvec(&req).unwrap();
1647        let decoded: I2cSetConfigurationRequest = from_bytes(&bytes).unwrap();
1648        assert_eq!(req, decoded);
1649    }
1650
1651    #[test]
1652    fn spi_set_configuration_request_round_trip() {
1653        let req = SpiSetConfigurationRequest {
1654            spi_frequency: 1_000_000,
1655            spi_phase: SpiPhase::CaptureOnSecondTransition,
1656            spi_polarity: SpiPolarity::IdleHigh,
1657        };
1658        let bytes = to_allocvec(&req).unwrap();
1659        let decoded: SpiSetConfigurationRequest = from_bytes(&bytes).unwrap();
1660        assert_eq!(req, decoded);
1661    }
1662
1663    #[test]
1664    fn spi_phase_round_trip() {
1665        for phase in [
1666            SpiPhase::CaptureOnFirstTransition,
1667            SpiPhase::CaptureOnSecondTransition,
1668        ] {
1669            let bytes = to_allocvec(&phase).unwrap();
1670            let decoded: SpiPhase = from_bytes(&bytes).unwrap();
1671            assert_eq!(phase, decoded);
1672        }
1673    }
1674
1675    #[test]
1676    fn spi_polarity_round_trip() {
1677        for pol in [SpiPolarity::IdleLow, SpiPolarity::IdleHigh] {
1678            let bytes = to_allocvec(&pol).unwrap();
1679            let decoded: SpiPolarity = from_bytes(&bytes).unwrap();
1680            assert_eq!(pol, decoded);
1681        }
1682    }
1683
1684    // --- Version round-trip test ---
1685
1686    #[test]
1687    fn version_info_round_trip() {
1688        let ver = VersionInfo {
1689            major: 1,
1690            minor: 2,
1691            patch: 42,
1692        };
1693        let bytes = to_allocvec(&ver).unwrap();
1694        let decoded: VersionInfo = from_bytes(&bytes).unwrap();
1695        assert_eq!(ver, decoded);
1696    }
1697
1698    // --- Error enum round-trip tests ---
1699
1700    #[test]
1701    fn i2c_error_variants_round_trip() {
1702        for err in [
1703            I2cError::Bus,
1704            I2cError::NoAcknowledge,
1705            I2cError::ArbitrationLoss,
1706            I2cError::Overrun,
1707            I2cError::BufferTooLong,
1708            I2cError::AddressOutOfRange,
1709            I2cError::Other,
1710        ] {
1711            let bytes = to_allocvec(&err).unwrap();
1712            let decoded: I2cError = from_bytes(&bytes).unwrap();
1713            assert_eq!(err, decoded);
1714        }
1715    }
1716
1717    #[test]
1718    fn spi_error_variants_round_trip() {
1719        for err in [SpiError::BufferTooLong, SpiError::Other] {
1720            let bytes = to_allocvec(&err).unwrap();
1721            let decoded: SpiError = from_bytes(&bytes).unwrap();
1722            assert_eq!(err, decoded);
1723        }
1724    }
1725
1726    #[test]
1727    fn gpio_error_variants_round_trip() {
1728        for err in [
1729            GpioError::InvalidPin,
1730            GpioError::Other,
1731            GpioError::WrongDirection,
1732            GpioError::PinMonitored,
1733            GpioError::PinNotMonitored,
1734        ] {
1735            let bytes = to_allocvec(&err).unwrap();
1736            let decoded: GpioError = from_bytes(&bytes).unwrap();
1737            assert_eq!(err, decoded);
1738        }
1739    }
1740
1741    #[test]
1742    #[cfg(feature = "use-std")]
1743    fn i2c_error_display() {
1744        assert_eq!(
1745            format!("{}", I2cError::NoAcknowledge),
1746            "no acknowledge from target"
1747        );
1748        assert_eq!(format!("{}", I2cError::Bus), "I2C bus error");
1749        assert_eq!(
1750            format!("{}", I2cError::ArbitrationLoss),
1751            "I2C arbitration loss"
1752        );
1753        assert_eq!(format!("{}", I2cError::Overrun), "I2C data overrun");
1754        assert_eq!(
1755            format!("{}", I2cError::BufferTooLong),
1756            "buffer exceeds firmware limit"
1757        );
1758        assert_eq!(
1759            format!("{}", I2cError::AddressOutOfRange),
1760            "I2C address out of range"
1761        );
1762        assert_eq!(format!("{}", I2cError::Other), "I2C error");
1763    }
1764
1765    #[test]
1766    #[cfg(feature = "use-std")]
1767    fn spi_error_display() {
1768        assert_eq!(
1769            format!("{}", SpiError::BufferTooLong),
1770            "buffer exceeds firmware limit"
1771        );
1772        assert_eq!(format!("{}", SpiError::Other), "SPI error");
1773    }
1774
1775    #[test]
1776    #[cfg(feature = "use-std")]
1777    fn gpio_error_display() {
1778        assert_eq!(
1779            format!("{}", GpioError::InvalidPin),
1780            "invalid GPIO pin number"
1781        );
1782        assert_eq!(format!("{}", GpioError::Other), "GPIO error");
1783        assert_eq!(
1784            format!("{}", GpioError::WrongDirection),
1785            "GPIO pin configured in wrong direction"
1786        );
1787        assert_eq!(
1788            format!("{}", GpioError::PinMonitored),
1789            "GPIO pin is being monitored for events"
1790        );
1791        assert_eq!(
1792            format!("{}", GpioError::PinNotMonitored),
1793            "GPIO pin is not monitored"
1794        );
1795    }
1796
1797    // --- P1: Schema stability tests ---
1798    //
1799    // These lock down the wire encoding for each type. If a field is
1800    // added, removed, or reordered the serialized bytes will change
1801    // and these tests will catch it.
1802
1803    #[test]
1804    fn i2c_read_request_wire_stability() {
1805        let req = I2cReadRequest {
1806            address: 0x48,
1807            count: 4,
1808        };
1809        let bytes = to_allocvec(&req).unwrap();
1810        assert_eq!(
1811            bytes,
1812            to_allocvec(&req).unwrap(),
1813            "encoding is deterministic"
1814        );
1815        // Re-decode and compare to ensure exact round-trip
1816        let decoded: I2cReadRequest = from_bytes(&bytes).unwrap();
1817        assert_eq!(decoded, req);
1818        // Lock the exact byte representation
1819        let snapshot = bytes.clone();
1820        let freshly_encoded = to_allocvec(&decoded).unwrap();
1821        assert_eq!(freshly_encoded, snapshot, "wire format must not change");
1822    }
1823
1824    #[test]
1825    fn i2c_set_configuration_request_wire_stability() {
1826        let req = I2cSetConfigurationRequest {
1827            frequency: I2cFrequency::Fast,
1828        };
1829        let bytes = to_allocvec(&req).unwrap();
1830        let canonical = bytes.clone();
1831        let decoded: I2cSetConfigurationRequest = from_bytes(&bytes).unwrap();
1832        assert_eq!(decoded, req);
1833        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
1834    }
1835
1836    #[test]
1837    fn spi_set_configuration_request_wire_stability() {
1838        let req = SpiSetConfigurationRequest {
1839            spi_frequency: 1_000_000,
1840            spi_phase: SpiPhase::CaptureOnFirstTransition,
1841            spi_polarity: SpiPolarity::IdleLow,
1842        };
1843        let bytes = to_allocvec(&req).unwrap();
1844        let canonical = bytes.clone();
1845        let decoded: SpiSetConfigurationRequest = from_bytes(&bytes).unwrap();
1846        assert_eq!(decoded, req);
1847        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
1848    }
1849
1850    #[test]
1851    fn version_info_wire_stability() {
1852        let ver = VersionInfo {
1853            major: 1,
1854            minor: 0,
1855            patch: 0,
1856        };
1857        let bytes = to_allocvec(&ver).unwrap();
1858        let canonical = bytes.clone();
1859        let decoded: VersionInfo = from_bytes(&bytes).unwrap();
1860        assert_eq!(decoded, ver);
1861        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
1862    }
1863
1864    #[test]
1865    fn gpio_put_request_wire_stability() {
1866        let req = GpioPutRequest {
1867            pin: 0,
1868            state: GpioState::High,
1869        };
1870        let bytes = to_allocvec(&req).unwrap();
1871        let canonical = bytes.clone();
1872        let decoded: GpioPutRequest = from_bytes(&bytes).unwrap();
1873        assert_eq!(decoded, req);
1874        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
1875    }
1876
1877    // --- P1: Boundary value tests ---
1878
1879    #[test]
1880    fn i2c_read_request_zero_count() {
1881        let req = I2cReadRequest {
1882            address: 0x00,
1883            count: 0,
1884        };
1885        let bytes = to_allocvec(&req).unwrap();
1886        let decoded: I2cReadRequest = from_bytes(&bytes).unwrap();
1887        assert_eq!(req, decoded);
1888    }
1889
1890    #[test]
1891    fn i2c_read_request_max_address() {
1892        let req = I2cReadRequest {
1893            address: u8::MAX,
1894            count: 1,
1895        };
1896        let bytes = to_allocvec(&req).unwrap();
1897        let decoded: I2cReadRequest = from_bytes(&bytes).unwrap();
1898        assert_eq!(req, decoded);
1899    }
1900
1901    #[test]
1902    fn spi_read_request_max_count() {
1903        let req = SpiReadRequest { count: u16::MAX };
1904        let bytes = to_allocvec(&req).unwrap();
1905        let decoded: SpiReadRequest = from_bytes(&bytes).unwrap();
1906        assert_eq!(req, decoded);
1907    }
1908
1909    #[test]
1910    fn spi_read_request_zero_count() {
1911        let req = SpiReadRequest { count: 0 };
1912        let bytes = to_allocvec(&req).unwrap();
1913        let decoded: SpiReadRequest = from_bytes(&bytes).unwrap();
1914        assert_eq!(req, decoded);
1915    }
1916
1917    #[test]
1918    fn i2c_write_request_empty_contents() {
1919        let req = I2cWriteRequest {
1920            address: 0x50,
1921            contents: &[],
1922        };
1923        let bytes = to_allocvec(&req).unwrap();
1924        let decoded: I2cWriteRequest = from_bytes(&bytes).unwrap();
1925        assert_eq!(req, decoded);
1926    }
1927
1928    #[test]
1929    fn spi_write_request_empty_contents() {
1930        let req = SpiWriteRequest { contents: &[] };
1931        let bytes = to_allocvec(&req).unwrap();
1932        let decoded: SpiWriteRequest = from_bytes(&bytes).unwrap();
1933        assert_eq!(req, decoded);
1934    }
1935
1936    #[test]
1937    fn i2c_write_read_request_empty_contents_max_count() {
1938        let req = I2cWriteReadRequest {
1939            address: 0x7F,
1940            contents: &[],
1941            count: u16::MAX,
1942        };
1943        let bytes = to_allocvec(&req).unwrap();
1944        let decoded: I2cWriteReadRequest = from_bytes(&bytes).unwrap();
1945        assert_eq!(req, decoded);
1946    }
1947
1948    #[test]
1949    fn gpio_get_request_all_pins() {
1950        for pin in 0..=u8::MAX {
1951            let req = GpioGetRequest { pin };
1952            let bytes = to_allocvec(&req).unwrap();
1953            let decoded: GpioGetRequest = from_bytes(&bytes).unwrap();
1954            assert_eq!(req, decoded);
1955        }
1956    }
1957
1958    #[test]
1959    fn gpio_wait_request_all_pins() {
1960        for pin in 0..=u8::MAX {
1961            let req = GpioWaitRequest { pin };
1962            let bytes = to_allocvec(&req).unwrap();
1963            let decoded: GpioWaitRequest = from_bytes(&bytes).unwrap();
1964            assert_eq!(req, decoded);
1965        }
1966    }
1967
1968    #[test]
1969    fn version_info_boundary_values() {
1970        for ver in [
1971            VersionInfo {
1972                major: 0,
1973                minor: 0,
1974                patch: 0,
1975            },
1976            VersionInfo {
1977                major: u16::MAX,
1978                minor: u16::MAX,
1979                patch: u32::MAX,
1980            },
1981        ] {
1982            let bytes = to_allocvec(&ver).unwrap();
1983            let decoded: VersionInfo = from_bytes(&bytes).unwrap();
1984            assert_eq!(ver, decoded);
1985        }
1986    }
1987
1988    #[test]
1989    fn spi_set_configuration_request_all_enum_combinations() {
1990        for (phase, polarity) in [
1991            (SpiPhase::CaptureOnFirstTransition, SpiPolarity::IdleLow),
1992            (SpiPhase::CaptureOnFirstTransition, SpiPolarity::IdleHigh),
1993            (SpiPhase::CaptureOnSecondTransition, SpiPolarity::IdleLow),
1994            (SpiPhase::CaptureOnSecondTransition, SpiPolarity::IdleHigh),
1995        ] {
1996            let req = SpiSetConfigurationRequest {
1997                spi_frequency: 500_000,
1998                spi_phase: phase,
1999                spi_polarity: polarity,
2000            };
2001            let bytes = to_allocvec(&req).unwrap();
2002            let decoded: SpiSetConfigurationRequest = from_bytes(&bytes).unwrap();
2003            assert_eq!(req, decoded);
2004        }
2005    }
2006
2007    #[test]
2008    fn i2c_set_configuration_request_all_frequencies() {
2009        for freq in [
2010            I2cFrequency::Standard,
2011            I2cFrequency::Fast,
2012            I2cFrequency::FastPlus,
2013        ] {
2014            let req = I2cSetConfigurationRequest { frequency: freq };
2015            let bytes = to_allocvec(&req).unwrap();
2016            let decoded: I2cSetConfigurationRequest = from_bytes(&bytes).unwrap();
2017            assert_eq!(req, decoded);
2018        }
2019    }
2020
2021    #[test]
2022    fn i2c_frequency_discriminants_are_stable() {
2023        assert_eq!(I2cFrequency::Standard as u8, 0);
2024        assert_eq!(I2cFrequency::Fast as u8, 1);
2025        assert_eq!(I2cFrequency::FastPlus as u8, 2);
2026    }
2027
2028    #[test]
2029    fn spi_set_configuration_request_max_frequency() {
2030        let req = SpiSetConfigurationRequest {
2031            spi_frequency: u32::MAX,
2032            spi_phase: SpiPhase::CaptureOnFirstTransition,
2033            spi_polarity: SpiPolarity::IdleLow,
2034        };
2035        let bytes = to_allocvec(&req).unwrap();
2036        let decoded: SpiSetConfigurationRequest = from_bytes(&bytes).unwrap();
2037        assert_eq!(req, decoded);
2038    }
2039
2040    // SpiPhase and SpiPolarity discriminants must be stable for wire compat
2041    #[test]
2042    fn spi_phase_discriminants_are_stable() {
2043        assert_eq!(
2044            to_allocvec(&SpiPhase::CaptureOnFirstTransition).unwrap(),
2045            to_allocvec(&SpiPhase::CaptureOnFirstTransition).unwrap()
2046        );
2047        // Different variants must produce different bytes
2048        assert_ne!(
2049            to_allocvec(&SpiPhase::CaptureOnFirstTransition).unwrap(),
2050            to_allocvec(&SpiPhase::CaptureOnSecondTransition).unwrap()
2051        );
2052    }
2053
2054    #[test]
2055    fn spi_polarity_discriminants_are_stable() {
2056        assert_ne!(
2057            to_allocvec(&SpiPolarity::IdleLow).unwrap(),
2058            to_allocvec(&SpiPolarity::IdleHigh).unwrap()
2059        );
2060    }
2061
2062    #[test]
2063    fn gpio_state_discriminants_are_stable() {
2064        assert_ne!(
2065            to_allocvec(&GpioState::Low).unwrap(),
2066            to_allocvec(&GpioState::High).unwrap()
2067        );
2068    }
2069
2070    // --- GPIO direction/pull tests ---
2071
2072    #[test]
2073    fn gpio_direction_round_trip() {
2074        for dir in [GpioDirection::Input, GpioDirection::Output] {
2075            let bytes = to_allocvec(&dir).unwrap();
2076            let decoded: GpioDirection = from_bytes(&bytes).unwrap();
2077            assert_eq!(dir, decoded);
2078        }
2079    }
2080
2081    #[test]
2082    fn gpio_pull_round_trip() {
2083        for pull in [GpioPull::None, GpioPull::Up, GpioPull::Down] {
2084            let bytes = to_allocvec(&pull).unwrap();
2085            let decoded: GpioPull = from_bytes(&bytes).unwrap();
2086            assert_eq!(pull, decoded);
2087        }
2088    }
2089
2090    #[test]
2091    fn gpio_set_configuration_request_round_trip() {
2092        let req = GpioSetConfigurationRequest {
2093            pin: 3,
2094            direction: GpioDirection::Input,
2095            pull: GpioPull::Up,
2096        };
2097        let bytes = to_allocvec(&req).unwrap();
2098        let decoded: GpioSetConfigurationRequest = from_bytes(&bytes).unwrap();
2099        assert_eq!(req, decoded);
2100    }
2101
2102    #[test]
2103    fn gpio_set_configuration_request_all_combinations() {
2104        for dir in [GpioDirection::Input, GpioDirection::Output] {
2105            for pull in [GpioPull::None, GpioPull::Up, GpioPull::Down] {
2106                let req = GpioSetConfigurationRequest {
2107                    pin: 0,
2108                    direction: dir,
2109                    pull,
2110                };
2111                let bytes = to_allocvec(&req).unwrap();
2112                let decoded: GpioSetConfigurationRequest = from_bytes(&bytes).unwrap();
2113                assert_eq!(req, decoded);
2114            }
2115        }
2116    }
2117
2118    #[test]
2119    fn gpio_direction_discriminants_are_stable() {
2120        assert_eq!(GpioDirection::Input as u8, 0);
2121        assert_eq!(GpioDirection::Output as u8, 1);
2122    }
2123
2124    #[test]
2125    fn gpio_pull_discriminants_are_stable() {
2126        assert_eq!(GpioPull::None as u8, 0);
2127        assert_eq!(GpioPull::Up as u8, 1);
2128        assert_eq!(GpioPull::Down as u8, 2);
2129    }
2130
2131    #[test]
2132    #[cfg(feature = "use-std")]
2133    fn gpio_direction_golden_bytes() {
2134        // Lock exact wire encoding: Input=0x00, Output=0x01
2135        assert_eq!(to_allocvec(&GpioDirection::Input).unwrap(), vec![0x00]);
2136        assert_eq!(to_allocvec(&GpioDirection::Output).unwrap(), vec![0x01]);
2137    }
2138
2139    #[test]
2140    #[cfg(feature = "use-std")]
2141    fn gpio_pull_golden_bytes() {
2142        // Lock exact wire encoding: None=0x00, Up=0x01, Down=0x02
2143        assert_eq!(to_allocvec(&GpioPull::None).unwrap(), vec![0x00]);
2144        assert_eq!(to_allocvec(&GpioPull::Up).unwrap(), vec![0x01]);
2145        assert_eq!(to_allocvec(&GpioPull::Down).unwrap(), vec![0x02]);
2146    }
2147
2148    #[test]
2149    fn gpio_set_configuration_request_wire_stability() {
2150        let req = GpioSetConfigurationRequest {
2151            pin: 5,
2152            direction: GpioDirection::Output,
2153            pull: GpioPull::None,
2154        };
2155        let bytes = to_allocvec(&req).unwrap();
2156        let canonical = bytes.clone();
2157        let decoded: GpioSetConfigurationRequest = from_bytes(&bytes).unwrap();
2158        assert_eq!(decoded, req);
2159        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2160    }
2161
2162    // --- Config query round-trip tests ---
2163
2164    #[test]
2165    fn spi_configuration_info_round_trip() {
2166        let info = SpiConfigurationInfo {
2167            spi_frequency: 1_000_000,
2168            spi_phase: SpiPhase::CaptureOnFirstTransition,
2169            spi_polarity: SpiPolarity::IdleLow,
2170        };
2171        let bytes = to_allocvec(&info).unwrap();
2172        let decoded: SpiConfigurationInfo = from_bytes(&bytes).unwrap();
2173        assert_eq!(info, decoded);
2174    }
2175
2176    #[test]
2177    fn spi_configuration_info_all_variants() {
2178        let info = SpiConfigurationInfo {
2179            spi_frequency: 25_000_000,
2180            spi_phase: SpiPhase::CaptureOnSecondTransition,
2181            spi_polarity: SpiPolarity::IdleHigh,
2182        };
2183        let bytes = to_allocvec(&info).unwrap();
2184        let decoded: SpiConfigurationInfo = from_bytes(&bytes).unwrap();
2185        assert_eq!(info, decoded);
2186    }
2187
2188    #[test]
2189    fn spi_configuration_info_golden_bytes() {
2190        // freq=1_000_000 (varint), phase=0, polarity=0
2191        let info = SpiConfigurationInfo {
2192            spi_frequency: 1_000_000,
2193            spi_phase: SpiPhase::CaptureOnFirstTransition,
2194            spi_polarity: SpiPolarity::IdleLow,
2195        };
2196        let bytes = to_allocvec(&info).unwrap();
2197        let decoded: SpiConfigurationInfo = from_bytes(&bytes).unwrap();
2198        assert_eq!(decoded, info);
2199        // phase and polarity are varint-0
2200        assert_eq!(bytes[bytes.len() - 2], 0x00); // phase
2201        assert_eq!(bytes[bytes.len() - 1], 0x00); // polarity
2202    }
2203
2204    #[test]
2205    fn i2c_frequency_round_trip_all_variants() {
2206        for freq in [
2207            I2cFrequency::Standard,
2208            I2cFrequency::Fast,
2209            I2cFrequency::FastPlus,
2210        ] {
2211            let bytes = to_allocvec(&freq).unwrap();
2212            let decoded: I2cFrequency = from_bytes(&bytes).unwrap();
2213            assert_eq!(freq, decoded);
2214        }
2215    }
2216
2217    // --- UART round-trip tests ---
2218
2219    #[test]
2220    fn uart_read_request_round_trip() {
2221        let req = UartReadRequest {
2222            count: 64,
2223            timeout_ms: 1000,
2224        };
2225        let bytes = to_allocvec(&req).unwrap();
2226        let decoded: UartReadRequest = from_bytes(&bytes).unwrap();
2227        assert_eq!(req, decoded);
2228    }
2229
2230    #[test]
2231    fn uart_read_request_zero_timeout() {
2232        let req = UartReadRequest {
2233            count: 10,
2234            timeout_ms: 0,
2235        };
2236        let bytes = to_allocvec(&req).unwrap();
2237        let decoded: UartReadRequest = from_bytes(&bytes).unwrap();
2238        assert_eq!(req, decoded);
2239    }
2240
2241    #[test]
2242    fn uart_read_request_max_count() {
2243        let req = UartReadRequest {
2244            count: u16::MAX,
2245            timeout_ms: 5000,
2246        };
2247        let bytes = to_allocvec(&req).unwrap();
2248        let decoded: UartReadRequest = from_bytes(&bytes).unwrap();
2249        assert_eq!(req, decoded);
2250    }
2251
2252    #[test]
2253    fn uart_write_request_round_trip() {
2254        let data = [0x48, 0x65, 0x6c, 0x6c, 0x6f];
2255        let req = UartWriteRequest { contents: &data };
2256        let bytes = to_allocvec(&req).unwrap();
2257        let decoded: UartWriteRequest = from_bytes(&bytes).unwrap();
2258        assert_eq!(req, decoded);
2259    }
2260
2261    #[test]
2262    fn uart_write_request_empty_contents() {
2263        let req = UartWriteRequest { contents: &[] };
2264        let bytes = to_allocvec(&req).unwrap();
2265        let decoded: UartWriteRequest = from_bytes(&bytes).unwrap();
2266        assert_eq!(req, decoded);
2267    }
2268
2269    #[test]
2270    fn uart_set_configuration_request_round_trip() {
2271        let req = UartSetConfigurationRequest { baud_rate: 115_200 };
2272        let bytes = to_allocvec(&req).unwrap();
2273        let decoded: UartSetConfigurationRequest = from_bytes(&bytes).unwrap();
2274        assert_eq!(req, decoded);
2275    }
2276
2277    #[test]
2278    fn uart_set_configuration_request_common_baud_rates() {
2279        for baud in [9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600] {
2280            let req = UartSetConfigurationRequest { baud_rate: baud };
2281            let bytes = to_allocvec(&req).unwrap();
2282            let decoded: UartSetConfigurationRequest = from_bytes(&bytes).unwrap();
2283            assert_eq!(req, decoded);
2284        }
2285    }
2286
2287    #[test]
2288    fn uart_configuration_info_round_trip() {
2289        let info = UartConfigurationInfo { baud_rate: 115_200 };
2290        let bytes = to_allocvec(&info).unwrap();
2291        let decoded: UartConfigurationInfo = from_bytes(&bytes).unwrap();
2292        assert_eq!(info, decoded);
2293    }
2294
2295    #[test]
2296    fn uart_error_variants_round_trip() {
2297        for err in [
2298            UartError::BufferTooLong,
2299            UartError::Overrun,
2300            UartError::Break,
2301            UartError::Parity,
2302            UartError::Framing,
2303            UartError::InvalidBaudRate,
2304            UartError::Other,
2305            UartError::Unsupported,
2306        ] {
2307            let bytes = to_allocvec(&err).unwrap();
2308            let decoded: UartError = from_bytes(&bytes).unwrap();
2309            assert_eq!(err, decoded);
2310        }
2311    }
2312
2313    #[test]
2314    #[cfg(feature = "use-std")]
2315    fn uart_error_display() {
2316        assert_eq!(
2317            format!("{}", UartError::BufferTooLong),
2318            "buffer exceeds firmware limit"
2319        );
2320        assert_eq!(format!("{}", UartError::Overrun), "UART receiver overrun");
2321        assert_eq!(format!("{}", UartError::Break), "UART break condition");
2322        assert_eq!(format!("{}", UartError::Parity), "UART parity error");
2323        assert_eq!(format!("{}", UartError::Framing), "UART framing error");
2324        assert_eq!(
2325            format!("{}", UartError::InvalidBaudRate),
2326            "invalid baud rate"
2327        );
2328        assert_eq!(format!("{}", UartError::Other), "UART error");
2329        assert_eq!(
2330            format!("{}", UartError::Unsupported),
2331            "UART not supported on this hardware"
2332        );
2333    }
2334
2335    #[test]
2336    fn uart_read_request_wire_stability() {
2337        let req = UartReadRequest {
2338            count: 64,
2339            timeout_ms: 1000,
2340        };
2341        let bytes = to_allocvec(&req).unwrap();
2342        let canonical = bytes.clone();
2343        let decoded: UartReadRequest = from_bytes(&bytes).unwrap();
2344        assert_eq!(decoded, req);
2345        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2346    }
2347
2348    #[test]
2349    fn uart_set_configuration_request_wire_stability() {
2350        let req = UartSetConfigurationRequest { baud_rate: 115_200 };
2351        let bytes = to_allocvec(&req).unwrap();
2352        let canonical = bytes.clone();
2353        let decoded: UartSetConfigurationRequest = from_bytes(&bytes).unwrap();
2354        assert_eq!(decoded, req);
2355        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2356    }
2357
2358    #[test]
2359    fn uart_configuration_info_wire_stability() {
2360        let info = UartConfigurationInfo { baud_rate: 115_200 };
2361        let bytes = to_allocvec(&info).unwrap();
2362        let canonical = bytes.clone();
2363        let decoded: UartConfigurationInfo = from_bytes(&bytes).unwrap();
2364        assert_eq!(decoded, info);
2365        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2366    }
2367
2368    // --- PWM round-trip tests ---
2369
2370    #[test]
2371    fn pwm_set_duty_cycle_request_round_trip() {
2372        let req = PwmSetDutyCycleRequest {
2373            channel: 2,
2374            duty: 32768,
2375        };
2376        let bytes = to_allocvec(&req).unwrap();
2377        let decoded: PwmSetDutyCycleRequest = from_bytes(&bytes).unwrap();
2378        assert_eq!(req, decoded);
2379    }
2380
2381    #[test]
2382    fn pwm_set_duty_cycle_request_zero_duty() {
2383        let req = PwmSetDutyCycleRequest {
2384            channel: 0,
2385            duty: 0,
2386        };
2387        let bytes = to_allocvec(&req).unwrap();
2388        let decoded: PwmSetDutyCycleRequest = from_bytes(&bytes).unwrap();
2389        assert_eq!(req, decoded);
2390    }
2391
2392    #[test]
2393    fn pwm_set_duty_cycle_request_max_duty() {
2394        let req = PwmSetDutyCycleRequest {
2395            channel: 3,
2396            duty: u16::MAX,
2397        };
2398        let bytes = to_allocvec(&req).unwrap();
2399        let decoded: PwmSetDutyCycleRequest = from_bytes(&bytes).unwrap();
2400        assert_eq!(req, decoded);
2401    }
2402
2403    #[test]
2404    fn pwm_get_duty_cycle_request_round_trip() {
2405        let req = PwmGetDutyCycleRequest { channel: 1 };
2406        let bytes = to_allocvec(&req).unwrap();
2407        let decoded: PwmGetDutyCycleRequest = from_bytes(&bytes).unwrap();
2408        assert_eq!(req, decoded);
2409    }
2410
2411    #[test]
2412    fn pwm_duty_cycle_info_round_trip() {
2413        let info = PwmDutyCycleInfo {
2414            max_duty: 65535,
2415            current_duty: 32768,
2416        };
2417        let bytes = to_allocvec(&info).unwrap();
2418        let decoded: PwmDutyCycleInfo = from_bytes(&bytes).unwrap();
2419        assert_eq!(info, decoded);
2420    }
2421
2422    #[test]
2423    fn pwm_enable_request_round_trip() {
2424        let req = PwmEnableRequest { channel: 0 };
2425        let bytes = to_allocvec(&req).unwrap();
2426        let decoded: PwmEnableRequest = from_bytes(&bytes).unwrap();
2427        assert_eq!(req, decoded);
2428    }
2429
2430    #[test]
2431    fn pwm_disable_request_round_trip() {
2432        let req = PwmDisableRequest { channel: 3 };
2433        let bytes = to_allocvec(&req).unwrap();
2434        let decoded: PwmDisableRequest = from_bytes(&bytes).unwrap();
2435        assert_eq!(req, decoded);
2436    }
2437
2438    #[test]
2439    fn pwm_set_configuration_request_round_trip() {
2440        let req = PwmSetConfigurationRequest {
2441            channel: 1,
2442            frequency_hz: 1_000,
2443            phase_correct: false,
2444        };
2445        let bytes = to_allocvec(&req).unwrap();
2446        let decoded: PwmSetConfigurationRequest = from_bytes(&bytes).unwrap();
2447        assert_eq!(req, decoded);
2448    }
2449
2450    #[test]
2451    fn pwm_set_configuration_request_phase_correct() {
2452        let req = PwmSetConfigurationRequest {
2453            channel: 2,
2454            frequency_hz: 50,
2455            phase_correct: true,
2456        };
2457        let bytes = to_allocvec(&req).unwrap();
2458        let decoded: PwmSetConfigurationRequest = from_bytes(&bytes).unwrap();
2459        assert_eq!(req, decoded);
2460    }
2461
2462    #[test]
2463    fn pwm_set_configuration_request_common_frequencies() {
2464        for freq in [50, 100, 1_000, 10_000, 50_000, 100_000, 1_000_000] {
2465            let req = PwmSetConfigurationRequest {
2466                channel: 0,
2467                frequency_hz: freq,
2468                phase_correct: false,
2469            };
2470            let bytes = to_allocvec(&req).unwrap();
2471            let decoded: PwmSetConfigurationRequest = from_bytes(&bytes).unwrap();
2472            assert_eq!(req, decoded);
2473        }
2474    }
2475
2476    #[test]
2477    fn pwm_get_configuration_request_round_trip() {
2478        let req = PwmGetConfigurationRequest { channel: 3 };
2479        let bytes = to_allocvec(&req).unwrap();
2480        let decoded: PwmGetConfigurationRequest = from_bytes(&bytes).unwrap();
2481        assert_eq!(req, decoded);
2482    }
2483
2484    #[test]
2485    fn pwm_configuration_info_round_trip() {
2486        let info = PwmConfigurationInfo {
2487            frequency_hz: 1_000,
2488            phase_correct: false,
2489            enabled: true,
2490        };
2491        let bytes = to_allocvec(&info).unwrap();
2492        let decoded: PwmConfigurationInfo = from_bytes(&bytes).unwrap();
2493        assert_eq!(info, decoded);
2494    }
2495
2496    #[test]
2497    fn pwm_error_variants_round_trip() {
2498        for err in [
2499            PwmError::InvalidChannel,
2500            PwmError::InvalidDutyCycle,
2501            PwmError::InvalidConfiguration,
2502            PwmError::Other,
2503        ] {
2504            let bytes = to_allocvec(&err).unwrap();
2505            let decoded: PwmError = from_bytes(&bytes).unwrap();
2506            assert_eq!(err, decoded);
2507        }
2508    }
2509
2510    #[test]
2511    #[cfg(feature = "use-std")]
2512    fn pwm_error_display() {
2513        assert_eq!(
2514            format!("{}", PwmError::InvalidChannel),
2515            "invalid PWM channel"
2516        );
2517        assert_eq!(
2518            format!("{}", PwmError::InvalidDutyCycle),
2519            "duty cycle exceeds maximum"
2520        );
2521        assert_eq!(
2522            format!("{}", PwmError::InvalidConfiguration),
2523            "invalid PWM configuration"
2524        );
2525        assert_eq!(format!("{}", PwmError::Other), "PWM error");
2526    }
2527
2528    #[test]
2529    fn pwm_set_duty_cycle_request_wire_stability() {
2530        let req = PwmSetDutyCycleRequest {
2531            channel: 1,
2532            duty: 1000,
2533        };
2534        let bytes = to_allocvec(&req).unwrap();
2535        let canonical = bytes.clone();
2536        let decoded: PwmSetDutyCycleRequest = from_bytes(&bytes).unwrap();
2537        assert_eq!(decoded, req);
2538        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2539    }
2540
2541    #[test]
2542    fn pwm_set_configuration_request_wire_stability() {
2543        let req = PwmSetConfigurationRequest {
2544            channel: 0,
2545            frequency_hz: 1_000,
2546            phase_correct: false,
2547        };
2548        let bytes = to_allocvec(&req).unwrap();
2549        let canonical = bytes.clone();
2550        let decoded: PwmSetConfigurationRequest = from_bytes(&bytes).unwrap();
2551        assert_eq!(decoded, req);
2552        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2553    }
2554
2555    #[test]
2556    fn pwm_configuration_info_wire_stability() {
2557        let info = PwmConfigurationInfo {
2558            frequency_hz: 50_000,
2559            phase_correct: true,
2560            enabled: true,
2561        };
2562        let bytes = to_allocvec(&info).unwrap();
2563        let canonical = bytes.clone();
2564        let decoded: PwmConfigurationInfo = from_bytes(&bytes).unwrap();
2565        assert_eq!(decoded, info);
2566        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2567    }
2568
2569    // --- ADC tests ---
2570
2571    #[test]
2572    fn adc_read_request_round_trip() {
2573        let req = AdcReadRequest {
2574            channel: AdcChannel::Adc2,
2575        };
2576        let bytes = to_allocvec(&req).unwrap();
2577        let decoded: AdcReadRequest = from_bytes(&bytes).unwrap();
2578        assert_eq!(req, decoded);
2579    }
2580
2581    #[test]
2582    #[cfg(feature = "use-std")]
2583    fn adc_channel_display() {
2584        assert_eq!(format!("{}", AdcChannel::Adc0), "ADC0 (GPIO26)");
2585        assert_eq!(format!("{}", AdcChannel::Adc1), "ADC1 (GPIO27)");
2586        assert_eq!(format!("{}", AdcChannel::Adc2), "ADC2 (GPIO28)");
2587        assert_eq!(format!("{}", AdcChannel::Adc3), "ADC3 (GPIO29)");
2588    }
2589
2590    #[test]
2591    #[cfg(feature = "use-std")]
2592    fn adc_error_display() {
2593        assert_eq!(
2594            format!("{}", AdcError::ConversionFailed),
2595            "ADC conversion failed"
2596        );
2597        assert_eq!(format!("{}", AdcError::Other), "ADC error");
2598        assert_eq!(
2599            format!("{}", AdcError::Unsupported),
2600            "ADC not supported on this hardware"
2601        );
2602    }
2603
2604    #[test]
2605    fn adc_configuration_info_round_trip() {
2606        let info = AdcConfigurationInfo {
2607            resolution_bits: 12,
2608            nominal_reference_mv: 3300,
2609            num_gpio_channels: 4,
2610        };
2611        let bytes = to_allocvec(&info).unwrap();
2612        let decoded: AdcConfigurationInfo = from_bytes(&bytes).unwrap();
2613        assert_eq!(info, decoded);
2614    }
2615
2616    #[test]
2617    fn adc_read_request_wire_stability() {
2618        let req = AdcReadRequest {
2619            channel: AdcChannel::Adc1,
2620        };
2621        let bytes = to_allocvec(&req).unwrap();
2622        let canonical = bytes.clone();
2623        let decoded: AdcReadRequest = from_bytes(&bytes).unwrap();
2624        assert_eq!(decoded, req);
2625        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2626    }
2627
2628    #[test]
2629    fn adc_configuration_info_wire_stability() {
2630        let info = AdcConfigurationInfo {
2631            resolution_bits: 12,
2632            nominal_reference_mv: 3300,
2633            num_gpio_channels: 4,
2634        };
2635        let bytes = to_allocvec(&info).unwrap();
2636        let canonical = bytes.clone();
2637        let decoded: AdcConfigurationInfo = from_bytes(&bytes).unwrap();
2638        assert_eq!(decoded, info);
2639        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2640    }
2641
2642    #[test]
2643    fn adc_channel_discriminants_are_stable() {
2644        // Verify enum discriminants match expected wire values
2645        assert_eq!(to_allocvec(&AdcChannel::Adc0).unwrap(), [0]);
2646        assert_eq!(to_allocvec(&AdcChannel::Adc1).unwrap(), [1]);
2647        assert_eq!(to_allocvec(&AdcChannel::Adc2).unwrap(), [2]);
2648        assert_eq!(to_allocvec(&AdcChannel::Adc3).unwrap(), [3]);
2649    }
2650
2651    // --- GPIO event monitoring tests ---
2652
2653    #[test]
2654    fn gpio_edge_round_trip() {
2655        for edge in [GpioEdge::Rising, GpioEdge::Falling, GpioEdge::Any] {
2656            let bytes = to_allocvec(&edge).unwrap();
2657            let decoded: GpioEdge = from_bytes(&bytes).unwrap();
2658            assert_eq!(edge, decoded);
2659        }
2660    }
2661
2662    #[test]
2663    fn gpio_edge_discriminants_are_stable() {
2664        assert_eq!(GpioEdge::Rising as u8, 0);
2665        assert_eq!(GpioEdge::Falling as u8, 1);
2666        assert_eq!(GpioEdge::Any as u8, 2);
2667    }
2668
2669    #[test]
2670    #[cfg(feature = "use-std")]
2671    fn gpio_edge_display() {
2672        assert_eq!(format!("{}", GpioEdge::Rising), "rising");
2673        assert_eq!(format!("{}", GpioEdge::Falling), "falling");
2674        assert_eq!(format!("{}", GpioEdge::Any), "any");
2675    }
2676
2677    #[test]
2678    fn gpio_event_round_trip() {
2679        let event = GpioEvent {
2680            pin: 2,
2681            edge: GpioEdge::Rising,
2682            state: GpioState::High,
2683            timestamp_us: 123_456_789,
2684        };
2685        let bytes = to_allocvec(&event).unwrap();
2686        let decoded: GpioEvent = from_bytes(&bytes).unwrap();
2687        assert_eq!(event, decoded);
2688    }
2689
2690    #[test]
2691    fn gpio_event_all_edge_variants() {
2692        for edge in [GpioEdge::Rising, GpioEdge::Falling, GpioEdge::Any] {
2693            for state in [GpioState::Low, GpioState::High] {
2694                let event = GpioEvent {
2695                    pin: 0,
2696                    edge,
2697                    state,
2698                    timestamp_us: 0,
2699                };
2700                let bytes = to_allocvec(&event).unwrap();
2701                let decoded: GpioEvent = from_bytes(&bytes).unwrap();
2702                assert_eq!(event, decoded);
2703            }
2704        }
2705    }
2706
2707    #[test]
2708    fn gpio_event_wire_stability() {
2709        let event = GpioEvent {
2710            pin: 1,
2711            edge: GpioEdge::Falling,
2712            state: GpioState::Low,
2713            timestamp_us: 999_999,
2714        };
2715        let bytes = to_allocvec(&event).unwrap();
2716        let canonical = bytes.clone();
2717        let decoded: GpioEvent = from_bytes(&bytes).unwrap();
2718        assert_eq!(decoded, event);
2719        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2720    }
2721
2722    #[test]
2723    fn gpio_subscribe_request_round_trip() {
2724        for edge in [GpioEdge::Rising, GpioEdge::Falling, GpioEdge::Any] {
2725            let req = GpioSubscribeRequest { pin: 3, edge };
2726            let bytes = to_allocvec(&req).unwrap();
2727            let decoded: GpioSubscribeRequest = from_bytes(&bytes).unwrap();
2728            assert_eq!(req, decoded);
2729        }
2730    }
2731
2732    #[test]
2733    fn gpio_unsubscribe_request_round_trip() {
2734        for pin in 0..4u8 {
2735            let req = GpioUnsubscribeRequest { pin };
2736            let bytes = to_allocvec(&req).unwrap();
2737            let decoded: GpioUnsubscribeRequest = from_bytes(&bytes).unwrap();
2738            assert_eq!(req, decoded);
2739        }
2740    }
2741
2742    #[test]
2743    fn gpio_subscribe_request_wire_stability() {
2744        let req = GpioSubscribeRequest {
2745            pin: 2,
2746            edge: GpioEdge::Any,
2747        };
2748        let bytes = to_allocvec(&req).unwrap();
2749        let canonical = bytes.clone();
2750        let decoded: GpioSubscribeRequest = from_bytes(&bytes).unwrap();
2751        assert_eq!(decoded, req);
2752        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
2753    }
2754
2755    // --- Transaction Batching Tests ---
2756
2757    #[test]
2758    fn i2c_batch_op_round_trip() {
2759        let ops = [
2760            I2cBatchOp::Read { len: 16 },
2761            I2cBatchOp::Write {
2762                data: &[0xAA, 0xBB],
2763            },
2764        ];
2765        for op in &ops {
2766            let bytes = to_allocvec(op).unwrap();
2767            let decoded: I2cBatchOp = from_bytes(&bytes).unwrap();
2768            assert_eq!(&decoded, op);
2769        }
2770    }
2771
2772    #[test]
2773    fn spi_batch_op_round_trip() {
2774        let ops = [
2775            SpiBatchOp::Read { len: 8 },
2776            SpiBatchOp::Write {
2777                data: &[0x01, 0x02],
2778            },
2779            SpiBatchOp::Transfer { data: &[0xFF; 4] },
2780            SpiBatchOp::DelayNs { ns: 1_000_000 },
2781        ];
2782        for op in &ops {
2783            let bytes = to_allocvec(op).unwrap();
2784            let decoded: SpiBatchOp = from_bytes(&bytes).unwrap();
2785            assert_eq!(&decoded, op);
2786        }
2787    }
2788
2789    #[cfg(feature = "use-std")]
2790    #[test]
2791    fn i2c_batch_ops_take_from_bytes() {
2792        let ops = [
2793            I2cBatchOp::Write {
2794                data: &[0xAA, 0xBB],
2795            },
2796            I2cBatchOp::Read { len: 4 },
2797        ];
2798        let encoded = encode_i2c_batch_ops(&ops);
2799        let mut remaining: &[u8] = &encoded;
2800        let mut decoded = Vec::new();
2801        while !remaining.is_empty() {
2802            let (op, rest) = postcard::take_from_bytes::<I2cBatchOp>(remaining).unwrap();
2803            decoded.push(op);
2804            remaining = rest;
2805        }
2806        assert_eq!(decoded.len(), 2);
2807        assert_eq!(decoded[0], ops[0]);
2808        assert_eq!(decoded[1], ops[1]);
2809    }
2810
2811    #[cfg(feature = "use-std")]
2812    #[test]
2813    fn spi_batch_ops_take_from_bytes() {
2814        let ops = [
2815            SpiBatchOp::Write {
2816                data: &[0x01, 0x02],
2817            },
2818            SpiBatchOp::Read { len: 8 },
2819            SpiBatchOp::Transfer { data: &[0xFF; 4] },
2820            SpiBatchOp::DelayNs { ns: 1000 },
2821        ];
2822        let encoded = encode_spi_batch_ops(&ops);
2823        let mut remaining: &[u8] = &encoded;
2824        let mut decoded = Vec::new();
2825        while !remaining.is_empty() {
2826            let (op, rest) = postcard::take_from_bytes::<SpiBatchOp>(remaining).unwrap();
2827            decoded.push(op);
2828            remaining = rest;
2829        }
2830        assert_eq!(decoded.len(), 4);
2831        for (d, o) in decoded.iter().zip(ops.iter()) {
2832            assert_eq!(d, o);
2833        }
2834    }
2835
2836    #[cfg(feature = "use-std")]
2837    #[test]
2838    fn i2c_batch_request_round_trip() {
2839        let ops = encode_i2c_batch_ops(&[
2840            I2cBatchOp::Write {
2841                data: &[0xAA, 0xBB],
2842            },
2843            I2cBatchOp::Read { len: 4 },
2844        ]);
2845        let req = I2cBatchRequest {
2846            address: 0x50,
2847            count: 2,
2848            ops: &ops,
2849        };
2850        let bytes = to_allocvec(&req).unwrap();
2851        let decoded: I2cBatchRequest = from_bytes(&bytes).unwrap();
2852        assert_eq!(decoded, req);
2853    }
2854
2855    #[cfg(feature = "use-std")]
2856    #[test]
2857    fn spi_batch_request_round_trip() {
2858        let ops = encode_spi_batch_ops(&[
2859            SpiBatchOp::Write {
2860                data: &[0x01, 0x02],
2861            },
2862            SpiBatchOp::Read { len: 8 },
2863            SpiBatchOp::Transfer { data: &[0xFF; 4] },
2864            SpiBatchOp::DelayNs { ns: 1000 },
2865        ]);
2866        let req = SpiBatchRequest {
2867            cs_pin: 2,
2868            count: 4,
2869            ops: &ops,
2870        };
2871        let bytes = to_allocvec(&req).unwrap();
2872        let decoded: SpiBatchRequest = from_bytes(&bytes).unwrap();
2873        assert_eq!(decoded, req);
2874    }
2875
2876    #[test]
2877    fn i2c_batch_error_round_trip() {
2878        let err = I2cBatchError {
2879            failed_op: 3,
2880            kind: I2cError::NoAcknowledge,
2881        };
2882        let bytes = to_allocvec(&err).unwrap();
2883        let decoded: I2cBatchError = from_bytes(&bytes).unwrap();
2884        assert_eq!(decoded, err);
2885    }
2886
2887    #[test]
2888    fn spi_batch_error_round_trip() {
2889        let err = SpiBatchError {
2890            failed_op: 1,
2891            kind: SpiError::Other,
2892        };
2893        let bytes = to_allocvec(&err).unwrap();
2894        let decoded: SpiBatchError = from_bytes(&bytes).unwrap();
2895        assert_eq!(decoded, err);
2896    }
2897
2898    #[cfg(feature = "use-std")]
2899    #[test]
2900    fn encode_i2c_empty_batch() {
2901        let ops = encode_i2c_batch_ops(&[]);
2902        assert!(ops.is_empty());
2903        assert_eq!(i2c_batch_response_len(&[]), 0);
2904    }
2905
2906    #[cfg(feature = "use-std")]
2907    #[test]
2908    fn encode_spi_empty_batch() {
2909        let ops = encode_spi_batch_ops(&[]);
2910        assert!(ops.is_empty());
2911        assert_eq!(spi_batch_response_len(&[]), 0);
2912    }
2913
2914    #[test]
2915    fn i2c_batch_response_len_mixed() {
2916        let ops = [
2917            I2cBatchOp::Write {
2918                data: &[0x00, 0x10],
2919            },
2920            I2cBatchOp::Read { len: 16 },
2921            I2cBatchOp::Write {
2922                data: &[0x00, 0x20],
2923            },
2924            I2cBatchOp::Read { len: 32 },
2925        ];
2926        assert_eq!(i2c_batch_response_len(&ops), 48);
2927    }
2928
2929    #[test]
2930    fn spi_batch_response_len_mixed() {
2931        let ops = [
2932            SpiBatchOp::Write {
2933                data: &[0x01, 0x02],
2934            },
2935            SpiBatchOp::Read { len: 8 },
2936            SpiBatchOp::Transfer { data: &[0xFF; 4] },
2937            SpiBatchOp::DelayNs { ns: 1000 },
2938        ];
2939        // Read(8) + Transfer(4) = 12
2940        assert_eq!(spi_batch_response_len(&ops), 12);
2941    }
2942
2943    #[cfg(feature = "use-std")]
2944    #[test]
2945    #[should_panic(expected = "too many batch operations")]
2946    fn encode_i2c_batch_ops_panics_over_limit() {
2947        let ops: Vec<I2cBatchOp> = (0..MAX_BATCH_OPS + 1)
2948            .map(|_| I2cBatchOp::Read { len: 1 })
2949            .collect();
2950        encode_i2c_batch_ops(&ops);
2951    }
2952
2953    #[cfg(feature = "use-std")]
2954    #[test]
2955    #[should_panic(expected = "too many batch operations")]
2956    fn encode_spi_batch_ops_panics_over_limit() {
2957        let ops: Vec<SpiBatchOp> = (0..MAX_BATCH_OPS + 1)
2958            .map(|_| SpiBatchOp::Read { len: 1 })
2959            .collect();
2960        encode_spi_batch_ops(&ops);
2961    }
2962
2963    #[cfg(feature = "use-std")]
2964    #[test]
2965    fn i2c_batch_ops_at_max_limit() {
2966        let ops: Vec<I2cBatchOp> = (0..MAX_BATCH_OPS)
2967            .map(|_| I2cBatchOp::Read { len: 1 })
2968            .collect();
2969        let encoded = encode_i2c_batch_ops(&ops);
2970        // Verify we can decode all ops back
2971        let mut remaining: &[u8] = &encoded;
2972        let mut count = 0;
2973        while !remaining.is_empty() {
2974            let (_, rest) = postcard::take_from_bytes::<I2cBatchOp>(remaining).unwrap();
2975            remaining = rest;
2976            count += 1;
2977        }
2978        assert_eq!(count, MAX_BATCH_OPS);
2979        assert_eq!(i2c_batch_response_len(&ops), MAX_BATCH_OPS);
2980    }
2981
2982    #[cfg(feature = "use-std")]
2983    #[test]
2984    fn spi_batch_ops_at_max_limit() {
2985        let ops: Vec<SpiBatchOp> = (0..MAX_BATCH_OPS)
2986            .map(|_| SpiBatchOp::Read { len: 1 })
2987            .collect();
2988        let encoded = encode_spi_batch_ops(&ops);
2989        let mut remaining: &[u8] = &encoded;
2990        let mut count = 0;
2991        while !remaining.is_empty() {
2992            let (_, rest) = postcard::take_from_bytes::<SpiBatchOp>(remaining).unwrap();
2993            remaining = rest;
2994            count += 1;
2995        }
2996        assert_eq!(count, MAX_BATCH_OPS);
2997        assert_eq!(spi_batch_response_len(&ops), MAX_BATCH_OPS);
2998    }
2999
3000    #[test]
3001    fn i2c_batch_write_only_response_len() {
3002        let ops = [
3003            I2cBatchOp::Write {
3004                data: &[0x00, 0x10],
3005            },
3006            I2cBatchOp::Write { data: &[0xFF; 32] },
3007        ];
3008        // No reads → zero response bytes
3009        assert_eq!(i2c_batch_response_len(&ops), 0);
3010    }
3011
3012    #[test]
3013    #[cfg(feature = "use-std")]
3014    fn i2c_batch_error_display() {
3015        let err = I2cBatchError {
3016            failed_op: 2,
3017            kind: I2cError::Bus,
3018        };
3019        assert_eq!(
3020            format!("{err}"),
3021            "I2C batch operation 2 failed: I2C bus error"
3022        );
3023    }
3024
3025    #[test]
3026    #[cfg(feature = "use-std")]
3027    fn spi_batch_error_display() {
3028        let err = SpiBatchError {
3029            failed_op: 0,
3030            kind: SpiError::Other,
3031        };
3032        assert_eq!(format!("{err}"), "SPI batch operation 0 failed: SPI error");
3033    }
3034
3035    // --- 1-Wire round-trip tests ---
3036
3037    #[test]
3038    fn onewire_read_request_round_trip() {
3039        let req = OneWireReadRequest { len: 9 };
3040        let bytes = to_allocvec(&req).unwrap();
3041        let decoded: OneWireReadRequest = from_bytes(&bytes).unwrap();
3042        assert_eq!(req, decoded);
3043    }
3044
3045    #[test]
3046    fn onewire_write_request_round_trip() {
3047        let data = [0xCC, 0x44];
3048        let req = OneWireWriteRequest { data: &data };
3049        let bytes = to_allocvec(&req).unwrap();
3050        let decoded: OneWireWriteRequest = from_bytes(&bytes).unwrap();
3051        assert_eq!(req, decoded);
3052    }
3053
3054    #[test]
3055    fn onewire_write_pullup_request_round_trip() {
3056        let data = [0xCC, 0x44];
3057        let req = OneWireWritePullupRequest {
3058            data: &data,
3059            pullup_duration_ms: 750,
3060        };
3061        let bytes = to_allocvec(&req).unwrap();
3062        let decoded: OneWireWritePullupRequest = from_bytes(&bytes).unwrap();
3063        assert_eq!(req, decoded);
3064    }
3065
3066    #[test]
3067    fn onewire_error_variants_round_trip() {
3068        for err in [
3069            OneWireError::NoPresence,
3070            OneWireError::BusError,
3071            OneWireError::BufferTooLong,
3072            OneWireError::Other,
3073            OneWireError::Unsupported,
3074        ] {
3075            let bytes = to_allocvec(&err).unwrap();
3076            let decoded: OneWireError = from_bytes(&bytes).unwrap();
3077            assert_eq!(err, decoded);
3078        }
3079    }
3080
3081    #[test]
3082    #[cfg(feature = "use-std")]
3083    fn onewire_error_display() {
3084        assert_eq!(
3085            format!("{}", OneWireError::NoPresence),
3086            "no device present on 1-Wire bus"
3087        );
3088        assert_eq!(format!("{}", OneWireError::BusError), "1-Wire bus error");
3089        assert_eq!(
3090            format!("{}", OneWireError::BufferTooLong),
3091            "buffer exceeds firmware limit"
3092        );
3093        assert_eq!(format!("{}", OneWireError::Other), "1-Wire error");
3094        assert_eq!(
3095            format!("{}", OneWireError::Unsupported),
3096            "1-Wire not supported on this hardware"
3097        );
3098    }
3099
3100    #[test]
3101    fn onewire_error_discriminants_are_stable() {
3102        assert_eq!(to_allocvec(&OneWireError::NoPresence).unwrap(), [0]);
3103        assert_eq!(to_allocvec(&OneWireError::BusError).unwrap(), [1]);
3104        assert_eq!(to_allocvec(&OneWireError::BufferTooLong).unwrap(), [2]);
3105        assert_eq!(to_allocvec(&OneWireError::Other).unwrap(), [3]);
3106        assert_eq!(to_allocvec(&OneWireError::Unsupported).unwrap(), [4]);
3107    }
3108
3109    #[test]
3110    fn onewire_read_request_wire_stability() {
3111        let req = OneWireReadRequest { len: 9 };
3112        let bytes = to_allocvec(&req).unwrap();
3113        let canonical = bytes.clone();
3114        let decoded: OneWireReadRequest = from_bytes(&bytes).unwrap();
3115        assert_eq!(decoded, req);
3116        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
3117    }
3118
3119    #[test]
3120    fn onewire_write_pullup_request_wire_stability() {
3121        let data = [0xCC, 0x44];
3122        let req = OneWireWritePullupRequest {
3123            data: &data,
3124            pullup_duration_ms: 750,
3125        };
3126        let bytes = to_allocvec(&req).unwrap();
3127        let canonical = bytes.clone();
3128        let decoded: OneWireWritePullupRequest = from_bytes(&bytes).unwrap();
3129        assert_eq!(decoded, req);
3130        assert_eq!(to_allocvec(&decoded).unwrap(), canonical);
3131    }
3132
3133    #[test]
3134    fn onewire_read_request_zero_len() {
3135        let req = OneWireReadRequest { len: 0 };
3136        let bytes = to_allocvec(&req).unwrap();
3137        let decoded: OneWireReadRequest = from_bytes(&bytes).unwrap();
3138        assert_eq!(req, decoded);
3139    }
3140
3141    #[test]
3142    fn onewire_read_request_max_len() {
3143        let req = OneWireReadRequest { len: u16::MAX };
3144        let bytes = to_allocvec(&req).unwrap();
3145        let decoded: OneWireReadRequest = from_bytes(&bytes).unwrap();
3146        assert_eq!(req, decoded);
3147    }
3148
3149    #[test]
3150    fn onewire_write_request_empty() {
3151        let req = OneWireWriteRequest { data: &[] };
3152        let bytes = to_allocvec(&req).unwrap();
3153        let decoded: OneWireWriteRequest = from_bytes(&bytes).unwrap();
3154        assert_eq!(req, decoded);
3155    }
3156
3157    #[test]
3158    fn onewire_search_response_some_round_trip() {
3159        let resp: OneWireSearchResponse = Ok(Some(0x0028_FF12_3456_7800));
3160        let bytes = to_allocvec(&resp).unwrap();
3161        let decoded: OneWireSearchResponse = from_bytes(&bytes).unwrap();
3162        assert_eq!(resp, decoded);
3163    }
3164
3165    #[test]
3166    fn onewire_search_response_none_round_trip() {
3167        let resp: OneWireSearchResponse = Ok(None);
3168        let bytes = to_allocvec(&resp).unwrap();
3169        let decoded: OneWireSearchResponse = from_bytes(&bytes).unwrap();
3170        assert_eq!(resp, decoded);
3171    }
3172
3173    #[test]
3174    fn onewire_reset_response_round_trip() {
3175        for present in [true, false] {
3176            let resp: OneWireResetResponse = Ok(present);
3177            let bytes = to_allocvec(&resp).unwrap();
3178            let decoded: OneWireResetResponse = from_bytes(&bytes).unwrap();
3179            assert_eq!(resp, decoded);
3180        }
3181    }
3182
3183    // --- Logic Capture Tests ---
3184
3185    // --- DeviceInfo / Capabilities round-trip tests ---
3186
3187    #[test]
3188    fn capabilities_bitflag_basics() {
3189        let caps = Capabilities::I2C | Capabilities::SPI;
3190        assert!(caps.contains(Capabilities::I2C));
3191        assert!(caps.contains(Capabilities::SPI));
3192        assert!(!caps.contains(Capabilities::UART));
3193        assert!(!caps.contains(Capabilities::GPIO));
3194        assert_eq!(caps.bits(), 0b11);
3195    }
3196
3197    #[test]
3198    fn capabilities_none_is_zero() {
3199        assert_eq!(Capabilities::NONE.bits(), 0);
3200        assert!(!Capabilities::NONE.contains(Capabilities::I2C));
3201    }
3202
3203    #[test]
3204    fn capabilities_round_trip() {
3205        let caps = Capabilities::I2C
3206            | Capabilities::SPI
3207            | Capabilities::GPIO
3208            | Capabilities::PWM
3209            | Capabilities::ADC;
3210        let bytes = to_allocvec(&caps).unwrap();
3211        let decoded: Capabilities = from_bytes(&bytes).unwrap();
3212        assert_eq!(caps, decoded);
3213    }
3214
3215    #[test]
3216    fn device_info_round_trip() {
3217        let info = DeviceInfo {
3218            fw_major: 0,
3219            fw_minor: 8,
3220            fw_patch: 0,
3221            schema_major: 0,
3222            schema_minor: 4,
3223            schema_patch: 0,
3224            hw_version: 1,
3225            capabilities: Capabilities::I2C
3226                | Capabilities::SPI
3227                | Capabilities::UART
3228                | Capabilities::GPIO
3229                | Capabilities::PWM
3230                | Capabilities::ADC
3231                | Capabilities::ONEWIRE,
3232        };
3233        let bytes = to_allocvec(&info).unwrap();
3234        let decoded: DeviceInfo = from_bytes(&bytes).unwrap();
3235        assert_eq!(info, decoded);
3236    }
3237
3238    #[test]
3239    fn device_info_no_capabilities_round_trip() {
3240        let info = DeviceInfo {
3241            fw_major: 1,
3242            fw_minor: 0,
3243            fw_patch: 0,
3244            schema_major: 1,
3245            schema_minor: 0,
3246            schema_patch: 0,
3247            hw_version: 2,
3248            capabilities: Capabilities::NONE,
3249        };
3250        let bytes = to_allocvec(&info).unwrap();
3251        let decoded: DeviceInfo = from_bytes(&bytes).unwrap();
3252        assert_eq!(info, decoded);
3253    }
3254}