30inline bool AppendCmd(
char* buf, std::size_t cap, std::string_view cmd)
noexcept {
31 if (cmd.size() + 2U > cap) {
34 std::memcpy(buf, cmd.data(), cmd.size());
35 buf[cmd.size()] =
'\r';
36 buf[cmd.size() + 1U] =
'\0';
42 if (line ==
nullptr || line[0] ==
'\0') {
45 char* colon = std::strrchr(line,
':');
46 if (colon ==
nullptr || colon <= line + 1) {
49 if (colon[-1] !=
' ') {
52 const char* p = colon + 1;
54 if (std::isdigit(
static_cast<unsigned char>(*p)) == 0) {
62template <
typename UartT>
64 uint32_t timeout_ms)
noexcept {
69 const std::size_t max_rx =
sizeof(tmp) - 1U;
70 const std::size_t n = uart.read(tmp, max_rx, timeout_ms);
75 for (std::size_t i = 0; i < n; ++i) {
76 const char c =
static_cast<char>(tmp[i]);
93inline const char*
SkipWs(
const char* p)
noexcept {
94 while (*p !=
'\0' && std::isspace(
static_cast<unsigned char>(*p)) != 0) {
101 const char* tokens[], std::size_t max_tokens,
102 std::size_t* out_count)
noexcept {
104 const char* p =
SkipWs(line);
109 while (*p !=
'\0' && *out_count < max_tokens) {
110 if (bi + 1U >= store_cap) {
113 const std::size_t start = bi;
114 tokens[*out_count] = store + start;
115 while (*p !=
'\0' && std::isspace(
static_cast<unsigned char>(*p)) == 0) {
116 if (bi + 1U >= store_cap) {
121 if (bi + 1U >= store_cap) {
134inline int32_t
ParseI32(
const char* s,
bool* ok)
noexcept {
136 const long v = std::strtol(s, &end, 10);
138 *ok = (end != s) && (*end ==
'\0');
140 constexpr long kMin = -2147483648L;
141 constexpr long kMax = 2147483647L;
142 if (v < kMin || v > kMax) {
148 return static_cast<int32_t
>(v);
151inline uint32_t
ParseU32(
const char* s,
bool* ok)
noexcept {
153 const unsigned long v = std::strtoul(s, &end, 10);
155 *ok = (end != s) && (*end ==
'\0');
157 return static_cast<uint32_t
>(v);
160inline uint64_t
ParseU64(
const char* s,
bool* ok)
noexcept {
162 const unsigned long long v = std::strtoull(s, &end, 10);
164 *ok = (end != s) && (*end ==
'\0');
166 return static_cast<uint64_t
>(v);
170 return std::strncmp(tok0,
"#ERRO", 5) == 0;
179template <
typename UartT>
182 explicit Driver(UartT& uart) noexcept : uart_(uart) {}
193 last_device_error_ = 0;
199 const auto err = TransactLine(tx, line,
sizeof(line));
210 return FailErro<VersionInfo>(tok, nt);
212 if (nt < 5U || std::strncmp(tok[0],
"#VERS", 5) != 0) {
228 last_device_error_ = 0;
234 const auto err = TransactLine(tx, line,
sizeof(line));
245 return FailErro<uint64_t>(tok, nt);
247 if (nt != 2U || std::strncmp(tok[0],
"#IDNR", 5) != 0) {
260 last_device_error_ = 0;
266 const uint32_t tmo = (timeout_ms != 0U) ? timeout_ms : measure_timeout_ms_;
267 const auto err = TransactLine(tx, line,
sizeof(line), tmo);
278 return FailErro<MoxyReading>(tok, nt);
280 if (nt != 4U || std::strncmp(tok[0],
"#MOXY", 5) != 0) {
295 last_device_error_ = 0;
301 const uint32_t tmo = (timeout_ms != 0U) ? timeout_ms : measure_timeout_ms_;
302 const auto err = TransactLine(tx, line,
sizeof(line), tmo);
313 return FailErro<MrawReading>(tok, nt);
315 if (nt != 9U || std::strncmp(tok[0],
"#MRAW", 5) != 0) {
335 last_device_error_ = 0;
336 return SimpleEchoCommand(
"#LOGO", 5);
340 template <
typename T>
346 last_device_error_ = -1;
349 last_device_error_ = -1;
354 DriverResult<void> SimpleEchoCommand(
const char* cmd, std::size_t cmd_len)
noexcept {
356 if (cmd_len + 2U >
sizeof(tx)) {
360 std::memcpy(tx, cmd, cmd_len);
362 tx[cmd_len + 1] =
'\0';
363 const auto err = TransactLine(tx, line,
sizeof(line), line_timeout_ms_);
374 return FailErro<void>(tok, nt);
376 if (nt < 1U || std::strncmp(tok[0], cmd, cmd_len) != 0) {
382 DriverError TransactLine(
const char* tx,
char* line, std::size_t line_cap,
383 uint32_t timeout_ms = 0U)
noexcept {
385 uart_.write(
reinterpret_cast<const uint8_t*
>(tx), std::strlen(tx));
386 const uint32_t tmo = (timeout_ms != 0U) ? timeout_ms : line_timeout_ms_;
391 uint32_t line_timeout_ms_{400};
392 uint32_t measure_timeout_ms_{250};
393 uint32_t slow_timeout_ms_{12000};
394 int32_t last_device_error_{0};
FDO2-G2 UART client.
Definition fdo2_driver.hpp:180
DriverResult< MoxyReading > MeasureMoxy(uint32_t timeout_ms=0U) noexcept
Single oxygen + temperature + status round-trip (typically < ~150 ms for M=2).
Definition fdo2_driver.hpp:259
DriverResult< uint64_t > ReadUniqueId() noexcept
Definition fdo2_driver.hpp:227
Driver(UartT &uart) noexcept
Definition fdo2_driver.hpp:182
uint32_t GetMeasureTimeoutMs() const noexcept
Definition fdo2_driver.hpp:189
void SetLineTimeoutMs(uint32_t ms) noexcept
Definition fdo2_driver.hpp:184
DriverResult< VersionInfo > ReadVersion() noexcept
Definition fdo2_driver.hpp:192
DriverResult< void > FlashLogo() noexcept
Flash the status LED (identification).
Definition fdo2_driver.hpp:334
int32_t LastDeviceErrorCode() const noexcept
Definition fdo2_driver.hpp:190
void SetSlowCommandTimeoutMs(uint32_t ms) noexcept
Definition fdo2_driver.hpp:186
void SetMeasureTimeoutMs(uint32_t ms) noexcept
Definition fdo2_driver.hpp:185
uint32_t GetLineTimeoutMs() const noexcept
Definition fdo2_driver.hpp:188
DriverResult< MrawReading > MeasureMraw(uint32_t timeout_ms=0U) noexcept
Same measurement plus raw optics / vent-path pressure / internal RH.
Definition fdo2_driver.hpp:294
FDO2-G2 UART types, scaling, and status decoding (PyroScience data sheet v5).
CRTP byte transport for the PyroScience Unified Protocol (PSUP).
DriverError Tokenize(const char *line, char *store, std::size_t store_cap, const char *tokens[], std::size_t max_tokens, std::size_t *out_count) noexcept
Definition fdo2_driver.hpp:100
const char * SkipWs(const char *p) noexcept
Definition fdo2_driver.hpp:93
bool IsErroHeader(const char *tok0) noexcept
Definition fdo2_driver.hpp:169
int32_t ParseI32(const char *s, bool *ok) noexcept
Definition fdo2_driver.hpp:134
void StripOptionalModbusCrcSuffix(char *line) noexcept
If CRC is enabled, response is ... : <decimal>\\r. Strip from last " :".
Definition fdo2_driver.hpp:41
bool AppendCmd(char *buf, std::size_t cap, std::string_view cmd) noexcept
Definition fdo2_driver.hpp:30
uint64_t ParseU64(const char *s, bool *ok) noexcept
Definition fdo2_driver.hpp:160
uint32_t ParseU32(const char *s, bool *ok) noexcept
Definition fdo2_driver.hpp:151
DriverError ReadAsciiLine(UartT &uart, char *buf, std::size_t cap, uint32_t timeout_ms) noexcept
Definition fdo2_driver.hpp:63
MrawReading DecodeMraw(int32_t o_raw, int32_t t_raw, uint32_t s, int32_t d_raw, int32_t i_raw, int32_t a_raw, int32_t p_raw, int32_t h_raw) noexcept
Definition fdo2_types.hpp:150
DriverError
Driver-level error codes (stable for logging / telemetry).
Definition fdo2_types.hpp:28
MoxyReading DecodeMoxy(int32_t o_raw, int32_t t_raw, uint32_t s) noexcept
Definition fdo2_types.hpp:140
Definition fdo2_types.hpp:50
static constexpr DriverResult success(T v) noexcept
Definition fdo2_types.hpp:57
static constexpr DriverResult failure(DriverError e) noexcept
Definition fdo2_types.hpp:58
#VERS D N R S fields (FDO2-G2 data sheet §4.3).
Definition fdo2_types.hpp:76
int32_t device_id
D (8 for FDO2-G2).
Definition fdo2_types.hpp:77