STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_ball_valve_position.cpp
Go to the documentation of this file.
1// Ball valve position: counts -> mA -> % open, and the Elodin tables it lives in.
2
3#include <cmath>
4#include <iostream>
5#include <set>
6#include <stdexcept>
7#include <string>
8
11
14
15static void require(bool condition, const std::string& what) {
16 if (!condition)
17 throw std::runtime_error("Ball valve position assertion failed: " + what);
18}
19
20static bool near(double a, double b, double tol) {
21 return std::fabs(a - b) <= tol;
22}
23
24/** The counts the board's 12-bit ADC reports for a loop current, as the firmware sees it. */
25static uint32_t counts_for_ma(double ma) {
26 return static_cast<uint32_t>(std::lround(ma / 1000.0 * 100.0 / 3.3 * 4095.0));
27}
28
29int main() {
30 // 4 mA across 100 ohm is 0.4 V = 496 counts; 20 mA is 2.0 V = 2482 counts.
31 require(counts_for_ma(4.0) == 496 && counts_for_ma(20.0) == 2482, "fixture counts");
32 require(near(vp::counts_to_ma(2482), 20.0, 0.01), "2482 counts is 20 mA");
33
34 struct Case {
35 double ma;
36 double pct;
37 };
38 for (const Case c :
39 {Case{4.0, 0.0}, Case{8.0, 25.0}, Case{12.0, 50.0}, Case{16.0, 75.0}, Case{20.0, 100.0}}) {
40 const auto r = vp::from_counts(counts_for_ma(c.ma));
41 require(r.status == vp::kStatusOk, std::to_string(c.ma) + " mA is a signal");
42 // One count is ~0.008 mA, ~0.05 %.
43 require(near(r.percent_open, c.pct, 0.1),
44 std::to_string(c.ma) + " mA -> " + std::to_string(r.percent_open) + " %");
45 }
46
47 // A loop a little past either end is still the end of travel, not 101 % or -2 %.
48 require(vp::from_counts(counts_for_ma(20.6)).percent_open == 100.0f, "clamped at 100");
49 require(vp::from_counts(counts_for_ma(3.7)).percent_open == 0.0f, "clamped at 0");
50 require(vp::from_counts(counts_for_ma(3.7)).status == vp::kStatusOk, "3.7 mA still a signal");
51
52 // A closed valve as the stand's ESP32 actually reads it (Ox Vent, 2026-11-05): 355 counts,
53 // 2.86 mA by the raw formula. That is closed, not a dead loop.
54 require(vp::from_counts(355).status == vp::kStatusOk, "closed valve read low is a signal");
55 require(vp::from_counts(355).percent_open == 0.0f, "closed valve read low is 0 % open");
56
57 // Below 2 mA the loop is open or unpowered: say so, rather than report "closed".
58 require(vp::from_counts(0).status == vp::kStatusNoSignal, "0 counts is no signal");
59 require(vp::from_counts(counts_for_ma(1.9)).status == vp::kStatusNoSignal, "1.9 mA no signal");
60 require(vp::from_counts(counts_for_ma(2.1)).status == vp::kStatusOk, "2.1 mA is a signal");
61
62 // Tables: board 71 is slot 1, the same slot as actuator board 11, but on its own type_hi.
63 const std::vector<BoardChannels> bv{{71, 1, {1}}};
64 const std::vector<BoardChannels> act{{11, 1, {1, 2}}};
66 std::vector<std::pair<uint8_t, uint8_t>>{{0x33, 0x01}},
67 "raw [0x33, 0x01]");
69 std::vector<std::pair<uint8_t, uint8_t>>{{0x34, 0x11}},
70 "calibrated [0x34, 0x11]");
72 std::vector<std::pair<uint8_t, uint8_t>>{{0x35, 0x01}},
73 "commanded [0x35, 0x01]");
74
75 std::set<std::pair<uint8_t, uint8_t>> act_keys;
76 for (const auto& k : fsw::elodin::raw_sensor_tables({}, act, {}, {}, {}, {}))
77 act_keys.insert(k);
78 for (const auto& k : fsw::elodin::calibrated_sensor_tables({}, {}, {}, {}, {}, act))
79 act_keys.insert(k);
80 act_keys.insert({0x32, 0x01}); // ACT_CMD.B1.CH1
81 act_keys.insert({0x32, 0x02});
82 for (const auto& k : fsw::elodin::ball_valve_raw_tables(bv))
83 require(!act_keys.count(k), "raw key shared with actuator board 11");
84 for (const auto& k : fsw::elodin::ball_valve_calibrated_tables(bv))
85 require(!act_keys.count(k), "calibrated key shared with actuator board 11");
86 for (const auto& k : fsw::elodin::ball_valve_cmd_tables(bv))
87 require(!act_keys.count(k), "commanded key shared with actuator board 11");
88
89 std::cout << "Ball valve position passed\n";
90}
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
*brief create JSON pointer *sa reference_tokens const string_t & a
Definition json.hpp:13877
double counts_to_ma(uint32_t counts)
Reading from_counts(uint32_t counts)
constexpr uint8_t kStatusOk
cal_status values published beside the position.
std::vector< std::pair< uint8_t, uint8_t > > ball_valve_calibrated_tables(const std::vector< BoardChannels > &bv_boards)
std::vector< std::pair< uint8_t, uint8_t > > ball_valve_cmd_tables(const std::vector< BoardChannels > &bv_boards)
std::vector< std::pair< uint8_t, uint8_t > > raw_sensor_tables(const std::vector< BoardChannels > &pt_boards, const std::vector< BoardChannels > &act_boards, const std::vector< BoardChannels > &tc_boards, const std::vector< BoardChannels > &rtd_boards, const std::vector< BoardChannels > &lc_boards, const std::vector< BoardChannels > &enc_boards)
Elodin table ids (hi, lo) for the RAW sensor VTables the given boards publish.
std::vector< std::pair< uint8_t, uint8_t > > calibrated_sensor_tables(const std::vector< BoardChannels > &pt_boards, const std::vector< BoardChannels > &tc_boards, const std::vector< BoardChannels > &rtd_boards, const std::vector< BoardChannels > &lc_boards, const std::vector< BoardChannels > &enc_boards, const std::vector< BoardChannels > &act_boards)
Elodin table ids for the CALIBRATED VTables, matching register_calibrated_tables() (same encoding as ...
std::vector< std::pair< uint8_t, uint8_t > > ball_valve_raw_tables(const std::vector< BoardChannels > &bv_boards)
Per-board channel info for board-namespaced entity registration.
static bool near(double a, double b, double tol)
static void require(bool condition, const std::string &what)
static uint32_t counts_for_ma(double ma)
The counts the board's 12-bit ADC reports for a loop current, as the firmware sees it.
static boot_report_t r