Partial implementation of network config, calibration

This commit is contained in:
Joris van Rantwijk 2024-09-29 19:21:51 +02:00
parent 27fc4236e4
commit 67c6a44f22
1 changed files with 321 additions and 85 deletions

View File

@ -14,6 +14,7 @@
#include <stdio.h> #include <stdio.h>
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <fstream>
#include <functional> #include <functional>
#include <istream> #include <istream>
#include <map> #include <map>
@ -34,6 +35,8 @@ using namespace puzzlefw;
namespace asio = boost::asio; namespace asio = boost::asio;
/* ******** Utility functions ******** */
/** Return true if the string ends with the suffix. */ /** Return true if the string ends with the suffix. */
bool str_ends_with(const std::string& value, const std::string& suffix) bool str_ends_with(const std::string& value, const std::string& suffix)
{ {
@ -102,32 +105,121 @@ bool parse_float(const std::string& s, double& v)
} }
// Forward declaration. /* ******** Network configuration ******** */
class ControlServer;
enum NetworkConfigMode { NETCFG_INVALID = 0, NETCFG_DHCP, NETCFG_STATIC };
/** struct NetworkConfig {
* The CommandHandler handles commands from remote clients. NetworkConfigMode mode;
*/ asio::ip::address_v4 ipaddr;
class CommandHandler asio::ip::address_v4 netmask;
{ asio::ip::address_v4 gateway;
public: NetworkConfig() : mode(NETCFG_INVALID) { }
// IDN response fields.
static constexpr const char * IDN_MANUFACTURER = "Jigsaw";
static constexpr const char * IDN_MODEL = "PuzzleFw";
// Configuration files.
static constexpr const char * CFG_FILE_CALIBRATION =
"/var/lib/puzzlefw/cfg/calibration.conf";
static constexpr const char * CFG_FILE_NETWORK =
"/var/lib/puzzlefw/cfg/network.conf";
enum ExitStatus {
EXIT_ERROR = 1,
EXIT_HALT = 10,
EXIT_REBOOT = 11
}; };
/** Read network configuration from file. */
bool read_network_config(const char *filename, NetworkConfig& ipcfg)
{
ipcfg = NetworkConfig();
std::ifstream is(filename);
if (!is) {
log(LOG_ERROR, "Can not read %s", filename);
return false;
}
std::string line;
while (std::getline(is, line)) {
boost::system::error_code ec{};
size_t p = line.find_last_not_of(" \t\n\v\f\r");
if (p != line.npos) {
line.erase(p + 1);
}
if (line == "MODE=dhcp") {
ipcfg.mode = NETCFG_DHCP;
}
if (line == "MODE=static") {
ipcfg.mode = NETCFG_STATIC;
}
if (line.compare(0, 7, "IPADDR=") == 0) {
ipcfg.ipaddr = asio::ip::make_address_v4(line.substr(7), ec);
}
if (line.compare(0, 8, "NETMASK=") == 0) {
ipcfg.netmask = asio::ip::make_address_v4(line.substr(8), ec);
}
if (line.compare(0, 8, "GATEWAY=") == 0 && line.size() > 8) {
ipcfg.gateway = asio::ip::make_address_v4(line.substr(8), ec);
}
if (ec) {
return false;
}
}
return ((ipcfg.mode == NETCFG_DHCP)
|| (ipcfg.mode == NETCFG_STATIC
&& (! ipcfg.ipaddr.is_unspecified())
&& (! ipcfg.netmask.is_unspecified())));
}
/** Parse network configuration arguments. */
bool parse_network_config(const std::vector<std::string>& args,
NetworkConfig& ipcfg,
std::string& errmsg)
{
ipcfg = NetworkConfig{};
errmsg = std::string();
if (args.size() == 1 && str_to_lower(args[0]) == "dhcp") {
ipcfg.mode = NETCFG_DHCP;
return true;
}
if ((args.size() == 3 || args.size() == 4)
&& str_to_lower(args[0]) == "static") {
ipcfg.mode = NETCFG_STATIC;
boost::system::error_code ec{};
ipcfg.ipaddr = asio::ip::make_address_v4(args[1], ec);
if (ec || ipcfg.ipaddr.is_unspecified()) {
errmsg = "Invalid IP address";
return false;
}
ipcfg.netmask = asio::ip::make_address_v4(args[2], ec);
if (ec || ipcfg.netmask.is_unspecified()) {
errmsg = "Invalid netmask";
return false;
}
// Check that netmask describes a valid prefix.
unsigned int mask = ipcfg.netmask.to_uint();
if (mask & ((~mask) >> 1)) {
errmsg = "Invalid netmask";
return false;
}
// Optional gateway address.
if (args.size() == 4) {
ipcfg.gateway = asio::ip::make_address_v4(args[3], ec);
if (ec) {
errmsg = "Invalid gateway";
return false;
}
}
return true;
}
errmsg = "Invalid address mode";
return false;
}
/* ******** Calibration ******** */
enum RangeSpec { enum RangeSpec {
RANGE_NONE = 0, RANGE_NONE = 0,
RANGE_LO = 1, RANGE_LO = 1,
@ -146,6 +238,149 @@ public:
ChannelCalibration channel_cal[4]; ChannelCalibration channel_cal[4];
}; };
/** Read calibration from file. */
void read_calibration_file(const char *filename, Calibration& cal)
{
// Set defaults in case calibration is missing or incomplete.
for (unsigned int channel = 0; channel < 4; channel++) {
cal.channel_cal[channel].range_spec = RANGE_LO;
cal.channel_cal[channel].offset_lo = 8192;
cal.channel_cal[channel].offset_hi = 8192;
cal.channel_cal[channel].gain_lo = -8191;
cal.channel_cal[channel].gain_hi = -409;
}
std::ifstream is(filename);
if (!is) {
log(LOG_ERROR, "Can not read calibration file");
return;
}
std::string line;
while (std::getline(is, line)) {
size_t p = line.find_last_not_of(" \t\n\v\f\r");
if (p != line.npos) {
line.erase(p + 1);
}
p = line.find('=');
if (p == line.npos) {
continue;
}
std::string label = line.substr(0, p);
std::string value = line.substr(p + 1);
if (label.size() < 4
|| label[0] != 'C'
|| label[1] != 'H'
|| label[2] < '1'
|| label[2] > '4'
|| label[3] != '_') {
continue;
}
unsigned int channel = label[2] - '1';
label.erase(0, 4);
if (label == "RANGE") {
if (value == "LO") {
cal.channel_cal[channel].range_spec = RANGE_LO;
}
if (value == "HI") {
cal.channel_cal[channel].range_spec = RANGE_HI;
}
}
if (label == "OFFSET_LO") {
parse_float(value, cal.channel_cal[channel].offset_lo);
}
if (label == "OFFSET_HI") {
parse_float(value, cal.channel_cal[channel].offset_hi);
}
if (label == "GAIN_LO") {
parse_float(value, cal.channel_cal[channel].gain_lo);
}
if (label == "GAIN_HI") {
parse_float(value, cal.channel_cal[channel].gain_hi);
}
}
}
/** Write calibration to file. */
bool write_calibration_file(const char *filename, const Calibration& cal)
{
std::ofstream os(filename);
if (!os) {
log(LOG_ERROR, "Can not write calibration file");
return false;
}
for (unsigned int channel = 0; channel < 4; channel++) {
std::string line;
line = str_format("CH%u_RANGE=%s\n", channel + 1,
(cal.channel_cal[channel].range_spec == RANGE_HI) ? "HI" : "LO");
os << line;
line = str_format("CH%u_OFFSET_LO=%.6f\n", channel + 1,
cal.channel_cal[channel].offset_lo);
os << line;
line = str_format("CH%u_OFFSET_HI=%.6f\n", channel + 1,
cal.channel_cal[channel].offset_hi);
os << line;
line = str_format("CH%u_GAIN_LO=%.6f\n", channel + 1,
cal.channel_cal[channel].gain_lo);
os << line;
line = str_format("CH%u_GAIN_HI=%.6f\n", channel + 1,
cal.channel_cal[channel].gain_hi);
os << line;
}
if (!os) {
log(LOG_ERROR, "Can not write calibration file");
return false;
}
return true;
}
/* ******** class CommandHandler ******** */
// Forward declaration.
class ControlServer;
/**
* The CommandHandler handles commands from remote clients.
*/
class CommandHandler
{
public:
// IDN response fields.
static constexpr const char * IDN_MANUFACTURER = "Jigsaw";
static constexpr const char * IDN_MODEL = "PuzzleFw";
// Configuration files.
static constexpr const char * CFG_FILE_CALIBRATION =
"/var/lib/puzzlefw/cfg/calibration.conf";
static constexpr const char * CFG_FILE_NETWORK_SAVED =
"/var/lib/puzzlefw/cfg/network.conf";
static constexpr const char * CFG_FILE_NETWORK_ACTIVE =
"/var/lib/puzzlefw/cfg/network_active.conf";
enum ExitStatus {
EXIT_ERROR = 1,
EXIT_HALT = 10,
EXIT_REBOOT = 11
};
struct CommandEnvironment { struct CommandEnvironment {
int channel; int channel;
RangeSpec range_spec; RangeSpec range_spec;
@ -206,7 +441,7 @@ public:
*/ */
void reset() void reset()
{ {
read_calibration(); read_calibration_file(CFG_FILE_CALIBRATION, m_calibration);
m_device.set_adc_simulation_enabled(false); m_device.set_adc_simulation_enabled(false);
m_device.set_digital_simulation_enabled(false); m_device.set_digital_simulation_enabled(false);
m_device.set_trigger_mode(TRIG_NONE); m_device.set_trigger_mode(TRIG_NONE);
@ -260,10 +495,10 @@ public:
&& action.compare(0, 6, "ain:ch") == 0 && action.compare(0, 6, "ain:ch") == 0
&& action[7] == ':') { && action[7] == ':') {
char channel_digit = action[6]; char channel_digit = action[6];
if (channel_digit < '0' || channel_digit > '3') { if (channel_digit < '1' || channel_digit > '4') {
return err_unknown_command(); return err_unknown_command();
} }
env.channel = channel_digit - '0'; env.channel = channel_digit - '1';
action[6] = 'N'; // mark channel index action[6] = 'N'; // mark channel index
} }
@ -323,14 +558,14 @@ public:
// Handle command IPCFG. // Handle command IPCFG.
if (action == "ipcfg" || action == "ipcfg:saved") { if (action == "ipcfg" || action == "ipcfg:saved") {
if (tokens.size() < 3) { if (tokens.size() < 2) {
return err_missing_argument(); return err_missing_argument();
} }
if (tokens.size() > 4) { if (tokens.size() > 5) {
return err_unexpected_argument(); return err_unexpected_argument();
} }
const std::string& gw = (tokens.size() > 3) ? tokens[3] : ""; tokens.erase(tokens.begin());
return cmd_ipcfg(env, tokens[1], tokens[2], gw); return cmd_ipcfg(env, tokens);
} }
return err_unknown_command(); return err_unknown_command();
@ -338,27 +573,13 @@ public:
private: private:
/** Asynchronously stop control and/or data servers. */ /** Asynchronously stop control and/or data servers. */
void stop_server(bool stop_control, std::function<void()> handler); void stop_server(std::function<void()> handler);
void stop_data_servers(unsigned int idx, std::function<void()> handler); void stop_data_servers(unsigned int idx, std::function<void()> handler);
/** Asynchronously start control and/or data servers. */ /** Asynchronously start control and/or data servers. */
void start_server(bool start_control); void start_server();
void start_data_servers(unsigned int idx); void start_data_servers(unsigned int idx);
/** Read calibration from file. */
void read_calibration()
{
for (int c = 0; c < 4; c++) {
m_calibration.channel_cal[c].range_spec = RANGE_LO;
m_calibration.channel_cal[c].offset_lo = 8192;
m_calibration.channel_cal[c].offset_hi = 8192;
m_calibration.channel_cal[c].gain_lo = -8191;
m_calibration.channel_cal[c].gain_hi = -409;
}
// TODO - read from file
}
/** Convert a raw ADC sample to Volt. */ /** Convert a raw ADC sample to Volt. */
double convert_sample_to_volt(unsigned int channel, unsigned int sample) double convert_sample_to_volt(unsigned int channel, unsigned int sample)
{ {
@ -456,7 +677,7 @@ private:
} }
/** Handle command AIN:CHn:OFFS[:range]? */ /** Handle command AIN:CHn:OFFS[:range]? */
std::string qry_channel_offs(CommandEnvironment env) std::string qry_channel_offset(CommandEnvironment env)
{ {
ChannelCalibration& cal = m_calibration.channel_cal[env.channel]; ChannelCalibration& cal = m_calibration.channel_cal[env.channel];
RangeSpec range_spec = env.range_spec; RangeSpec range_spec = env.range_spec;
@ -608,8 +829,25 @@ private:
/** Handle command IPCFG[:SAVED]? */ /** Handle command IPCFG[:SAVED]? */
std::string qry_ipcfg(CommandEnvironment env) std::string qry_ipcfg(CommandEnvironment env)
{ {
// TODO - read network config from file const char *filename = env.saved_flag ? CFG_FILE_NETWORK_SAVED
return "ERROR"; : CFG_FILE_NETWORK_ACTIVE;
NetworkConfig ipcfg;
if (! read_network_config(filename, ipcfg)) {
return "ERROR Unconfigured";
}
std::string result;
if (ipcfg.mode == NETCFG_DHCP) {
return "DHCP";
}
if (ipcfg.mode == NETCFG_STATIC) {
return "STATIC "
+ ipcfg.ipaddr.to_string()
+ " " + ipcfg.netmask.to_string()
+ " " + ipcfg.gateway.to_string();
}
return "ERROR Unconfigured";
} }
/** Handle command RESET */ /** Handle command RESET */
@ -706,7 +944,7 @@ private:
} }
/** Handle command AIN:CHn:OFFS[:range] */ /** Handle command AIN:CHn:OFFS[:range] */
std::string cmd_channel_offs(CommandEnvironment env, std::string cmd_channel_offset(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
double offs; double offs;
@ -962,18 +1200,24 @@ private:
/** Handle command AIN:MINMAX:CLEAR */ /** Handle command AIN:MINMAX:CLEAR */
std::string cmd_cal_save(CommandEnvironment env) std::string cmd_cal_save(CommandEnvironment env)
{ {
// TODO - write calibration to file if (! write_calibration_file(CFG_FILE_CALIBRATION, m_calibration)) {
return "ERROR Can not write calibration";
}
// TODO - invoke script to save to SD card // TODO - invoke script to save to SD card
return "ERROR"; return "ERROR";
} }
/** Handle command IPCFG */ /** Handle command IPCFG */
std::string cmd_ipcfg(CommandEnvironment env, std::string cmd_ipcfg(CommandEnvironment env,
const std::string& ipaddr, const std::vector<std::string>& args)
const std::string& netmask,
const std::string& gateway)
{ {
// TODO -- parse and check address NetworkConfig ipcfg;
std::string errmsg;
if (! parse_network_config(args, ipcfg, errmsg)) {
return "ERROR " + errmsg;
}
// TODO -- activate or save address, restart networking if needed // TODO -- activate or save address, restart networking if needed
return "ERROR"; return "ERROR";
} }
@ -987,8 +1231,8 @@ private:
{ "ain:channels:count?", &CommandHandler::qry_channels_count }, { "ain:channels:count?", &CommandHandler::qry_channels_count },
{ "ain:channels:active?", &CommandHandler::qry_channels_active }, { "ain:channels:active?", &CommandHandler::qry_channels_active },
{ "ain:chN:range?", &CommandHandler::qry_channel_range }, { "ain:chN:range?", &CommandHandler::qry_channel_range },
{ "ain:chN:offs?", &CommandHandler::qry_channel_offs }, { "ain:chN:offset?", &CommandHandler::qry_channel_offset },
{ "ain:chN:offs:RR?", &CommandHandler::qry_channel_offs }, { "ain:chN:offset:RR?", &CommandHandler::qry_channel_offset },
{ "ain:chN:gain?", &CommandHandler::qry_channel_gain }, { "ain:chN:gain?", &CommandHandler::qry_channel_gain },
{ "ain:chN:gain:RR?", &CommandHandler::qry_channel_gain }, { "ain:chN:gain:RR?", &CommandHandler::qry_channel_gain },
{ "ain:chN:sample?", &CommandHandler::qry_channel_sample }, { "ain:chN:sample?", &CommandHandler::qry_channel_sample },
@ -1025,8 +1269,8 @@ private:
command_table_one_arg = { command_table_one_arg = {
{ "ain:channels:active", &CommandHandler::cmd_channels_active }, { "ain:channels:active", &CommandHandler::cmd_channels_active },
{ "ain:chN:range", &CommandHandler::cmd_channel_range }, { "ain:chN:range", &CommandHandler::cmd_channel_range },
{ "ain:chN:offs", &CommandHandler::cmd_channel_offs }, { "ain:chN:offset", &CommandHandler::cmd_channel_offset },
{ "ain:chN:offs:RR", &CommandHandler::cmd_channel_offs }, { "ain:chN:offset:RR", &CommandHandler::cmd_channel_offset },
{ "ain:chN:gain", &CommandHandler::cmd_channel_gain }, { "ain:chN:gain", &CommandHandler::cmd_channel_gain },
{ "ain:chN:gain:RR", &CommandHandler::cmd_channel_gain }, { "ain:chN:gain:RR", &CommandHandler::cmd_channel_gain },
{ "ain:srate", &CommandHandler::cmd_srate }, { "ain:srate", &CommandHandler::cmd_srate },
@ -1052,6 +1296,8 @@ private:
}; };
/* ******** class ControlServer ******** */
/** /**
* Manage TCP connections for remote control. * Manage TCP connections for remote control.
*/ */
@ -1374,10 +1620,8 @@ private:
/* ******** Methods for class CommandHandler ******** */ /* ******** Methods for class CommandHandler ******** */
void CommandHandler::stop_server(bool stop_control, void CommandHandler::stop_server(std::function<void()> handler)
std::function<void()> handler)
{ {
if (stop_control) {
asio::post(m_control_server->get_executor(), asio::post(m_control_server->get_executor(),
[this,handler]() { [this,handler]() {
m_control_server->stop_server(); m_control_server->stop_server();
@ -1386,9 +1630,6 @@ void CommandHandler::stop_server(bool stop_control,
stop_data_servers(0, handler); stop_data_servers(0, handler);
}); });
}); });
} else {
stop_data_servers(0, handler);
}
} }
void CommandHandler::stop_data_servers(unsigned int idx, void CommandHandler::stop_data_servers(unsigned int idx,
@ -1408,9 +1649,8 @@ void CommandHandler::stop_data_servers(unsigned int idx,
} }
} }
void CommandHandler::start_server(bool start_control) void CommandHandler::start_server()
{ {
if (start_control) {
asio::post(m_control_server->get_executor(), asio::post(m_control_server->get_executor(),
[this]() { [this]() {
m_control_server->start_server(); m_control_server->start_server();
@ -1419,9 +1659,6 @@ void CommandHandler::start_server(bool start_control)
start_data_servers(0); start_data_servers(0);
}); });
}); });
} else {
start_data_servers(0);
}
} }
void CommandHandler::start_data_servers(unsigned int idx) void CommandHandler::start_data_servers(unsigned int idx)
@ -1522,9 +1759,8 @@ int run_remote_control_server(
io.run(); io.run();
// TODO -- multi-threading // TODO -- multi-threading
// TODO -- get exit status from command handler
return 0; return command_handler.exit_status();
} }