22std::array<uint8_t, 2>
table_id(uint8_t slot, uint8_t ain) {
23 return {
kTypeHi,
static_cast<uint8_t
>((slot - 1) * 0x20 + ain)};
27 return "T7" + std::to_string(slot) +
".AIN" + std::to_string(ain);
31 return {
kTypeHi,
static_cast<uint8_t
>((slot - 1) * 0x20 + 0x10 + ain)};
35 return "T7" + std::to_string(slot) +
"_Cal.AIN" + std::to_string(ain);
40 auto set = [&](
const std::string& name,
double value) {
41 p.setup_names.push_back(name);
42 p.setup_values.push_back(
value);
47 set(
"STREAM_TRIGGER_INDEX", 0);
48 set(
"STREAM_CLOCK_SOURCE", 0);
49 set(
"STREAM_RESOLUTION_INDEX",
board.resolution_index);
50 set(
"STREAM_SETTLING_US",
board.settling_us);
51 for (
const auto& c :
board.channels) {
52 const std::string ain =
"AIN" + std::to_string(c.ain);
53 set(ain +
"_RANGE", c.range_v);
54 set(ain +
"_NEGATIVE_CH", c.negative_ch());
57 set(ain +
"_EF_INDEX", 0);
58 p.scan_names.push_back(ain);
59 p.ains.push_back(
static_cast<uint8_t
>(c.ain));
60 p.channels.emplace_back(c);
67 return c.kind ==
"TC";
70 const std::string ain =
"AIN" + std::to_string(
kCjcAin);
71 set(ain +
"_RANGE", 10.0);
72 set(ain +
"_NEGATIVE_CH", 199);
73 set(ain +
"_EF_INDEX", 0);
74 p.cjc_index =
static_cast<int>(
p.scan_names.size());
75 p.scan_names.push_back(ain);
77 p.channels.emplace_back(std::nullopt);
99 std::to_string(
serial_) +
", config says " + std::to_string(
board_.serial));
106 const std::string tag =
"[" +
board_.section +
"] ";
112 throw T7Error(tag +
"could not stop the stream already running on the T7 at " +
113 board_.ip +
": " + e.what(),
123 device_->write_names({
"STREAM_ENABLE"}, {0.0});
127 if (
plan_.cjc_index >= 0) {
131 const auto v =
device_->read_names({
"TEMPERATURE_DEVICE_K",
"AIN14"});
132 if (v.size() != 2 || !(v[0] > 200.0 && v[0] < 400.0))
133 throw T7Error(
"[" +
board_.section +
"] implausible TEMPERATURE_DEVICE_K " +
134 (v.empty() ? std::string(
"(none)") : std::to_string(v[0])));
137 const int spr =
board_.effective_scans_per_read();
144 throw T7Error(tag +
"another client started a stream on the T7 at " +
board_.ip +
145 " while this one configured it (a second t7_bridge or server? "
151 throw T7Error(
"[" +
board_.section +
"] device reported a scan rate of " +
153 buffer_.assign(
static_cast<size_t>(spr) *
plan_.ains.size(), 0.0);
161 const uint64_t arrival_ns =
clock_();
162 const size_t n =
plan_.ains.size();
163 const uint64_t scans =
buffer_.size() /
n;
170 auto device_ns = [&](uint64_t k) {
171 return static_cast<uint64_t
>(std::llround(
static_cast<double>(k) * period_ns));
173 auto device_ms = [&](uint64_t k) {
174 return static_cast<uint32_t
>(device_ns(k) / 1000000ULL);
178 const uint64_t last = first + scans - 1;
181 const uint64_t last_ns = device_ns(last);
182 const uint64_t sub_ms = last_ns % 1000000ULL;
184 clock_sync_.stamp_packet(
board_.section, arrival_ns, {device_ms(last)}).front() + sub_ms;
185 anchor = std::min(anchor, arrival_ns);
188 for (uint64_t s = 0; s < scans; ++s) {
189 const uint64_t k = first + s;
190 const uint64_t
back = last_ns - device_ns(k);
191 const uint64_t ts = anchor >
back ? anchor -
back : 0;
195 if (
plan_.cjc_index >= 0) {
196 const double v14 =
buffer_[s *
n +
static_cast<size_t>(
plan_.cjc_index)];
198 b.cold_junction.push_back(
202 for (
size_t c = 0; c <
n; ++c) {
203 const double v =
buffer_[s *
n + c];
208 b.samples.push_back(
Sample{
plan_.ains[c],
static_cast<float>(v), ts, device_ms(k)});
244 if (
r.first == name) {
263 std::ifstream in(
path_);
271 while (std::getline(in, line)) {
272 std::istringstream ls(line);
275 if (key ==
"streaming")
277 else if (key ==
"stream_id")
279 else if (key ==
"starts")
281 else if (key ==
"stops")
283 else if (key ==
"scan")
284 for (std::string
n; ls >>
n;)
286 else if (key ==
"reg") {
298 const std::string tmp =
path_ +
".tmp";
300 std::ofstream out(tmp, std::ios::trunc);
301 out <<
"streaming " << (
streaming ? 1 : 0) <<
"\n"
303 <<
"starts " <<
starts <<
"\n"
304 <<
"stops " <<
stops <<
"\n"
311 out <<
"reg " <<
r.first <<
" " <<
r.second <<
"\n";
313 std::rename(tmp.c_str(),
path_.c_str());
320 opt_.unit = std::make_shared<FakeT7Unit>();
334 const std::vector<double>& values) {
337 for (
size_t i = 0; i < names.size(); ++i) {
340 if (names[i] ==
"STREAM_ENABLE" && values.at(i) == 0.0) {
341 writes.emplace_back(names[i], 0.0);
350 throw T7Error(
"fake T7: STREAM_IS_ACTIVE writing " + names[i],
353 writes.emplace_back(names[i], values.at(i));
354 u.set_reg(names[i], values.at(i));
362 if (
opt_.unit->streaming)
363 throw T7Error(
"fake T7: STREAM_IS_ACTIVE (command-response AIN read while streaming)",
365 std::vector<double> out;
366 for (
const auto&
n : names) {
368 if (
n ==
"TEMPERATURE_DEVICE_K")
369 out.push_back(
opt_.device_temp_k);
370 else if (
n.rfind(
"AIN", 0) == 0)
371 out.push_back(
volts(std::stoi(
n.substr(3)), 0.0));
373 throw T7Error(
"fake T7: no register " +
n, 1294);
382 return opt_.signal(ain, t);
383 return 0.1 * ain + 0.05 * std::sin(2.0 * M_PI * 0.2 * t);
387 double scan_rate_hz) {
391 throw T7Error(
"fake T7: STREAM_IS_ACTIVE (stream already running)",
395 rate_ =
opt_.rate_cap_hz > 0.0 ? std::min(scan_rate_hz,
opt_.rate_cap_hz) : scan_rate_hz;
400 u.scan_names = names;
415 throw T7Error(
"fake T7: no stream data (the stream was stopped by another client)",
426 const bool lost =
opt_.dummy_scan_every > 0 && (
scan_ + 1) %
opt_.dummy_scan_every == 0;
427 const double t =
static_cast<double>(
scan_) /
rate_;
428 for (
size_t c = 0; c <
n; ++c) {
429 const int ain = std::stoi(
scan_names[c].substr(3));
440 if (
opt_.fail_stop_code != 0)
441 throw T7Error(
"fake T7: stop failed",
opt_.fail_stop_code);
445 if (
opt_.stop_only_own_stream && !mine)
446 throw T7Error(
"fake T7: LJME_STREAM_NOT_INITIALIZED (this handle started no stream)",
double stream_start(const std::vector< std::string > &scan_names, int scans_per_read, double scan_rate_hz) override
Starts the stream; returns the scan rate the device will actually run at.
std::vector< std::string > scan_names
Backlog stream_read(std::vector< double > &data) override
Fills data with scans_per_read scans, channels interleaved.
void write_names(const std::vector< std::string > &names, const std::vector< double > &values) override
void stream_stop() override
void open(const std::string &ip) override
uint64_t my_stream_
unit stream_id this handle started; 0 = none
int64_t serial_number() override
std::vector< std::string > reads
every name read_names() was asked for
std::vector< double > read_names(const std::vector< std::string > &names) override
Command-response reads; only valid while no stream is running.
bool streaming
this handle's own stream is running
std::vector< std::pair< std::string, double > > writes
double volts(int ain, double t) const
Volts on ain at t: Options::signal, else the default described above.
The stand-in T7 itself, as distinct from a connection to it.
std::vector< std::pair< std::string, double > > registers
last value written per name
void load()
from state_path; no-op without one or when the file is absent
void power_cycle()
Power off and on: no stream, registers back to defaults.
std::vector< std::string > scan_names
the running (or last) stream's scan list
void set_reg(const std::string &name, double value)
double reg(const std::string &name, double fallback=0.0) const
FakeT7Unit(std::string state_path="")
uint64_t stream_id
bumped by every stream start; a handle reads only its own stream
Block read()
Reads and decodes one block.
double start()
Opens the device, checks its serial number if the config gives one, writes the setup,...
std::vector< double > buffer_
const config::T7Board & board() const
time::BoardClockSync clock_sync_
T7Session(config::T7Board board, std::unique_ptr< T7Device > device, const time::TimeSyncConfig &time_sync, Clock clock)
std::function< uint64_t()> Clock
epoch ns
double cjc_device_offset_k_
std::unique_ptr< T7Device > device_
void stop() noexcept
Stops the stream and closes the device; never throws.
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
return reference_tokens back()
constexpr int kStreamNotInitialized
LJME_STREAM_NOT_INITIALIZED (LJM, this handle).
constexpr int kDeviceStreamNotRunning
device: "Stream was already disabled"
constexpr int kStreamNotRunning
LJME_STREAM_NOT_RUNNING.
constexpr int kSynchronizationTimeout
LJME_SYNCHRONIZATION_TIMEOUT (no stream data).
constexpr int kStreamIsActive
device: "can not be performed while stream is running"
double cjc_device_offset_k(double device_temp_k, double ain14_v)
AIN14 offset (K) that makes slope*ain14_v land on the device's own temperature reading.
constexpr double kAin14SlopeKPerV
The T7's internal temperature sensor (an LM94021 on AIN14) reads "Device temperature K =volts * -92....
StreamPlan plan_stream(const config::T7Board &board)
bool is_stream_not_running(int code)
True for every way a stop can answer "there was no stream to stop".
std::string entity_name(uint8_t slot, uint8_t ain)
Elodin entity name for one AIN, e.g.
constexpr uint8_t kTypeHi
Elodin table-id high byte for T7 analog inputs (0x20-0x25 are the ESP32 sensor types).
std::array< uint8_t, 2 > calibrated_table_id(uint8_t slot, uint8_t ain)
Calibrated table for one AIN: table_id + 0x10, e.g.
std::array< uint8_t, 2 > table_id(uint8_t slot, uint8_t ain)
Table id for one AIN: {0x26, (slot-1)*0x20 + ain}, the same layout as the ESP32 boards.
constexpr uint8_t kCjcAin
The T7's internal temperature sensor, streamed for thermocouple cold-junction compensation whenever a...
std::string calibrated_entity_name(uint8_t slot, uint8_t ain)
Calibrated entity name, e.g.
constexpr double kDummyValue
What LJM writes into a scan lost to stream auto-recovery (LJM_DUMMY_VALUE).
double cjc_temp_c(double ain14_v, double device_offset_k, double cjc_offset_k)
Cold-junction temperature (C) from a streamed AIN14 reading.
constexpr double kAin14NominalOffsetK
Nominal AIN14 offset (K), datasheet 18.0.
Motorized ball valve position: raw ADC counts -> % open.
*brief user defined string literal for JSON values *sa std::size_t n
One streamed analog input.
A decoded LJM_eStreamRead block.
What to write before streaming and what to stream, derived from a board's config.
std::string board(const std::string &name, const std::string &id, bool enabled=true)