From 60f7df6fd6643e76d16ecaa51bb5d2f7af91c6be Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Sat, 28 Sep 2024 21:22:24 +0200 Subject: [PATCH] Continue work on remote control server --- sw/src/userspace/interrupt_manager.hpp | 1 + sw/src/userspace/puzzlecmd.cpp | 13 - sw/src/userspace/puzzlefw.hpp | 12 + sw/src/userspace/remotectl.cpp | 679 +++++++++++++++++++++---- 4 files changed, 598 insertions(+), 107 deletions(-) diff --git a/sw/src/userspace/interrupt_manager.hpp b/sw/src/userspace/interrupt_manager.hpp index 9ef8506..e700600 100644 --- a/sw/src/userspace/interrupt_manager.hpp +++ b/sw/src/userspace/interrupt_manager.hpp @@ -9,6 +9,7 @@ #ifndef PUZZLEFW_INTERRUPT_MANAGER_H_ #define PUZZLEFW_INTERRUPT_MANAGER_H_ +#include #include #include #include diff --git a/sw/src/userspace/puzzlecmd.cpp b/sw/src/userspace/puzzlecmd.cpp index 6e08be4..3a4376a 100644 --- a/sw/src/userspace/puzzlecmd.cpp +++ b/sw/src/userspace/puzzlecmd.cpp @@ -21,19 +21,6 @@ #include "data_server.hpp" -/** Convert TriggerMode to string description. */ -std::string trigger_mode_to_string(puzzlefw::TriggerMode mode) -{ - using puzzlefw::TriggerMode; - switch (mode) { - case TriggerMode::TRIG_AUTO: return "auto"; - case TriggerMode::TRIG_EXTERNAL: return "external"; - case TriggerMode::TRIG_EXTERNAL_ONCE: return "external-once"; - default: return "none"; - } -} - - /** Show firmware status. */ void show_status(puzzlefw::PuzzleFwDevice& device) { diff --git a/sw/src/userspace/puzzlefw.hpp b/sw/src/userspace/puzzlefw.hpp index 4556169..ebc6d2c 100644 --- a/sw/src/userspace/puzzlefw.hpp +++ b/sw/src/userspace/puzzlefw.hpp @@ -95,6 +95,18 @@ enum TriggerMode { }; +/** Convert TriggerMode to string description. */ +inline std::string trigger_mode_to_string(TriggerMode mode) +{ + switch (mode) { + case TriggerMode::TRIG_AUTO: return "AUTO"; + case TriggerMode::TRIG_EXTERNAL: return "EXTERNAL"; + case TriggerMode::TRIG_EXTERNAL_ONCE: return "EXTERNAL_ONCE"; + default: return "NONE"; + } +} + + /** Firmware version information. */ struct VersionInfo { uint8_t api_version; diff --git a/sw/src/userspace/remotectl.cpp b/sw/src/userspace/remotectl.cpp index 784ed39..93679b1 100644 --- a/sw/src/userspace/remotectl.cpp +++ b/sw/src/userspace/remotectl.cpp @@ -9,9 +9,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include @@ -50,6 +53,55 @@ std::string str_to_lower(const std::string& value) } +/** String formatting. */ +std::string str_format(const char *format, ...) + __attribute__ ((format (printf, 1, 2))); + +std::string str_format(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + + std::string result(800, ' '); + size_t n = vsnprintf(result.data(), result.size(), format, ap); + result.resize(n); + + va_end(ap); + return result; +} + + +/** Convert string to unsigned integer. */ +bool parse_uint(const std::string& s, unsigned int& v) +{ + if (s.empty()) { + return false; + } + size_t pos = 0; + unsigned long t = std::stoul(s, &pos, 10); + if (pos != s.size()) { + return false; + } + if (t > UINT_MAX) { + return false; + } + v = t; + return true; +} + + +/** Convert string to floating point number. */ +bool parse_float(const std::string& s, double& v) +{ + if (s.empty()) { + return false; + } + size_t pos = 0; + v = std::stod(s, &pos); + return (pos == s.size()); +} + + // Forward declaration. class ControlServer; @@ -60,14 +112,20 @@ class ControlServer; class CommandHandler { public: - // IDN response fields - static constexpr std::string_view IDN_MANUFACTURER = "Jigsaw"; - static constexpr std::string_view IDN_MODEL = "PuzzleFw"; + // 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 = 2, - EXIT_REBOOT = 3 + EXIT_HALT = 10, + EXIT_REBOOT = 11 }; enum RangeSpec { @@ -76,6 +134,18 @@ public: RANGE_HI = 2 }; + struct ChannelCalibration { + RangeSpec range_spec; + double offset_lo; + double offset_hi; + double gain_lo; + double gain_hi; + }; + + struct Calibration { + ChannelCalibration channel_cal[4]; + }; + struct CommandEnvironment { int channel; RangeSpec range_spec; @@ -124,6 +194,34 @@ public: return m_strand; } + /** + * Reset non-persistent settings to power-on defaults. + * + * This resets all settings except for + * - saved calibration + * - active network configuration + * - saved network configuration. + * + * The calibration is reset to the saved calibration. + */ + void reset() + { + read_calibration(); + m_device.set_adc_simulation_enabled(false); + m_device.set_digital_simulation_enabled(false); + m_device.set_trigger_mode(TRIG_NONE); + m_device.set_trigger_ext_channel(0); + m_device.set_trigger_ext_falling(false); + m_device.set_trigger_delay(0); + m_device.set_4channel_mode(false); + m_device.set_decimation_factor(125); + m_device.set_averaging_enabled(true); + m_device.set_shift_steps(0); + m_device.set_record_length(1024); + m_device.set_acquisition_enabled(true); + m_device.set_timetagger_event_mask(0); + } + /** * Handle a command. * @@ -162,11 +260,11 @@ public: && action.compare(0, 6, "ain:ch") == 0 && action[7] == ':') { char channel_digit = action[6]; - if (channel_digit < '1' || channel_digit > '4') { + if (channel_digit < '0' || channel_digit > '3') { return err_unknown_command(); } - env.channel = channel_digit - '1'; // 0-based channel index - action[7] = 'N'; // mark channel index + env.channel = channel_digit - '0'; + action[6] = 'N'; // mark channel index } // Extract range specifier. @@ -239,7 +337,37 @@ public: } private: - // TODO -- shutdown flow + /** Asynchronously stop control and/or data servers. */ + void stop_server(bool stop_control, std::function handler); + void stop_data_servers(unsigned int idx, std::function handler); + + /** Asynchronously start control and/or data servers. */ + void start_server(bool start_control); + 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. */ + double convert_sample_to_volt(unsigned int channel, unsigned int sample) + { + ChannelCalibration& cal = m_calibration.channel_cal[channel]; + RangeSpec range_spec = cal.range_spec; + double offs = (range_spec == RANGE_HI) ? cal.offset_hi : cal.offset_lo; + double gain = (range_spec == RANGE_HI) ? cal.gain_hi : cal.gain_lo; + return (sample - offs) / gain; + } std::string err_unknown_command() const { @@ -256,6 +384,11 @@ private: return "ERROR Missing argument"; } + std::string err_invalid_argument() const + { + return "ERROR Invalid argument"; + } + /** Parse command to a list of white space separated tokens. */ std::vector parse_command(const std::string& command) { @@ -285,154 +418,204 @@ private: std::string qry_idn(CommandEnvironment env) { VersionInfo fw_version = m_device.get_version_info(); - std::stringstream idn; - idn << IDN_MANUFACTURER; - idn << "," << IDN_MODEL; - idn << "," << m_serial_number; - idn << ","; - idn << "FW-" << (int)fw_version.major_version; - idn << "." << (int)fw_version.minor_version; - idn << "/SW-" << PUZZLEFW_SW_MAJOR << "." << PUZZLEFW_SW_MINOR; - return idn.str(); + return str_format("%s,%s,%s,FW-%d.%d/SW-%d.%d", + IDN_MANUFACTURER, + IDN_MODEL, + m_serial_number.c_str(), + fw_version.major_version, + fw_version.minor_version, + PUZZLEFW_SW_MAJOR, + PUZZLEFW_SW_MINOR); } /** Handle command TIMESTAMP? */ std::string qry_timestamp(CommandEnvironment env) { - // TODO - return "ERROR"; + uint64_t timestamp = m_device.get_timestamp(); + return std::to_string(timestamp); } /** Handle command AIN:CHANNELS:COUNT? */ std::string qry_channels_count(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int n = m_device.get_analog_channel_count(); + return std::to_string(n); } /** Handle command AIN:CHANNELS:ACTIVE? */ std::string qry_channels_active(CommandEnvironment env) { - // TODO - return "ERROR"; + return m_device.is_4channel_mode() ? "4" : "2"; } /** Handle command AIN:CHn:RANGE? */ std::string qry_channel_range(CommandEnvironment env) { - // TODO - return "ERROR"; + ChannelCalibration& cal = m_calibration.channel_cal[env.channel]; + return (cal.range_spec == RANGE_HI) ? "HI" : "LO"; } /** Handle command AIN:CHn:OFFS[:range]? */ std::string qry_channel_offs(CommandEnvironment env) { - // TODO - return "ERROR"; + ChannelCalibration& cal = m_calibration.channel_cal[env.channel]; + RangeSpec range_spec = env.range_spec; + if (range_spec == RANGE_NONE) { + range_spec = cal.range_spec; + } + double offs = (range_spec == RANGE_HI) ? cal.offset_hi : cal.offset_lo; + return str_format("%.6f", offs); } /** Handle command AIN:CHn:GAIN[:range]? */ std::string qry_channel_gain(CommandEnvironment env) { - // TODO - return "ERROR"; + ChannelCalibration& cal = m_calibration.channel_cal[env.channel]; + RangeSpec range_spec = env.range_spec; + if (range_spec == RANGE_NONE) { + range_spec = cal.range_spec; + } + double gain = (range_spec == RANGE_HI) ? cal.gain_hi : cal.gain_lo; + return str_format("%.6f", gain); } /** Handle command AIN:CHn:SAMPLE[:RAW]? */ std::string qry_channel_sample(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int sample = m_device.get_adc_sample(env.channel); + if (env.raw_flag) { + return std::to_string(sample); + } else { + double v = convert_sample_to_volt(env.channel, sample); + return str_format("%.6f", v); + } } /** Handle command AIN:CHn:MINMAX[:RAW]? */ std::string qry_channel_minmax(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int min_sample, max_sample; + m_device.get_adc_range(env.channel, min_sample, max_sample); + if (env.raw_flag) { + return std::to_string(min_sample) + " " + + std::to_string(max_sample); + } else { + double vmin = convert_sample_to_volt(env.channel, min_sample); + double vmax = convert_sample_to_volt(env.channel, max_sample); + return str_format("%.6f %.6f", vmin, vmax); + } } /** Handle command AIN:SRATE? */ std::string qry_srate(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int divisor = m_device.get_decimation_factor(); + double srate = 125e6 / divisor; + return str_format("%.3f", srate); } /** Handle command AIN:SRATE:DIVISOR? */ std::string qry_srate_divisor(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int divisor = m_device.get_decimation_factor(); + return std::to_string(divisor); } /** Handle command AIN:SRATE:MODE? */ std::string qry_srate_mode(CommandEnvironment env) { - // TODO - return "ERROR"; + return m_device.is_averaging_enabled() ? "AVERAGE" : "DECIMATE"; } /** Handle command AIN:SRATE:GAIN? */ std::string qry_srate_gain(CommandEnvironment env) { - // TODO - return "ERROR"; + double gain = 1.0; + if (m_device.is_averaging_enabled()) { + unsigned int divisor = m_device.get_decimation_factor(); + int shift_steps = m_device.get_shift_steps(); + gain = divisor / static_cast(1 << shift_steps); + } + return str_format("%.8f", gain); } /** Handle command AIN:NSAMPLES? */ std::string qry_nsamples(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int nsamples = m_device.get_record_length(); + return std::to_string(nsamples); } /** Handle command AIN:TRIGGER:MODE? */ std::string qry_trigger_mode(CommandEnvironment env) { - // TODO - return "ERROR"; + TriggerMode trigger_mode = m_device.get_trigger_mode(); + return trigger_mode_to_string(trigger_mode); + } + + /** Handle command AIN:TRIGGER:EXT:CHANNEL? */ + std::string qry_trigger_ext_channel(CommandEnvironment env) + { + unsigned int n = m_device.get_trigger_ext_channel(); + return std::to_string(n); + } + + /** Handle command AIN:TRIGGER:EXT:EDGE? */ + std::string qry_trigger_ext_edge(CommandEnvironment env) + { + return m_device.get_trigger_ext_falling() ? "FALLING" : "RISING"; } /** Handle command AIN:TRIGGER:DELAY? */ std::string qry_trigger_delay(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int n = m_device.get_trigger_delay(); + return std::to_string(n); } /** Handle command AIN:TRIGGER:STATUS? */ std::string qry_trigger_status(CommandEnvironment env) { - // TODO - return "ERROR"; + if (m_device.is_waiting_for_trigger()) { + return "WAITING"; + } else { + if (m_device.is_acquisition_enabled()) { + return "BUSY"; + } else { + return "IDLE"; + } + } } /** Handle command TT:SAMPLE? */ std::string qry_tt_sample(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int sample = m_device.get_digital_input_state(); + return str_format("%d %d %d %d", + (sample & 1), + ((sample >> 1) & 1), + ((sample >> 2) & 1), + ((sample >> 3) & 1)); } - /** Handle command TT:CHANNEL:MASK? */ - std::string qry_tt_channel_mask(CommandEnvironment env) + /** Handle command TT:EVENT:MASK? */ + std::string qry_tt_event_mask(CommandEnvironment env) { - // TODO - return "ERROR"; + unsigned int event_mask = m_device.get_timetagger_event_mask(); + return std::to_string(event_mask); } /** Handle command IPCFG[:SAVED]? */ std::string qry_ipcfg(CommandEnvironment env) { - // TODO + // TODO - read network config from file return "ERROR"; } /** Handle command RESET */ std::string cmd_reset(CommandEnvironment env) { - // TODO -- reset + reset(); return "OK"; } @@ -467,102 +650,320 @@ private: /** Handle command AIN:MINMAX:CLEAR */ std::string cmd_minmax_clear(CommandEnvironment env) { - // TODO - return "ERROR"; + m_device.clear_adc_range(); + return "OK"; } /** Handle command TT:MARK */ std::string cmd_tt_mark(CommandEnvironment env) { - // TODO - return "ERROR"; + m_device.timetagger_mark(); + return "OK"; } /** Handle command AIN:CHANNELS:ACTIVE */ std::string cmd_channels_active(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + unsigned int n; + if (! parse_uint(arg, n)) { + return err_invalid_argument(); + } + if (n != 2 && n != 4) { + return err_invalid_argument(); + } + + // Reduce sample rate if necessary. + if (n == 4) { + unsigned int min_divisor = min_srate_divisor( + m_device.get_trigger_mode() == TRIG_AUTO, + true); + unsigned int divisor = m_device.get_decimation_factor(); + if (divisor < min_divisor) { + m_device.set_decimation_factor(min_divisor); + } + } + + m_device.set_4channel_mode(n == 4); + return "OK"; } /** Handle command AIN:CHn:RANGE */ std::string cmd_channel_range(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + std::string range_name = str_to_lower(arg); + + if (range_name == "lo") { + m_calibration.channel_cal[env.channel].range_spec = RANGE_LO; + } else if (range_name == "hi") { + m_calibration.channel_cal[env.channel].range_spec = RANGE_HI; + } else { + return err_invalid_argument(); + } + + return "OK"; } /** Handle command AIN:CHn:OFFS[:range] */ std::string cmd_channel_offs(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + double offs; + if ((! parse_float(arg, offs)) + || (offs < 0) + || (offs > 16383)) { + return err_invalid_argument(); + } + + ChannelCalibration& cal = m_calibration.channel_cal[env.channel]; + RangeSpec range_spec = env.range_spec; + if (range_spec == RANGE_NONE) { + range_spec = cal.range_spec; + } + + if (range_spec == RANGE_LO) { + cal.offset_lo = offs; + } + if (range_spec == RANGE_HI) { + cal.offset_hi = offs; + } + + return "OK"; } /** Handle command AIN:CHn:GAIN[:range] */ std::string cmd_channel_gain(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + double gain; + if ((! parse_float(arg, gain)) + || (gain < -1e6) + || (gain > 1e6)) { + return err_invalid_argument(); + } + + ChannelCalibration& cal = m_calibration.channel_cal[env.channel]; + RangeSpec range_spec = env.range_spec; + if (range_spec == RANGE_NONE) { + range_spec = cal.range_spec; + } + + if (range_spec == RANGE_LO) { + cal.gain_lo = gain; + } + if (range_spec == RANGE_HI) { + cal.gain_hi = gain; + } + + return "OK"; + } + + /** + * Return minimum sample rate divisor depending on trigger mode + * and number of active channels. + * + * In auto-trigger mode, divisor must be at least 2. + * In 4-channel mode, divisor must be at least 2 or 4 depending + * on auto-trigger mode. + */ + unsigned int min_srate_divisor(bool trig_auto, bool ch4) + { + unsigned int min_divisor = 1; + if (trig_auto) { + min_divisor *= 2; + } + if (ch4) { + min_divisor += 2; + } + return min_divisor; + } + + /** Commond handling for setting sample rate. */ + std::string set_srate_divisor(unsigned int divisor) + { + unsigned int min_divisor = min_srate_divisor( + m_device.get_trigger_mode() == TRIG_AUTO, + m_device.is_4channel_mode()); + + if ((divisor < min_divisor) + || (divisor > PuzzleFwDevice::MAX_DECIMATION_FACTOR)) { + return err_invalid_argument(); + } + + m_device.set_decimation_factor(divisor); + + if (m_device.is_averaging_enabled()) { + // Adjust shift steps to avoid 24-bit overflow. + int shift_steps = 0; + while (divisor > (1UL << (10 + shift_steps))) { + shift_steps++; + } + m_device.set_shift_steps(shift_steps); + } + + return "OK"; } /** Handle command AIN:SRATE */ std::string cmd_srate(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + double v; + if ((! parse_float(arg, v)) || (v < 1) || (v > 125e6)) { + return err_invalid_argument(); + } + + unsigned int divisor = lrint(125e6 / v); + return set_srate_divisor(divisor); } /** Handle command AIN:SRATE:DIVISOR */ std::string cmd_srate_divisor(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + unsigned int divisor; + if (! parse_uint(arg, divisor)) { + return err_invalid_argument(); + } + + return set_srate_divisor(divisor); } /** Handle command AIN:SRATE:MODE */ std::string cmd_srate_mode(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + std::string srate_mode = str_to_lower(arg); + + if (srate_mode == "average") { + + // Adjust shift steps to avoid 24-bit overflow. + unsigned int divisor = m_device.get_decimation_factor(); + int shift_steps = 0; + while (divisor > (1UL << (10 + shift_steps))) { + shift_steps++; + } + + m_device.set_averaging_enabled(true); + m_device.set_shift_steps(shift_steps); + } else if (srate_mode == "decimate") { + m_device.set_averaging_enabled(false); + m_device.set_shift_steps(0); + } else { + return err_invalid_argument(); + } + + return "OK"; } /** Handle command AIN:NSAMPLES */ std::string cmd_nsamples(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + unsigned int n; + if ((! parse_uint(arg, n)) + || (n < 1) + || (n > PuzzleFwDevice::MAX_RECORD_LENGTH)) { + return err_invalid_argument(); + } + m_device.set_record_length(n); + return "OK"; + } + + /** Handle command AIN:TRIGGER */ + std::string cmd_trigger(CommandEnvironment env) + { + m_device.trigger_force(); + return "OK"; } /** Handle command AIN:TRIGGER:MODE */ std::string cmd_trigger_mode(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + std::string trigger_mode = str_to_lower(arg); + + if (trigger_mode == "none") { + m_device.set_trigger_mode(TRIG_NONE); + } else if (trigger_mode == "auto") { + // Reduce sample rate if necessary. + unsigned int min_divisor = min_srate_divisor( + true, + m_device.is_4channel_mode()); + unsigned int divisor = m_device.get_decimation_factor(); + if (divisor < min_divisor) { + m_device.set_decimation_factor(min_divisor); + } + m_device.set_trigger_mode(TRIG_AUTO); + } else if (trigger_mode == "external") { + m_device.set_trigger_mode(TRIG_EXTERNAL); + } else if (trigger_mode == "external_once") { + m_device.set_trigger_mode(TRIG_EXTERNAL_ONCE); + } else { + return err_invalid_argument(); + } + + return "OK"; + } + + /** Handle command AIN:TRIGGER:EXT:CHANNEL */ + std::string cmd_trigger_ext_channel(CommandEnvironment env, + const std::string& arg) + { + unsigned int n; + if ((! parse_uint(arg, n)) || (n > 3)) { + return err_invalid_argument(); + } + m_device.set_trigger_ext_channel(n); + return "OK"; + } + + /** Handle command AIN:TRIGGER:EXT:EDGE */ + std::string cmd_trigger_ext_edge(CommandEnvironment env, + const std::string& arg) + { + std::string edge = str_to_lower(arg); + if (edge == "rising") { + m_device.set_trigger_ext_falling(false); + } else if (edge == "falling") { + m_device.set_trigger_ext_falling(true); + } else { + return err_invalid_argument(); + } + return "OK"; } /** Handle command AIN:TRIGGER:DELAY */ std::string cmd_trigger_delay(CommandEnvironment env, const std::string& arg) { - // TODO - return "ERROR"; + unsigned int n; + if ((! parse_uint(arg, n)) + || (n > PuzzleFwDevice::MAX_TRIGGER_DELAY)) { + return err_invalid_argument(); + } + m_device.set_trigger_delay(n); + return "OK"; } - /** Handle command TT:CHANNEL:MASK */ - std::string cmd_tt_channel_mask(CommandEnvironment env, - const std::string& arg) + /** Handle command TT:EVENT:MASK */ + std::string cmd_tt_event_mask(CommandEnvironment env, + const std::string& arg) { - // TODO + unsigned int n; + if ((! parse_uint(arg, n)) || (n > 255)) { + return err_invalid_argument(); + } + m_device.set_timetagger_event_mask(n); + return "OK"; + } + + /** Handle command AIN:MINMAX:CLEAR */ + std::string cmd_cal_save(CommandEnvironment env) + { + // TODO - write calibration to file + // TODO - invoke script to save to SD card return "ERROR"; } @@ -572,8 +973,9 @@ private: const std::string& netmask, const std::string& gateway) { - // TODO - return "OK"; + // TODO -- parse and check address + // TODO -- activate or save address, restart networking if needed + return "ERROR"; } static const inline std::map< @@ -591,24 +993,29 @@ private: { "ain:chN:gain:RR?", &CommandHandler::qry_channel_gain }, { "ain:chN:sample?", &CommandHandler::qry_channel_sample }, { "ain:chN:sample:raw?", &CommandHandler::qry_channel_sample }, - { "ain:chN:minman?", &CommandHandler::qry_channel_minmax }, - { "ain:chN:manmax:raw?", &CommandHandler::qry_channel_minmax }, + { "ain:chN:minmax?", &CommandHandler::qry_channel_minmax }, + { "ain:chN:minmax:raw?", &CommandHandler::qry_channel_minmax }, { "ain:srate?", &CommandHandler::qry_srate }, { "ain:srate:divisor?", &CommandHandler::qry_srate_divisor }, { "ain:srate:mode?", &CommandHandler::qry_srate_mode }, { "ain:srate:gain?", &CommandHandler::qry_srate_gain }, { "ain:nsamples?", &CommandHandler::qry_nsamples }, { "ain:trigger:mode?", &CommandHandler::qry_trigger_mode }, + { "ain:trigger:ext:channel?", + &CommandHandler::qry_trigger_ext_channel }, + { "ain:trigger:ext:edge?", &CommandHandler::qry_trigger_ext_edge }, { "ain:trigger:delay?", &CommandHandler::qry_trigger_delay }, { "ain:trigger:status?", &CommandHandler::qry_trigger_status }, { "tt:sample?", &CommandHandler::qry_tt_sample }, - { "tt:channel:mask?", &CommandHandler::qry_tt_channel_mask }, + { "tt:event:mask?", &CommandHandler::qry_tt_event_mask }, { "ipcfg?", &CommandHandler::qry_ipcfg }, { "ipcfg:saved?", &CommandHandler::qry_ipcfg }, { "reset", &CommandHandler::cmd_reset }, { "halt", &CommandHandler::cmd_halt }, { "reboot", &CommandHandler::cmd_reboot }, + { "ain:cal:save", &CommandHandler::cmd_cal_save }, { "ain:minmax:clear", &CommandHandler::cmd_minmax_clear }, + { "ain:trigger", &CommandHandler::cmd_trigger }, { "tt:mark", &CommandHandler::cmd_tt_mark } }; @@ -627,8 +1034,10 @@ private: { "ain:srate:mode", &CommandHandler::cmd_srate_mode }, { "ain:nsamples", &CommandHandler::cmd_nsamples }, { "ain:trigger:mode", &CommandHandler::cmd_trigger_mode }, + { "ain:trigger:ext:channel", &CommandHandler::cmd_trigger_ext_channel }, + { "ain:trigger:ext:edge", &CommandHandler::cmd_trigger_ext_edge }, { "ain:trigger:delay", &CommandHandler::cmd_trigger_delay }, - { "tt:channel:mask", &CommandHandler::cmd_tt_channel_mask } + { "tt:event:mask", &CommandHandler::cmd_tt_event_mask } }; asio::io_context& m_io; @@ -637,6 +1046,7 @@ private: std::string m_serial_number; ControlServer* m_control_server; std::vector m_data_servers; + Calibration m_calibration; bool m_shutting_down; ExitStatus m_exit_status; }; @@ -663,6 +1073,12 @@ public: ControlServer(const ControlServer&) = delete; ControlServer& operator=(const ControlServer&) = delete; + /** Return the Asio strand that runs all handlers for this object. */ + asio::strand get_executor() + { + return m_strand; + } + /** * Start the server. * @@ -956,6 +1372,75 @@ private: }; +/* ******** Methods for class CommandHandler ******** */ + +void CommandHandler::stop_server(bool stop_control, + std::function handler) +{ + if (stop_control) { + asio::post(m_control_server->get_executor(), + [this,handler]() { + m_control_server->stop_server(); + asio::post(m_strand, + [this,handler]() { + stop_data_servers(0, handler); + }); + }); + } else { + stop_data_servers(0, handler); + } +} + +void CommandHandler::stop_data_servers(unsigned int idx, + std::function handler) +{ + if (idx < m_data_servers.size()) { + asio::post(m_data_servers[idx]->get_executor(), + [this,idx,handler]() { + m_data_servers[idx]->stop_server(); + asio::post(m_strand, + [this,idx,handler]() { + stop_data_servers(idx + 1, handler); + }); + }); + } else { + handler(); + } +} + +void CommandHandler::start_server(bool start_control) +{ + if (start_control) { + asio::post(m_control_server->get_executor(), + [this]() { + m_control_server->start_server(); + asio::post(m_strand, + [this]() { + start_data_servers(0); + }); + }); + } else { + start_data_servers(0); + } +} + +void CommandHandler::start_data_servers(unsigned int idx) +{ + if (idx < m_data_servers.size()) { + asio::post(m_data_servers[idx]->get_executor(), + [this,idx]() { + m_data_servers[idx]->start_server(); + asio::post(m_strand, + [this,idx]() { + start_data_servers(idx + 1); + }); + }); + } +} + + +/* ******** Main program ******** */ + /** Run remote control server. */ int run_remote_control_server( puzzlefw::PuzzleFwDevice& device, @@ -1011,13 +1496,19 @@ int run_remote_control_server( command_handler.add_data_server(acq_server); command_handler.add_data_server(timetagger_server); - // Disable DMA engine on exit from this function. + // Restore firmware status on exit from this function. struct ScopeGuard { PuzzleFwDevice& m_device; ScopeGuard(PuzzleFwDevice& device) : m_device(device) { } - ~ScopeGuard() { m_device.set_dma_enabled(false); } + ~ScopeGuard() { + m_device.set_dma_enabled(false); + m_device.set_acquisition_enabled(false); + } } scope_guard(device); + // Reset instrument. + command_handler.reset(); + // Clear DMA errors, then enable DMA engine. device.clear_dma_errors(); device.set_dma_enabled(true);