SFM Flow Meter Driver 1.0.0
API reference
Loading...
Searching...
No Matches
sfm_types.hpp
Go to the documentation of this file.
1
18#pragma once
19
20#include "sfm_commands.hpp"
21
22#include <cstdint>
23#include <string_view>
24
25namespace sfm {
26
28enum class DriverError : uint8_t {
29 None = 0,
32 BusWrite,
33 BusRead,
34 NoData,
35 Crc,
40};
41
42constexpr std::string_view ToString(DriverError e) noexcept {
43 switch (e) {
44 case DriverError::None: return "None";
45 case DriverError::InvalidParameter: return "InvalidParameter";
46 case DriverError::NotInitialized: return "NotInitialized";
47 case DriverError::BusWrite: return "BusWrite";
48 case DriverError::BusRead: return "BusRead";
49 case DriverError::NoData: return "NoData";
50 case DriverError::Crc: return "Crc";
51 case DriverError::UnsupportedGas: return "UnsupportedGas";
52 case DriverError::NotSupported: return "NotSupported";
53 case DriverError::UnexpectedUnit: return "UnexpectedUnit";
54 case DriverError::VariantUnknown: return "VariantUnknown";
55 }
56 return "?";
57}
58
59template <typename T>
61 T value{};
63
64 constexpr bool ok() const noexcept { return error == DriverError::None; }
65 constexpr explicit operator bool() const noexcept { return ok(); }
66
67 static constexpr DriverResult success(T v) noexcept { return {v, DriverError::None}; }
68 static constexpr DriverResult failure(DriverError e) noexcept { return {T{}, e}; }
69};
70
71template <>
72struct DriverResult<void> {
74 constexpr bool ok() const noexcept { return error == DriverError::None; }
75 constexpr explicit operator bool() const noexcept { return ok(); }
76 static constexpr DriverResult success() noexcept { return {DriverError::None}; }
77 static constexpr DriverResult failure(DriverError e) noexcept { return {e}; }
78};
79
81enum class Family : uint8_t {
82 Sf06 = 0,
83 Sfx6000,
84};
85
86constexpr std::string_view ToString(Family f) noexcept {
87 return f == Family::Sfx6000 ? "SFx6000" : "SF06";
88}
89
91enum class Gas : uint8_t {
92 O2 = 0,
93 Air,
94 N2O,
95 CO2,
96 AirO2Mix,
97 N2OO2Mix,
98 CO2O2Mix,
99 Ar,
100 HeOx,
101 HeOxO2Mix,
102};
103
104constexpr std::string_view ToString(Gas g) noexcept {
105 switch (g) {
106 case Gas::O2: return "O2";
107 case Gas::Air: return "Air";
108 case Gas::N2O: return "N2O";
109 case Gas::CO2: return "CO2";
110 case Gas::AirO2Mix: return "Air/O2";
111 case Gas::N2OO2Mix: return "N2O/O2";
112 case Gas::CO2O2Mix: return "CO2/O2";
113 case Gas::Ar: return "Ar";
114 case Gas::HeOx: return "HeOx";
115 case Gas::HeOxO2Mix: return "HeOx/O2";
116 }
117 return "?";
118}
119
126constexpr uint16_t StartCommandFor(Family family, Gas g) noexcept {
127 if (family == Family::Sfx6000) {
128 switch (g) {
129 case Gas::O2: return cmd::kSfx6000StartGas0;
130 case Gas::Air: return cmd::kSfx6000StartGas1;
131 case Gas::CO2: return cmd::kSfx6000StartGas2;
132 case Gas::N2O: return cmd::kSfx6000StartGas3;
133 case Gas::Ar: return cmd::kSfx6000StartGas4;
135 case Gas::N2OO2Mix:
136 case Gas::CO2O2Mix:
137 case Gas::HeOx:
138 case Gas::HeOxO2Mix: return 0;
139 }
140 return 0;
141 }
142 switch (g) {
143 case Gas::O2: return cmd::kStartO2;
144 case Gas::Air: return cmd::kStartAir;
145 case Gas::N2O: return cmd::kStartN2O;
146 case Gas::CO2: return cmd::kStartCO2;
150 case Gas::HeOx: return cmd::kStartN2O; // 0x3615 is HeOx on the SFM3013-300-CLM
151 case Gas::HeOxO2Mix: return cmd::kStartN2OO2Mix; // 0x3639 is HeOx/O2 on the SFM3013-300-CLM
152 case Gas::Ar: return 0;
153 }
154 return 0;
155}
156
157static_assert(StartCommandFor(Family::Sf06, Gas::CO2) == 0x361E &&
159 "CO2 start code differs by family (SFM4300 data sheet / SFx6000 reference)");
160
161constexpr bool IsMixture(Gas g) noexcept {
162 return g == Gas::AirO2Mix || g == Gas::N2OO2Mix || g == Gas::CO2O2Mix || g == Gas::HeOxO2Mix;
163}
164
189
190constexpr std::string_view ToString(Variant v) noexcept {
191 switch (v) {
192 case Variant::Unknown: return "Unknown";
193 case Variant::Sfm4300_20_B: return "SFM4300-20-B";
194 case Variant::Sfm4300_20_O: return "SFM4300-20-O";
195 case Variant::Sfm4300_20_P: return "SFM4300-20-P";
196 case Variant::Sfm4300_50_B: return "SFM4300-50-B";
197 case Variant::Sfm4300_50_O: return "SFM4300-50-O";
198 case Variant::Sfm4300_50_P: return "SFM4300-50-P";
199 case Variant::Sfm6000D_50: return "SFM6000D-50";
200 case Variant::Sfm6000D_20: return "SFM6000D-20";
201 case Variant::Sfm6000D_5: return "SFM6000D-5";
202 case Variant::Sfm3003_300_CL: return "SFM3003-300-CL";
203 case Variant::Sfm3003_300_CE: return "SFM3003-300-CE";
204 case Variant::Sfm3013_300_CL: return "SFM3013-300-CL";
205 case Variant::Sfm3013_300_CLM: return "SFM3013-300-CLM";
206 case Variant::Sfm3019_240: return "SFM3019";
207 case Variant::Sfm3119_240_CL: return "SFM3119-240-CL";
208 }
209 return "?";
210}
211
213constexpr Family FamilyOf(Variant v) noexcept {
214 switch (v) {
218 default: return Family::Sf06;
219 }
220}
221
227constexpr Variant VariantFromProductId(uint32_t product_id) noexcept {
228 switch (product_id & 0xFFFFFF00U) {
229 case 0x06021100U: return Variant::Sfm6000D_50;
230 case 0x06021200U: return Variant::Sfm6000D_20;
231 case 0x06021400U: return Variant::Sfm6000D_5;
232 default: break;
233 }
234 /* SF06: 0x0403 <variant> <revision>. The low byte is a revision — parts
235 * ship as 0x...1x and 0x...01 (an SFM4300-20-P read 0x04030301 on the
236 * 2026-10-02 bench) — so it is ignored. */
237 switch (product_id & 0xFFFFFF00U) {
238 case 0x04030100U: return Variant::Sfm4300_20_B;
239 case 0x04030200U: return Variant::Sfm4300_20_O;
240 case 0x04030300U: return Variant::Sfm4300_20_P;
241 case 0x04030900U: return Variant::Sfm4300_50_B;
242 case 0x04030700U: return Variant::Sfm4300_50_O;
243 case 0x04030600U: return Variant::Sfm4300_50_P;
244 case 0x04020100U: return Variant::Sfm3003_300_CL;
245 case 0x04020700U: return Variant::Sfm3003_300_CE;
246 case 0x04020500U: return Variant::Sfm3013_300_CL;
247 case 0x04020200U: return Variant::Sfm3013_300_CLM;
248 case 0x04020600U: return Variant::Sfm3019_240;
249 case 0x04040100U: return Variant::Sfm3119_240_CL;
250 default: return Variant::Unknown;
251 }
252}
253
255constexpr float FullScaleSlm(Variant v) noexcept {
256 switch (v) {
259 case Variant::Sfm4300_20_P: return 20.0f;
262 case Variant::Sfm4300_50_P: return 50.0f;
263 case Variant::Sfm6000D_50: return 50.0f;
264 case Variant::Sfm6000D_20: return 20.0f;
265 case Variant::Sfm6000D_5: return 5.0f;
269 case Variant::Sfm3013_300_CLM: return 300.0f;
271 case Variant::Sfm3119_240_CL: return 240.0f;
272 case Variant::Unknown: return 0.0f;
273 }
274 return 0.0f;
275}
276
288constexpr bool SupportsGas(Variant v, Gas g) noexcept {
289 switch (v) {
298 return g == Gas::O2 || g == Gas::Air || g == Gas::AirO2Mix;
300 return g == Gas::O2 || g == Gas::Air || g == Gas::AirO2Mix || g == Gas::HeOx ||
301 g == Gas::HeOxO2Mix;
305 return g != Gas::Ar && g != Gas::HeOx && g != Gas::HeOxO2Mix;
309 return g != Gas::N2OO2Mix && g != Gas::CO2O2Mix && g != Gas::HeOx &&
310 g != Gas::HeOxO2Mix;
311 case Variant::Unknown:
312 return g == Gas::O2 || g == Gas::Air;
313 }
314 return false;
315}
316
318constexpr bool GasNeedsKnownFamily(Gas g) noexcept {
319 return g != Gas::O2 && g != Gas::Air;
320}
321
324 uint32_t product_id{0};
325 uint8_t serial[8]{};
327};
328
330struct Scaling {
331 int16_t scale{1};
332 int16_t offset{0};
333 uint16_t unit{0};
334 int16_t full_scale_raw{0};
335 uint16_t gas_id{0};
336 bool valid{false};
337
339 constexpr float FullScaleFlow() const noexcept {
340 return full_scale_raw != 0 ? ToFlow(full_scale_raw) : 0.0f;
341 }
342
344 constexpr float ToFlow(int16_t raw) const noexcept {
345 return scale != 0 ? (static_cast<float>(raw) - static_cast<float>(offset)) /
346 static_cast<float>(scale)
347 : 0.0f;
348 }
349};
350
359 uint16_t raw{0};
360
361 constexpr uint8_t command_nibble() const noexcept { return static_cast<uint8_t>(raw >> 12); }
362 constexpr bool exponential_smoothing() const noexcept { return (raw & (1U << 11)) != 0U; }
363 constexpr bool fixed_n_averaging() const noexcept { return (raw & (1U << 10)) != 0U; }
364 constexpr uint16_t gas_fraction_permille() const noexcept { return raw & 0x03FFU; }
365 constexpr bool pure_gas() const noexcept {
367 }
368};
369
377constexpr bool StatusNibbleIsKnown(Family family, uint8_t nibble) noexcept {
378 const uint8_t n = nibble & 0x0FU;
379 if (family == Family::Sfx6000) {
380 return n <= 0x4U || n == 0xAU;
381 }
382 return n <= 0x3U || (n >= 0x6U && n <= 0x8U);
383}
384
392constexpr Gas GasFromStatusNibble(Family family, uint8_t nibble) noexcept {
393 if (family == Family::Sfx6000) {
394 switch (nibble & 0x0FU) {
395 case 0x0: return Gas::O2;
396 case 0x1: return Gas::Air;
397 case 0x2: return Gas::CO2;
398 case 0x3: return Gas::N2O;
399 case 0x4: return Gas::Ar;
400 case 0xA: return Gas::AirO2Mix;
401 default: return Gas::Air;
402 }
403 }
404 switch (nibble & 0x0FU) {
405 case 0x0: return Gas::O2;
406 case 0x1: return Gas::Air;
407 case 0x2: return Gas::N2O;
408 case 0x3: return Gas::CO2;
409 case 0x6: return Gas::AirO2Mix;
410 case 0x7: return Gas::N2OO2Mix;
411 case 0x8: return Gas::CO2O2Mix;
412 default: return Gas::Air; // 4, 5 reserved; 9 to 15 unused
413 }
414}
415
416static_assert(GasFromStatusNibble(Family::Sf06, 0x6) == Gas::AirO2Mix,
417 "SFM4300 data sheet Table 5: Air/O2 (0x3632) reports 0b0110");
418
423constexpr Gas GasFromStatusNibble(Variant variant, uint8_t nibble) noexcept {
424 if (variant == Variant::Sfm3013_300_CLM) {
425 if ((nibble & 0x0FU) == 0x2U) {
426 return Gas::HeOx;
427 }
428 if ((nibble & 0x0FU) == 0x7U) {
429 return Gas::HeOxO2Mix;
430 }
431 }
432 return GasFromStatusNibble(FamilyOf(variant), nibble);
433}
434
437 int16_t flow_raw{0};
438 int16_t temperature_raw{0};
440 float flow{0.0f};
441 float temperature_c{0.0f};
442 bool temperature_valid{true};
443};
444
445} // namespace sfm
constexpr uint16_t kSfx6000StartGas3
N2O (CO2 on SF06!)
Definition sfm_commands.hpp:60
constexpr uint16_t kSfx6000StartGas2
CO2 (N2O on SF06!)
Definition sfm_commands.hpp:59
constexpr uint16_t kSfx6000StartGas4
Ar.
Definition sfm_commands.hpp:61
constexpr uint16_t kStartCO2O2Mix
argument: O2 volume fraction (‰)
Definition sfm_commands.hpp:55
constexpr uint16_t kStartAirO2Mix
argument: O2 volume fraction (‰)
Definition sfm_commands.hpp:53
constexpr uint16_t kStartN2O
Definition sfm_commands.hpp:51
constexpr uint16_t kStartN2OO2Mix
argument: O2 volume fraction (‰)
Definition sfm_commands.hpp:54
constexpr uint16_t kSfx6000StartGas1
Air.
Definition sfm_commands.hpp:58
constexpr uint16_t kStartAir
Definition sfm_commands.hpp:50
constexpr uint16_t kSfx6000StartMix0
Gas0 in Gas1 (O2 in Air); argument ‰
Definition sfm_commands.hpp:62
constexpr uint16_t kStartO2
Definition sfm_commands.hpp:49
constexpr uint16_t kStartCO2
Definition sfm_commands.hpp:52
constexpr uint16_t kSfx6000StartGas0
O2.
Definition sfm_commands.hpp:57
Definition sfm.hpp:20
constexpr Family FamilyOf(Variant v) noexcept
Family of a known variant (Sf06 for Unknown — the caller's assumption).
Definition sfm_types.hpp:213
constexpr uint16_t StartCommandFor(Family family, Gas g) noexcept
Start-measurement command code for g on family.
Definition sfm_types.hpp:126
constexpr uint16_t kStatusPureGasFraction
Status-word gas fraction value for a pure calibration gas (bits 9:0).
Definition sfm_commands.hpp:98
DriverError
Driver-level error codes (stable for logging / telemetry).
Definition sfm_types.hpp:28
@ BusWrite
command write NACKed / bus error
@ VariantUnknown
gas needs a known part family (identity not read)
@ Crc
CRC-8 mismatch on a received word.
@ InvalidParameter
bad argument (e.g. averaging window > 128)
@ BusRead
command reply (identifier, scaling) NACKed / bus error
@ UnexpectedUnit
scaling unusable: scale 0, or unit ≠ 0x0148 on a known SF06 part
@ NotInitialized
transport EnsureInitialized() failed
@ NotSupported
transport lacks the feature (e.g. general call)
@ UnsupportedGas
gas / mixture not calibrated on this variant
@ NoData
result-buffer read NACKed — no fresh sample yet / not measuring
constexpr bool SupportsGas(Variant v, Gas g) noexcept
Whether a variant carries a factory calibration for g.
Definition sfm_types.hpp:288
constexpr std::string_view ToString(DriverError e) noexcept
Definition sfm_types.hpp:42
Gas
Calibrated gas / gas-mixture lookup table selector (family-neutral).
Definition sfm_types.hpp:91
@ CO2
SF06 Gas 3 (SFM4300-20 only) · SFx6000 Gas 2.
@ HeOx
SF06 Gas 2 on SFM3013-300-CLM only: heliox 80/20 (code 0x3615)
@ CO2O2Mix
SF06 mixture 2 — SFM4300-20 only.
@ Air
SF06 Gas 1 · SFx6000 Gas 1.
@ Ar
SFx6000 Gas 4 only.
@ N2O
SF06 Gas 2 (SFM4300-20 only) · SFx6000 Gas 3.
@ O2
SF06 Gas 0 · SFx6000 Gas 0.
@ AirO2Mix
O2 in Air — argument: O2 volume fraction (‰)
@ N2OO2Mix
SF06 mixture 1 — SFM4300-20 only.
@ HeOxO2Mix
SF06 mixture 1 on SFM3013-300-CLM only: O2 in HeOx (code 0x3639)
Family
Part family: decides command meaning, frame layout and optional features.
Definition sfm_types.hpp:81
@ Sf06
SFM4300, SFM3003/3013/3019/3119.
@ Sfx6000
SFM6000D (SFC6000D controllers share the interface)
constexpr bool StatusNibbleIsKnown(Family family, uint8_t nibble) noexcept
Whether nibble (status bits 15:12) names a start command of family.
Definition sfm_types.hpp:377
constexpr bool IsMixture(Gas g) noexcept
Definition sfm_types.hpp:161
constexpr Variant VariantFromProductId(uint32_t product_id) noexcept
Decode a 32-bit product identifier.
Definition sfm_types.hpp:227
constexpr Gas GasFromStatusNibble(Family family, uint8_t nibble) noexcept
Map the status command nibble back onto a gas selector (per family).
Definition sfm_types.hpp:392
constexpr float FullScaleSlm(Variant v) noexcept
Full-scale Air / O2 flow [slm] for a known variant (0 when unknown).
Definition sfm_types.hpp:255
constexpr bool GasNeedsKnownFamily(Gas g) noexcept
True when g's start code differs between families (needs a known variant).
Definition sfm_types.hpp:318
Variant
Part variant decoded from the product identifier.
Definition sfm_types.hpp:171
@ Sfm4300_20_P
0x0403031x — 20 slm, push-in fittings
@ Sfm6000D_50
0x060211xx — 50 slm (Air/O2; 20 slm CO2/N2O/Ar)
@ Sfm6000D_20
0x060212xx — 20 slm (Air/O2; 10 slm CO2/N2O/Ar)
@ Sfm4300_20_B
0x0403011x — 20 slm, basemount
@ Sfm3013_300_CLM
0x040202xx — −30…300 slm, also heliox, address 0x2F
@ Sfm4300_20_O
0x0403021x — 20 slm, O-rings
@ Sfm6000D_5
0x060214xx — 5 slm (Air/O2; 2 slm CO2/N2O/Ar)
@ Sfm4300_50_P
0x0403061x — 50 slm, push-in fittings
@ Sfm4300_50_B
0x0403091x — 50 slm, basemount
@ Sfm3003_300_CL
0x040201xx — −30…300 slm, address 0x28
@ Sfm3003_300_CE
0x040207xx — −150…300 slm, address 0x2D
@ Sfm3019_240
0x040206xx — −10…240 slm, address 0x2E
@ Sfm3119_240_CL
0x040401xx — −10…240 slm, address 0x29
@ Sfm3013_300_CL
0x040205xx — −30…300 slm, address 0x2F
@ Sfm4300_50_O
0x0403071x — 50 slm, O-rings
I2C command codes, addresses, timing and CRC-8 for Sensirion flow meters: the SF06 family (SFM4300 da...
static constexpr DriverResult success() noexcept
Definition sfm_types.hpp:76
static constexpr DriverResult failure(DriverError e) noexcept
Definition sfm_types.hpp:77
constexpr bool ok() const noexcept
Definition sfm_types.hpp:74
Definition sfm_types.hpp:60
constexpr bool ok() const noexcept
Definition sfm_types.hpp:64
static constexpr DriverResult failure(DriverError e) noexcept
Definition sfm_types.hpp:68
T value
Definition sfm_types.hpp:61
static constexpr DriverResult success(T v) noexcept
Definition sfm_types.hpp:67
DriverError error
Definition sfm_types.hpp:62
One continuous-mode sample (data sheet §4.3.1 read frame).
Definition sfm_types.hpp:436
float flow
in device unit (slm for SFM4300)
Definition sfm_types.hpp:440
int16_t flow_raw
Definition sfm_types.hpp:437
int16_t temperature_raw
Definition sfm_types.hpp:438
StatusWord status
Definition sfm_types.hpp:439
float temperature_c
gas temperature [°C] (SF06 frame only)
Definition sfm_types.hpp:441
bool temperature_valid
false on SFx6000 (frame word 2 is reserved)
Definition sfm_types.hpp:442
Product identifier + 8-byte serial number (data sheet §4.3.9).
Definition sfm_types.hpp:323
uint32_t product_id
Definition sfm_types.hpp:324
Variant variant
Definition sfm_types.hpp:326
uint8_t serial[8]
Definition sfm_types.hpp:325
Scale factor / offset / unit for one gas lookup table (data sheet §4.3.6).
Definition sfm_types.hpp:330
int16_t scale
counts per flow unit (SFM4300: 1000 per slm)
Definition sfm_types.hpp:331
constexpr float FullScaleFlow() const noexcept
Full-scale flow in the device unit (SFx6000; 0 when not reported).
Definition sfm_types.hpp:339
int16_t full_scale_raw
SFx6000 only: full-scale flow, raw counts.
Definition sfm_types.hpp:334
constexpr float ToFlow(int16_t raw) const noexcept
raw → flow in the device unit (slm for SFM4300).
Definition sfm_types.hpp:344
int16_t offset
counts at zero flow (SFM4300: −28672)
Definition sfm_types.hpp:332
bool valid
Definition sfm_types.hpp:336
uint16_t gas_id
SFx6000 only: SEMI gas ID.
Definition sfm_types.hpp:335
uint16_t unit
unit code (SFM4300: 0x0148 = slm @ 20 °C)
Definition sfm_types.hpp:333
Decoded status word (data sheet §4.3.2, Table 5).
Definition sfm_types.hpp:358
uint16_t raw
Definition sfm_types.hpp:359
constexpr bool fixed_n_averaging() const noexcept
Definition sfm_types.hpp:363
constexpr bool exponential_smoothing() const noexcept
Definition sfm_types.hpp:362
constexpr bool pure_gas() const noexcept
Definition sfm_types.hpp:365
constexpr uint16_t gas_fraction_permille() const noexcept
Definition sfm_types.hpp:364
constexpr uint8_t command_nibble() const noexcept
Definition sfm_types.hpp:361