45#include "alicat_basis2_version.h"
56 return HF_ALICAT_BASIS2_VERSION_STRING;
69template <
typename UartT>
83 : uart_(uart), address_(address), timeout_ms_(timeout_ms) {}
140 std::array<uint16_t, 10> regs{};
142 static_cast<uint8_t
>(regs.size()));
146 out.
gas =
static_cast<Gas>(regs[0] & 0xFFU);
148 const uint16_t status = regs[1];
149 out.mass_flow_overrange = (status & 0x0001U) != 0;
150 out.temperature_overrange = (status & 0x0002U) != 0;
151 out.totalizer_overrange = (status & 0x0004U) != 0;
152 out.valve_held = (status & 0x0008U) != 0;
153 out.valve_thermal_management = (status & 0x0010U) != 0;
155 out.raw_temp_2102 =
static_cast<int16_t
>(regs[2]);
156 out.temperature_c =
static_cast<float>(out.raw_temp_2102) * 0.01f;
158 out.raw_flow_2103 =
static_cast<int16_t
>(regs[3]);
159 out.flow = ScaleFlow(out.raw_flow_2103, flow_decimals);
161 out.raw_total_volume_2104_2105 =
162 (
static_cast<uint32_t
>(regs[4]) << 16) | regs[5];
163 out.total_volume = ScaleFlow(
static_cast<int32_t
>(out.raw_total_volume_2104_2105),
166 out.raw_setpoint_2053_2054 =
static_cast<int16_t
>(regs[6]);
167 out.setpoint = ScaleFlow(out.raw_setpoint_2053_2054, flow_decimals);
169 out.valve_drive_pct =
static_cast<float>(
static_cast<int16_t
>(regs[7])) * 0.01f;
171 out.raw_batch_remaining_2108_2109 =
172 (
static_cast<uint32_t
>(regs[8]) << 16) | regs[9];
173 out.batch_remaining = ScaleFlow(
static_cast<int32_t
>(out.raw_batch_remaining_2108_2109),
197 std::array<uint16_t, 1> fw{};
202 id.firmware_version_raw = fw[0];
203 id.fw_major =
static_cast<uint8_t
>((fw[0] >> 8) & 0xFFU);
204 id.fw_minor =
static_cast<uint8_t
>((fw[0] >> 4) & 0x0FU);
205 id.fw_patch =
static_cast<uint8_t
>(fw[0] & 0x0FU);
208 std::array<uint16_t, reg::SerialNumberCount> sn{};
215 id.serial_number[i * 2] =
static_cast<char>((sn[i] >> 8) & 0xFFU);
216 id.serial_number[i * 2 + 1] =
static_cast<char>(sn[i] & 0xFFU);
218 id.serial_number[12] =
'\0';
221 std::array<uint16_t, 5> tail{};
223 static_cast<uint8_t
>(tail.size()));
227 id.modbus_address =
static_cast<uint8_t
>(tail[0] & 0xFFU);
228 id.ascii_unit_id =
static_cast<char>(tail[1] & 0xFFU);
229 id.full_scale_flow_raw =
230 (
static_cast<uint32_t
>(tail[2]) << 16) | tail[3];
231 id.flow_units =
static_cast<FlowUnits>(tail[4] & 0xFFU);
255 const int32_t scaled =
static_cast<int32_t
>(
256 value_in_user_units * Pow10(flow_decimals + 0));
258 const int32_t raw =
static_cast<int32_t
>(value_in_user_units * 1000.0f);
259 std::array<uint16_t, 2> regs = {
260 static_cast<uint16_t
>((raw >> 16) & 0xFFFFU),
261 static_cast<uint16_t
>(raw & 0xFFFFU),
291 std::array<uint16_t, 2> regs = {
292 static_cast<uint16_t
>((pct_per_ms_x_10e7 >> 16) & 0xFFFFU),
293 static_cast<uint16_t
>(pct_per_ms_x_10e7 & 0xFFFFU),
376 std::array<uint16_t, 2> regs = {
377 static_cast<uint16_t
>((value_scaled >> 16) & 0xFFFFU),
378 static_cast<uint16_t
>(value_scaled & 0xFFFFU),
405 const uint16_t raw =
static_cast<uint16_t
>(c * 100.0f);
456 std::array<uint16_t, kLast - kFirst + 1> regs{};
460 const auto at = [®s](uint16_t r) {
return regs[r - kFirst]; };
471 out.prev_avg_flow_raw =
474 out.min_temperature_c =
static_cast<float>(out.min_temperature_raw) * 0.01f;
475 out.max_temperature_c =
static_cast<float>(out.max_temperature_raw) * 0.01f;
476 out.avg_temperature_c =
static_cast<float>(out.avg_temperature_raw) * 0.01f;
477 out.avg_flow = ScaleFlow(out.avg_flow_raw, flow_decimals);
502 if (rc.ok()) address_ = new_address;
514 if (
id <
'A' ||
id >
'Z') {
571 std::size_t bitmap_bytes,
572 uint16_t probe_timeout_ms = 30,
573 uint8_t first_addr = 1,
575 if (!present_bitmap || bitmap_bytes < 32) {
578 std::memset(present_bitmap, 0, bitmap_bytes);
580 const uint8_t saved_addr = address_;
581 const uint16_t saved_timeout = timeout_ms_;
582 timeout_ms_ = probe_timeout_ms;
586 for (uint16_t a = first_addr; a <= last_addr; ++a) {
587 address_ =
static_cast<uint8_t
>(a);
588 std::array<uint16_t, 1> dummy{};
591 present_bitmap[a / 8] |=
static_cast<uint8_t
>(1U << (a % 8));
596 address_ = saved_addr;
597 timeout_ms_ = saved_timeout;
614 std::array<uint8_t, 8> tx{};
616 tx.data(), tx.size());
620 const std::size_t need = 5 + 2U * count;
621 std::array<uint8_t, 5 + 2 * 32> rx{};
624 SendRequest(tx.data(), tx_len);
640 std::array<uint8_t, 8> tx{};
642 tx.data(), tx.size());
645 SendRequest(tx.data(), tx_len);
650 std::array<uint8_t, 8> rx{};
666 const uint16_t* regs, uint8_t count)
noexcept {
667 if (!regs || count == 0 || count > 32) {
670 std::array<uint8_t, 9 + 32 * 2> tx{};
672 tx.data(), tx.size());
675 SendRequest(tx.data(), tx_len);
679 std::array<uint8_t, 8> rx{};
695 return last_exception_;
700 static constexpr const char* kLogTag =
"alicat_basis2";
708 uart_.log(kLogTag,
"addr %u fn 0x%02X reg %u: ModbusException %s",
709 static_cast<unsigned>(address_),
static_cast<unsigned>(fn),
710 static_cast<unsigned>(reg_addr),
ToString(last_exception_).data());
712 uart_.log(kLogTag,
"addr %u fn 0x%02X reg %u: %s",
static_cast<unsigned>(address_),
713 static_cast<unsigned>(fn),
static_cast<unsigned>(reg_addr),
ToString(rc).data());
727 void SendRequest(
const uint8_t* tx, std::size_t tx_len)
noexcept {
731 uart_.write(tx, tx_len);
749 DriverError ReceiveReply(uint8_t fn, uint8_t* rx, std::size_t expected_len,
750 std::size_t& got)
noexcept {
765 got += uart_.read(rx + got, expected_len - got, timeout_ms_);
769 static float Pow10(
int n)
noexcept {
771 for (
int i = 0; i < n; ++i) v *= 10.0f;
777 static float ScaleFlow(int32_t raw, uint8_t decimals)
noexcept {
778 return static_cast<float>(raw) / Pow10(decimals);
783 uint16_t timeout_ms_;
Modbus-RTU framing and CRC-16 helpers used by the BASIS-2 driver.
Modbus-RTU holding-register addresses for Alicat BASIS-2.
Public type definitions for the Alicat BASIS-2 driver.
CRTP serial transport for the Alicat BASIS-2 driver.
Main BASIS-2 driver.
Definition alicat_basis2.hpp:70
ModbusExceptionCode GetLastException() const noexcept
Exception code of the last transaction's exception reply.
Definition alicat_basis2.hpp:694
uint8_t GetAddress() const noexcept
Definition alicat_basis2.hpp:118
void SetTimeoutMs(uint16_t ms) noexcept
Update the per-transaction timeout (ms).
Definition alicat_basis2.hpp:89
DriverResult< void > SetCommWatchdogMs(uint16_t ms) noexcept
Configure the communication watchdog (controllers only).
Definition alicat_basis2.hpp:285
DriverResult< void > SetSetpoint(float value_in_user_units, uint8_t flow_decimals=3) noexcept
Set the digital setpoint (controllers only).
Definition alicat_basis2.hpp:254
void SetAddress(uint8_t addr) noexcept
Switch the slave address this driver targets at runtime.
Definition alicat_basis2.hpp:116
DriverResult< void > SetFlowAveragingMs(uint16_t ms) noexcept
Configure on-chip flow averaging (IIR-style time constant).
Definition alicat_basis2.hpp:393
Driver(UartT &uart, uint8_t address, uint16_t timeout_ms=kDefaultTimeoutMs) noexcept
Construct a driver bound to one bus + one slave address.
Definition alicat_basis2.hpp:81
DriverResult< void > SetModbusAddress(uint8_t new_address) noexcept
Change the instrument's Modbus slave address.
Definition alicat_basis2.hpp:497
uint32_t GetInterFrameGapUs() const noexcept
Definition alicat_basis2.hpp:111
DriverResult< void > StartMeasurementSamples(uint16_t num_samples) noexcept
Start a measurement window of num_samples samples.
Definition alicat_basis2.hpp:434
void SetBusBaudRate(uint32_t bps) noexcept
Tell the driver the bus baud rate (default 38400, the factory rate).
Definition alicat_basis2.hpp:103
DriverResult< uint8_t > DiscoverPresentAddresses(uint8_t *present_bitmap, std::size_t bitmap_bytes, uint16_t probe_timeout_ms=30, uint8_t first_addr=1, uint8_t last_addr=kMaxModbusAddress) noexcept
Probe the entire bus to find which addresses respond.
Definition alicat_basis2.hpp:570
DriverResult< void > SetReferenceTemperatureC(float c) noexcept
Set the reference temperature used for standard / normal flow.
Definition alicat_basis2.hpp:403
DriverResult< void > SetAsciiUnitId(char id) noexcept
Change the instrument's ASCII unit-id (used by the legacy ASCII serial protocol).
Definition alicat_basis2.hpp:513
DriverResult< void > Tare() noexcept
Force a tare (zero-flow reference) on the instrument.
Definition alicat_basis2.hpp:310
uint32_t GetBusBaudRate() const noexcept
Definition alicat_basis2.hpp:107
DriverResult< void > SetAutotareEnabled(bool enabled) noexcept
Enable / disable automatic taring (controllers only).
Definition alicat_basis2.hpp:322
DriverResult< void > SetBaudRate(BaudRate br) noexcept
Change the serial baud rate (instrument register 21).
Definition alicat_basis2.hpp:537
DriverResult< MeasurementData > ReadMeasurement(uint8_t flow_decimals=3) noexcept
Read the current / most-recent measurement window result.
Definition alicat_basis2.hpp:453
DriverResult< void > SetMaxSetpointRamp(uint32_t pct_per_ms_x_10e7) noexcept
Definition alicat_basis2.hpp:290
DriverResult< void > SetTotalizerBatch(uint32_t value_scaled) noexcept
Set the totalizer batch volume (controllers only).
Definition alicat_basis2.hpp:375
DriverResult< void > ConfigureMeasurementTrigger(uint16_t trigger_bits) noexcept
Configure when a new measurement window auto-restarts.
Definition alicat_basis2.hpp:420
DriverResult< void > ResetTotalizer() noexcept
Reset the totalizer to zero.
Definition alicat_basis2.hpp:352
DriverResult< void > SetTotalizerLimitMode(TotalizerLimitMode mode) noexcept
Configure totalizer overflow behaviour.
Definition alicat_basis2.hpp:361
DriverResult< void > WriteMultipleRegisters(uint16_t reg_addr, const uint16_t *regs, uint8_t count) noexcept
Function 16: write count (1..32) consecutive holding registers.
Definition alicat_basis2.hpp:665
DriverResult< void > WriteSingleRegister(uint16_t reg_addr, uint16_t value) noexcept
Function 06: write one holding register.
Definition alicat_basis2.hpp:639
DriverResult< void > FactoryRestore() noexcept
Restore the instrument to factory defaults.
Definition alicat_basis2.hpp:551
Driver(const Driver &)=delete
DriverResult< InstantaneousData > ReadInstantaneous(uint8_t flow_decimals=3) noexcept
Read all instantaneous registers (2100..2109) in one burst.
Definition alicat_basis2.hpp:139
uint16_t GetTimeoutMs() const noexcept
Definition alicat_basis2.hpp:91
Driver & operator=(const Driver &)=delete
DriverResult< InstrumentIdentity > ReadIdentity() noexcept
Pull the static identification of the instrument.
Definition alicat_basis2.hpp:193
DriverResult< void > SetGas(Gas g) noexcept
Select one of the 9 factory-calibrated gases.
Definition alicat_basis2.hpp:336
DriverError ReadHoldingRegisters(uint16_t reg_addr, uint16_t *out, uint8_t count) noexcept
Function 03: read count holding registers starting at reg_addr.
Definition alicat_basis2.hpp:612
DriverResult< void > SetSetpointSource(SetpointSource src) noexcept
Switch the setpoint source (controllers only).
Definition alicat_basis2.hpp:272
constexpr uint32_t InterFrameGapUs(uint32_t bps) noexcept
Minimum idle time t3.5 between two Modbus-RTU frames, in microseconds.
Definition alicat_basis2_modbus.hpp:145
bool IsExceptionReply(const uint8_t *frame, std::size_t length) noexcept
Definition alicat_basis2_modbus.hpp:168
std::size_t BuildWriteMultiple(uint8_t unit_addr, uint16_t reg_addr, const uint16_t *regs, uint8_t count, uint8_t *out, std::size_t out_capacity) noexcept
Build a Function-16 (Write Multiple Registers) request.
Definition alicat_basis2_modbus.hpp:105
DriverError ParseReadHolding(const uint8_t *frame, std::size_t length, uint8_t expected_addr, uint8_t expected_count, uint16_t *regs) noexcept
Parse a Function-03 response into regs[].
Definition alicat_basis2_modbus.hpp:200
DriverError ParseWriteSingleEcho(const uint8_t *frame, std::size_t length, const uint8_t *request, std::size_t req_len) noexcept
Validate the echo of a Function-06 (Write Single) request against the just-sent request.
Definition alicat_basis2_modbus.hpp:220
std::size_t BuildReadHolding(uint8_t unit_addr, uint16_t reg_addr, uint16_t reg_count, uint8_t *out, std::size_t out_capacity) noexcept
Build a Function-03 (Read Holding Registers) request.
Definition alicat_basis2_modbus.hpp:74
std::size_t BuildWriteSingle(uint8_t unit_addr, uint16_t reg_addr, uint16_t value, uint8_t *out, std::size_t out_capacity) noexcept
Build a Function-06 (Write Single Register) request.
Definition alicat_basis2_modbus.hpp:89
constexpr uint32_t InterFrameGapMs(uint32_t bps) noexcept
InterFrameGapUs(bps) rounded up to whole milliseconds (for UartInterface::delay_ms()).
Definition alicat_basis2_modbus.hpp:156
DriverError ParseException(const uint8_t *frame, std::size_t length, uint8_t expected_addr, uint8_t expected_fn, uint8_t *code) noexcept
Parse a 5-byte exception reply (Modbus Application Protocol V1.1b3, section 7).
Definition alicat_basis2_modbus.hpp:183
constexpr std::size_t kReplyHeaderLength
Number of reply bytes a receiver reads before it can tell an exception reply (bit 7 of the function c...
Definition alicat_basis2_modbus.hpp:165
DriverError ParseWriteMultipleEcho(const uint8_t *frame, std::size_t length, uint8_t expected_addr, uint16_t expected_reg_addr, uint16_t expected_count) noexcept
Validate the response of a Function-16 (Write Multiple) request.
Definition alicat_basis2_modbus.hpp:233
constexpr std::size_t kExceptionFrameLength
Length of a Modbus-RTU exception reply: address, function | 0x80, exception code, CRC-16.
Definition alicat_basis2_modbus.hpp:161
constexpr uint8_t ReadHolding
Definition alicat_basis2_registers.hpp:99
constexpr uint8_t WriteMultiple
Definition alicat_basis2_registers.hpp:101
constexpr uint8_t WriteSingle
Definition alicat_basis2_registers.hpp:100
constexpr uint16_t SerialNumberCount
Definition alicat_basis2_registers.hpp:25
constexpr uint16_t MeasurementMinTemp
Definition alicat_basis2_registers.hpp:75
constexpr uint16_t MeasurementAvgTemp
Definition alicat_basis2_registers.hpp:80
constexpr uint16_t MeasurementTriggerMode
Definition alicat_basis2_registers.hpp:73
constexpr uint16_t MaxSetpointRamp
Definition alicat_basis2_registers.hpp:53
constexpr uint16_t TotalizerBatchVolume
Definition alicat_basis2_registers.hpp:52
constexpr uint16_t MeasurementMinFlow
Definition alicat_basis2_registers.hpp:77
constexpr uint16_t CommWatchdogMs
Definition alicat_basis2_registers.hpp:47
constexpr uint16_t TotalizerResetCommand
Definition alicat_basis2_registers.hpp:35
constexpr uint16_t TotalizerLimitMode
Definition alicat_basis2_registers.hpp:36
constexpr uint16_t FactoryRestoreCommand
Definition alicat_basis2_registers.hpp:39
constexpr uint16_t AsciiUnitId
Definition alicat_basis2_registers.hpp:30
constexpr uint16_t kPrevAvgFlowCount
Definition alicat_basis2_registers.hpp:89
constexpr uint16_t ModbusAddress
Definition alicat_basis2_registers.hpp:29
constexpr uint16_t MeasurementPrevAvgFlow
Previous window's average flow: signed 32-bit at 4214-4215, high register first.
Definition alicat_basis2_registers.hpp:88
constexpr uint16_t MeasurementAvgFlow
Definition alicat_basis2_registers.hpp:81
constexpr uint16_t FlowAveragingMs
Definition alicat_basis2_registers.hpp:37
constexpr uint16_t SetpointCommand
Definition alicat_basis2_registers.hpp:59
constexpr uint16_t AutotareEnable
Definition alicat_basis2_registers.hpp:48
constexpr uint16_t SelectedGas
Definition alicat_basis2_registers.hpp:60
constexpr uint16_t MeasurementMaxTemp
Definition alicat_basis2_registers.hpp:76
constexpr uint16_t FirmwareVersion
Definition alicat_basis2_registers.hpp:23
constexpr uint16_t SerialNumberStart
Definition alicat_basis2_registers.hpp:24
constexpr uint16_t BaudRate
Definition alicat_basis2_registers.hpp:22
constexpr uint16_t TareCommand
Definition alicat_basis2_registers.hpp:28
constexpr uint16_t MeasurementSamplesAveraged
Definition alicat_basis2_registers.hpp:79
constexpr uint16_t MeasurementSampleCount
Definition alicat_basis2_registers.hpp:74
constexpr uint16_t SetpointSource
Definition alicat_basis2_registers.hpp:49
constexpr uint16_t MeasurementMaxFlow
Definition alicat_basis2_registers.hpp:78
constexpr uint16_t ReferenceTemperature
Definition alicat_basis2_registers.hpp:34
Definition alicat_basis2.hpp:52
constexpr uint8_t kBroadcastAddress
Definition alicat_basis2_types.hpp:374
constexpr uint8_t kMaxModbusAddress
Definition alicat_basis2_types.hpp:375
FlowUnits
Flow-unit identifier read from instrument register 49.
Definition alicat_basis2_types.hpp:181
BaudRate
Baud-rate codes for register 21 (datasheet "Changing the Baud Rate").
Definition alicat_basis2_types.hpp:230
DriverError
Driver-level error codes.
Definition alicat_basis2_types.hpp:39
@ InvalidFrame
Frame too short, wrong function code, etc.
@ InvalidParameter
Out-of-range argument.
@ InvalidAddress
Modbus address outside 1..247.
@ None
Operation succeeded.
@ ModbusException
Slave returned a Modbus exception PDU.
@ BufferTooSmall
Provided buffer is too small for the response.
@ Timeout
No response within the configured timeout.
constexpr uint16_t kDefaultTimeoutMs
Definition alicat_basis2_types.hpp:379
const char * GetDriverVersion() noexcept
Driver version string (from generated alicat_basis2_version.h).
Definition alicat_basis2.hpp:55
constexpr uint16_t kMagicFactoryRestore
Definition alicat_basis2_types.hpp:377
SetpointSource
Setpoint source — register 516 (controllers only).
Definition alicat_basis2_types.hpp:263
constexpr uint16_t kMagicTare
Definition alicat_basis2_types.hpp:376
TotalizerLimitMode
Totalizer overflow behaviour — register 54.
Definition alicat_basis2_types.hpp:272
constexpr std::string_view ToString(DriverError e) noexcept
Convert a DriverError to a stable human-readable string (no allocation).
Definition alicat_basis2_types.hpp:56
constexpr uint32_t BaudRateToBps(BaudRate b) noexcept
Definition alicat_basis2_types.hpp:239
Gas
Index into the BASIS-2 calibrated gas table.
Definition alicat_basis2_types.hpp:145
constexpr uint32_t kDefaultBusBaudBps
Baud rate the driver assumes for the inter-frame gap until told otherwise (the instrument's factory d...
Definition alicat_basis2_types.hpp:386
ModbusExceptionCode
Exception code of a Modbus exception reply (function code | 0x80, one code byte).
Definition alicat_basis2_types.hpp:82
@ None
The last transaction got no exception reply.
Lightweight result wrapper. Aborts on value() if !ok().
Definition alicat_basis2_types.hpp:115
static constexpr DriverResult success(T v) noexcept
Definition alicat_basis2_types.hpp:122
static constexpr DriverResult failure(DriverError e) noexcept
Definition alicat_basis2_types.hpp:123
Decoded snapshot of registers 2100..2109 (one Modbus burst).
Definition alicat_basis2_types.hpp:302
Gas gas
Definition alicat_basis2_types.hpp:303
Static identification of an instrument (read once at init).
Definition alicat_basis2_types.hpp:347
Decoded measurement-window result (registers 4201..4215).
Definition alicat_basis2_types.hpp:326
uint16_t num_samples_requested
Register 4201: samples to take.
Definition alicat_basis2_types.hpp:341