Skip to content

Architecture

How the driver is put together: three layers, what each owns, and how one call reaches the sensor.

Layer diagram: the application calls the hardware-agnostic FDO2 driver, which uses the CRTP UART interface, which calls the platform port you write, which drives the UART lines of the PyroScience FDO2-G2 oxygen sensor.Layer diagram: the application calls the hardware-agnostic FDO2 driver, which uses the CRTP UART interface, which calls the platform port you write, which drives the UART lines of the PyroScience FDO2-G2 oxygen sensor.
Three layers, one of them yours
Figure description and sources

From top to bottom. Your application talks to one fdo2::Driver object, whose public calls (listed from inc/fdo2_driver.hpp) send the FDO2-G2's ASCII commands and decode the replies into readings in hPa, degrees Celsius, mbar and %RH. The communication interface (UartInterface in inc/fdo2_uart_interface.hpp) is a CRTP base that forwards bytes to your class. The platform port is the only code you write: a class that writes a command, returns one reply line and flushes the receive buffer. Below it is the sensor.

Drawn from: inc/fdo2_driver.hpp Driver public calls; inc/fdo2_uart_interface.hpp UartInterface; figure module docs/diagrams/figures/driver-layers.py.

The three layers

Layer Where You change it when
Hardware-agnostic core: fdo2::Driver<UartT> inc/fdo2_driver.hpp, inc/fdo2_types.hpp Never, for a new board
Communication interface: UartInterface<Derived> inc/fdo2_uart_interface.hpp Never
Platform port: your class, three methods Your project Once per MCU or board (Porting)

Everything above the port is the same on ESP32, STM32, a Linux host or the unit-test model. That is how the host tests run the real driver code without hardware.

How one call reaches the sensor

sequenceDiagram
    participant A as Application
    participant D as Driver
    participant P as Your port
    participant S as FDO2-G2
    A->>D: MeasureMoxy()
    D->>P: flush_rx()
    D->>P: write(MOXY command, CR)
    P->>S: 6 bytes at 19200 baud
    S-->>P: MOXY 203456 17892 0, CR (after up to 150 ms)
    D->>P: read(buf, 511, 250 ms)
    P-->>D: one line, CR included
    D->>D: check and strip CRC suffix, split, check header, field count and every value
    D->>D: ERRO reply: DeviceError, code in LastDeviceErrorCode()
    D-->>A: result with MoxyReading: 203.456 hPa, 17.892 C, status 0

Why CRTP

The interface is a class template parameter, not a virtual base class. The compiler sees your port's methods directly and inlines them; the driver has no vtable, allocates nothing and needs no RTTI. The cost is that the driver type names the port: fdo2::Driver<MyUart>. Code that has to work with several port types takes the driver as a template parameter too.

Results instead of exceptions

Every command returns DriverResult<T>: a value and an error (DriverError). It converts to bool, so if (!m) reads naturally, and fdo2::ToString(error) names the error for a log. An #ERRO reply from the module becomes DriverError::DeviceError, with the module's own code kept in LastDeviceErrorCode() until the next command.

The status word S is not an error to the driver: a measurement with fault bits set still returns DriverError::None, because the module did answer. Checking S is your job, as the datasheet requires; the API guide shows how.

Memory and timing

The driver allocates nothing. A call uses under 1 KB of stack: the largest buffer is a 512-byte receive buffer, then the reply line and its tokens. A call blocks for at most the timeout you set, plus whatever your port's write() takes.

Source map

Path Contents
inc/fdo2.hpp Umbrella header: everything below plus GetDriverVersion()
inc/fdo2_driver.hpp Driver<UartT>: commands, parsing, the split exchange
inc/fdo2_types.hpp DriverResult, DriverError, readings, scaling, status bits, VolumePercentO2()
inc/fdo2_uart_interface.hpp UartInterface<Derived>, the CRTP base of your port
inc/fdo2_version.h.in Version header template, filled in by CMake
tests/ Host unit tests and the compiled documentation snippets
examples/esp32/ ESP-IDF applications and the ESP-IDF port