API guide¶
How to do the common jobs with the driver: identify the module, measure, read raw data, measure without blocking and handle errors.
Every snippet on this page is compiled by the host CI from
tests/docs/api_tasks.cpp, with Sensor standing for fdo2::Driver<MyUart>. The
API reference lists every member.
The shape of the API¶
One fdo2::Driver<UartT> object per sensor. Each command is one call that sends the command, waits for the
reply and returns a DriverResult<T>.
| Call | Command | Returns |
|---|---|---|
ReadVersion() |
#VERS |
VersionInfo: device id, channels, firmware, sensors |
ReadUniqueId() |
#IDNR |
uint64_t |
MeasureMoxy(timeout_ms) |
#MOXY |
MoxyReading: pO2, temperature, status |
MeasureMraw(timeout_ms) |
#MRAW |
MrawReading: the above plus dphi, signal, ambient light, pressure, humidity |
FlashLogo() |
#LOGO |
DriverResult<void> |
SendMeasure(), TryReadReplyLine(), ParseMoxyLine(), ParseMrawLine(), ParseVersionLine() |
any of the above | The same exchange in two halves |
A DriverResult<T> has value, error and ok(), and converts to bool. The driver never throws and never
allocates.
Identify the module¶
const auto version = sensor.ReadVersion(); // #VERS D N R S
if (!version) {
std::printf("#VERS failed: %s\n", fdo2::ToString(version.error).data());
return false;
}
std::printf("device %" PRId32 ", firmware %.2f, sensors 0x%" PRIX32 "\n", version.value.device_id,
fdo2::FirmwareRevisionToDouble(version.value.firmware_revision),
static_cast<uint32_t>(version.value.sensor_types)); // 15: O2, T, P, RH
const auto id = sensor.ReadUniqueId(); // #IDNR N, unsigned 64-bit
if (id) {
std::printf("unique id %" PRIu64 "\n", id.value);
}
Call it once after power-up (wait kFdo2G2PowerUpSettleMs first). Device id 8 confirms an FDO2-G2; the
firmware revision tells you whether the noise erratum applies.
Measure oxygen¶
const auto m = sensor.MeasureMoxy(); // #MOXY O T S
if (!m) {
return false; // Timeout, ProtocolError or DeviceError
}
if (!fdo2::MoxyStatusOkForOxygen(m.value.status_u32)) {
return false; // S > 1: O and T are or may be wrong
}
const double po2_hpa = m.value.p_o2_hpa; // O x 10^-3
const double temp_c = m.value.temp_c; // T x 10^-3
#MOXY is the shortest exchange: one measurement, three fields. A successful call means the module answered;
the status check tells you whether the answer is a valid oxygen reading. A status of 1 (amplification reduced) is
still valid.
Read raw data and volume percent¶
const auto r = sensor.MeasureMraw(); // #MRAW O T S D I A P H
if (!r || !fdo2::MoxyStatusOkForOxygen(r.value.status_u32)) {
return false;
}
// Valid when the gas at the membrane is at the pressure measured at the back of the housing.
const double percent_o2 = fdo2::VolumePercentO2(r.value.p_o2_hpa, r.value.pressure_mbar);
// Optics check: keep signal + ambient light under about 2000 mV (shade the sensor otherwise).
const bool too_bright = r.value.signal_intensity_mv + r.value.ambient_light_mv > 2000.0;
#MRAW takes the same measurement and adds the raw optical values and the pressure and humidity inside the
housing. Use it for diagnostics, for volume percent, or at a slower rate next to fast #MOXY reads, as the
fdo2_sensor_demo example does. UART protocol lists every field and its unit.
Measure without blocking¶
MeasureMoxy() blocks for the whole measurement, up to about 150 ms. A loop that serves other devices can split
the exchange: send the command, keep working, and pick up the reply when the UART has a complete line.
// Start a measurement, do other work, collect the reply when the UART has it.
class OxygenPoller {
public:
explicit OxygenPoller(Sensor& sensor) : sensor_(sensor) {}
void start() { pending_ = sensor_.SendMeasure(Sensor::Measure::Moxy) == fdo2::DriverError::None; }
// Call from your loop; true when a new reading was stored in `out`.
bool poll(fdo2::MoxyReading& out) {
char line[96];
if (!pending_ || sensor_.TryReadReplyLine(line, sizeof(line)) != fdo2::DriverError::None) {
return false; // no complete line yet; you own the timeout
}
pending_ = false;
int32_t device_error = 0;
const auto m = Sensor::ParseMoxyLine(line, &device_error);
if (!m || !fdo2::MoxyStatusOkForOxygen(m.value.status_u32)) {
return false;
}
out = m.value;
return true;
}
private:
Sensor& sensor_;
bool pending_ = false;
};
TryReadReplyLine() calls your port's read() with a timeout of 0, so the port must return a whole line or
nothing (Porting). You own the timeout: if no reply arrives within about 300 ms,
send the command again.
Set timeouts¶
sensor.SetMeasureTimeoutMs(400); // #MOXY / #MRAW: covers M = 3 or 4 (up to about 300 ms)
sensor.SetLineTimeoutMs(400); // #VERS, #IDNR
sensor.SetSlowCommandTimeoutMs(2000); // #LOGO: echoed after the ~1 s flash sequence
const auto m = sensor.MeasureMoxy(1000); // or per call, in ms
| Timeout | Default | Used by |
|---|---|---|
| Measurement | 250 ms | MeasureMoxy(), MeasureMraw() when called with 0 (the default argument) |
| Line | 400 ms | ReadVersion(), ReadUniqueId() |
| Slow command | 12000 ms | FlashLogo(): the echo comes after the LED has flashed, about 1 s |
The defaults fit the factory setting (M = 2, up to about 150 ms per measurement). A module set to M = 3 or 4 takes up to about 300 ms: raise the measurement timeout above that.
Handle errors and status¶
const auto m = sensor.MeasureMoxy();
switch (m.error) {
case fdo2::DriverError::None:
break;
case fdo2::DriverError::Timeout: // no reply: wiring, baud, power, still booting
case fdo2::DriverError::ProtocolError: // reply did not parse: noise, wrong baud, broadcast on
case fdo2::DriverError::CrcMismatch: // #CRCE 1 checksum wrong: corrupted in transit
sensor.MeasureMoxy(); // the data sheet's advice: send the command again
break;
case fdo2::DriverError::DeviceError: // #ERRO E
std::printf("#ERRO %" PRId32 "\n", sensor.LastDeviceErrorCode()); // -21, -22, -23: repeat; -26: unknown
break;
default:
std::printf("%s\n", fdo2::ToString(m.error).data());
break;
}
if (m && (m.value.status_u32 & fdo2::moxy_status::kWarnHumidityHigh) != 0U) {
std::printf("humidity in the housing above 90 %%RH\n");
}
| Signal | Meaning | What to do |
|---|---|---|
DriverError::Timeout |
No reply within the timeout | Check wiring, baud rate, supply; wait 1 s after power-up |
DriverError::ProtocolError |
A reply that did not parse: wrong header or field count, a value that is not a number in range, no CR at the end | Repeat the command; check the baud rate and broadcast mode |
DriverError::CrcMismatch |
With #CRCE 1: the reply's checksum is wrong, or missing while SetCrcRequired(true) |
Repeat the command; see optional CRC |
DriverError::DeviceError |
#ERRO E |
LastDeviceErrorCode(); error codes |
DriverError::BufferOverflow |
A reply longer than the line buffer | Noise on RX, or broadcast replies running together |
MoxyStatusOkForOxygen(S) is false |
Status bits other than bit 0 set | Do not use O and T; see the status bits |
LastDeviceErrorCode() is reset to 0 at the start of every command, so read it right after the call that
failed.
Identify a sensor on a port¶
return sensor.FlashLogo().ok(); // status LED flashes 4 times, about 1 s: which sensor is on this port
With several modules on several UARTs, FlashLogo() shows which connector belongs to which port. The module
answers only after the flash sequence, so the call blocks for about 1 s, up to the slow-command timeout.
Next¶
Configuration lists every setting and default; Troubleshooting maps symptoms to causes.