Continue work on remote control server

This commit is contained in:
Joris van Rantwijk 2024-09-28 21:22:24 +02:00
parent ea5d3c3a1d
commit 60f7df6fd6
4 changed files with 598 additions and 107 deletions

View File

@ -9,6 +9,7 @@
#ifndef PUZZLEFW_INTERRUPT_MANAGER_H_ #ifndef PUZZLEFW_INTERRUPT_MANAGER_H_
#define PUZZLEFW_INTERRUPT_MANAGER_H_ #define PUZZLEFW_INTERRUPT_MANAGER_H_
#include <functional>
#include <initializer_list> #include <initializer_list>
#include <system_error> #include <system_error>
#include <vector> #include <vector>

View File

@ -21,19 +21,6 @@
#include "data_server.hpp" #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. */ /** Show firmware status. */
void show_status(puzzlefw::PuzzleFwDevice& device) void show_status(puzzlefw::PuzzleFwDevice& device)
{ {

View File

@ -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. */ /** Firmware version information. */
struct VersionInfo { struct VersionInfo {
uint8_t api_version; uint8_t api_version;

View File

@ -9,9 +9,12 @@
#include <ctype.h> #include <ctype.h>
#include <getopt.h> #include <getopt.h>
#include <limits.h> #include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <functional>
#include <istream> #include <istream>
#include <map> #include <map>
#include <memory> #include <memory>
@ -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. // Forward declaration.
class ControlServer; class ControlServer;
@ -60,14 +112,20 @@ class ControlServer;
class CommandHandler class CommandHandler
{ {
public: public:
// IDN response fields // IDN response fields.
static constexpr std::string_view IDN_MANUFACTURER = "Jigsaw"; static constexpr const char * IDN_MANUFACTURER = "Jigsaw";
static constexpr std::string_view IDN_MODEL = "PuzzleFw"; 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 { enum ExitStatus {
EXIT_ERROR = 1, EXIT_ERROR = 1,
EXIT_HALT = 2, EXIT_HALT = 10,
EXIT_REBOOT = 3 EXIT_REBOOT = 11
}; };
enum RangeSpec { enum RangeSpec {
@ -76,6 +134,18 @@ public:
RANGE_HI = 2 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 { struct CommandEnvironment {
int channel; int channel;
RangeSpec range_spec; RangeSpec range_spec;
@ -124,6 +194,34 @@ public:
return m_strand; 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. * Handle a command.
* *
@ -162,11 +260,11 @@ 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 < '1' || channel_digit > '4') { if (channel_digit < '0' || channel_digit > '3') {
return err_unknown_command(); return err_unknown_command();
} }
env.channel = channel_digit - '1'; // 0-based channel index env.channel = channel_digit - '0';
action[7] = 'N'; // mark channel index action[6] = 'N'; // mark channel index
} }
// Extract range specifier. // Extract range specifier.
@ -239,7 +337,37 @@ public:
} }
private: private:
// TODO -- shutdown flow /** Asynchronously stop control and/or data servers. */
void stop_server(bool stop_control, std::function<void()> handler);
void stop_data_servers(unsigned int idx, std::function<void()> 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 std::string err_unknown_command() const
{ {
@ -256,6 +384,11 @@ private:
return "ERROR Missing argument"; return "ERROR Missing argument";
} }
std::string err_invalid_argument() const
{
return "ERROR Invalid argument";
}
/** Parse command to a list of white space separated tokens. */ /** Parse command to a list of white space separated tokens. */
std::vector<std::string> parse_command(const std::string& command) std::vector<std::string> parse_command(const std::string& command)
{ {
@ -285,154 +418,204 @@ private:
std::string qry_idn(CommandEnvironment env) std::string qry_idn(CommandEnvironment env)
{ {
VersionInfo fw_version = m_device.get_version_info(); VersionInfo fw_version = m_device.get_version_info();
std::stringstream idn; return str_format("%s,%s,%s,FW-%d.%d/SW-%d.%d",
idn << IDN_MANUFACTURER; IDN_MANUFACTURER,
idn << "," << IDN_MODEL; IDN_MODEL,
idn << "," << m_serial_number; m_serial_number.c_str(),
idn << ","; fw_version.major_version,
idn << "FW-" << (int)fw_version.major_version; fw_version.minor_version,
idn << "." << (int)fw_version.minor_version; PUZZLEFW_SW_MAJOR,
idn << "/SW-" << PUZZLEFW_SW_MAJOR << "." << PUZZLEFW_SW_MINOR; PUZZLEFW_SW_MINOR);
return idn.str();
} }
/** Handle command TIMESTAMP? */ /** Handle command TIMESTAMP? */
std::string qry_timestamp(CommandEnvironment env) std::string qry_timestamp(CommandEnvironment env)
{ {
// TODO uint64_t timestamp = m_device.get_timestamp();
return "ERROR"; return std::to_string(timestamp);
} }
/** Handle command AIN:CHANNELS:COUNT? */ /** Handle command AIN:CHANNELS:COUNT? */
std::string qry_channels_count(CommandEnvironment env) std::string qry_channels_count(CommandEnvironment env)
{ {
// TODO unsigned int n = m_device.get_analog_channel_count();
return "ERROR"; return std::to_string(n);
} }
/** Handle command AIN:CHANNELS:ACTIVE? */ /** Handle command AIN:CHANNELS:ACTIVE? */
std::string qry_channels_active(CommandEnvironment env) std::string qry_channels_active(CommandEnvironment env)
{ {
// TODO return m_device.is_4channel_mode() ? "4" : "2";
return "ERROR";
} }
/** Handle command AIN:CHn:RANGE? */ /** Handle command AIN:CHn:RANGE? */
std::string qry_channel_range(CommandEnvironment env) std::string qry_channel_range(CommandEnvironment env)
{ {
// TODO ChannelCalibration& cal = m_calibration.channel_cal[env.channel];
return "ERROR"; return (cal.range_spec == RANGE_HI) ? "HI" : "LO";
} }
/** Handle command AIN:CHn:OFFS[:range]? */ /** Handle command AIN:CHn:OFFS[:range]? */
std::string qry_channel_offs(CommandEnvironment env) std::string qry_channel_offs(CommandEnvironment env)
{ {
// TODO ChannelCalibration& cal = m_calibration.channel_cal[env.channel];
return "ERROR"; 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]? */ /** Handle command AIN:CHn:GAIN[:range]? */
std::string qry_channel_gain(CommandEnvironment env) std::string qry_channel_gain(CommandEnvironment env)
{ {
// TODO ChannelCalibration& cal = m_calibration.channel_cal[env.channel];
return "ERROR"; 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]? */ /** Handle command AIN:CHn:SAMPLE[:RAW]? */
std::string qry_channel_sample(CommandEnvironment env) std::string qry_channel_sample(CommandEnvironment env)
{ {
// TODO unsigned int sample = m_device.get_adc_sample(env.channel);
return "ERROR"; 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]? */ /** Handle command AIN:CHn:MINMAX[:RAW]? */
std::string qry_channel_minmax(CommandEnvironment env) std::string qry_channel_minmax(CommandEnvironment env)
{ {
// TODO unsigned int min_sample, max_sample;
return "ERROR"; 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? */ /** Handle command AIN:SRATE? */
std::string qry_srate(CommandEnvironment env) std::string qry_srate(CommandEnvironment env)
{ {
// TODO unsigned int divisor = m_device.get_decimation_factor();
return "ERROR"; double srate = 125e6 / divisor;
return str_format("%.3f", srate);
} }
/** Handle command AIN:SRATE:DIVISOR? */ /** Handle command AIN:SRATE:DIVISOR? */
std::string qry_srate_divisor(CommandEnvironment env) std::string qry_srate_divisor(CommandEnvironment env)
{ {
// TODO unsigned int divisor = m_device.get_decimation_factor();
return "ERROR"; return std::to_string(divisor);
} }
/** Handle command AIN:SRATE:MODE? */ /** Handle command AIN:SRATE:MODE? */
std::string qry_srate_mode(CommandEnvironment env) std::string qry_srate_mode(CommandEnvironment env)
{ {
// TODO return m_device.is_averaging_enabled() ? "AVERAGE" : "DECIMATE";
return "ERROR";
} }
/** Handle command AIN:SRATE:GAIN? */ /** Handle command AIN:SRATE:GAIN? */
std::string qry_srate_gain(CommandEnvironment env) std::string qry_srate_gain(CommandEnvironment env)
{ {
// TODO double gain = 1.0;
return "ERROR"; 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<double>(1 << shift_steps);
}
return str_format("%.8f", gain);
} }
/** Handle command AIN:NSAMPLES? */ /** Handle command AIN:NSAMPLES? */
std::string qry_nsamples(CommandEnvironment env) std::string qry_nsamples(CommandEnvironment env)
{ {
// TODO unsigned int nsamples = m_device.get_record_length();
return "ERROR"; return std::to_string(nsamples);
} }
/** Handle command AIN:TRIGGER:MODE? */ /** Handle command AIN:TRIGGER:MODE? */
std::string qry_trigger_mode(CommandEnvironment env) std::string qry_trigger_mode(CommandEnvironment env)
{ {
// TODO TriggerMode trigger_mode = m_device.get_trigger_mode();
return "ERROR"; 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? */ /** Handle command AIN:TRIGGER:DELAY? */
std::string qry_trigger_delay(CommandEnvironment env) std::string qry_trigger_delay(CommandEnvironment env)
{ {
// TODO unsigned int n = m_device.get_trigger_delay();
return "ERROR"; return std::to_string(n);
} }
/** Handle command AIN:TRIGGER:STATUS? */ /** Handle command AIN:TRIGGER:STATUS? */
std::string qry_trigger_status(CommandEnvironment env) std::string qry_trigger_status(CommandEnvironment env)
{ {
// TODO if (m_device.is_waiting_for_trigger()) {
return "ERROR"; return "WAITING";
} else {
if (m_device.is_acquisition_enabled()) {
return "BUSY";
} else {
return "IDLE";
}
}
} }
/** Handle command TT:SAMPLE? */ /** Handle command TT:SAMPLE? */
std::string qry_tt_sample(CommandEnvironment env) std::string qry_tt_sample(CommandEnvironment env)
{ {
// TODO unsigned int sample = m_device.get_digital_input_state();
return "ERROR"; return str_format("%d %d %d %d",
(sample & 1),
((sample >> 1) & 1),
((sample >> 2) & 1),
((sample >> 3) & 1));
} }
/** Handle command TT:CHANNEL:MASK? */ /** Handle command TT:EVENT:MASK? */
std::string qry_tt_channel_mask(CommandEnvironment env) std::string qry_tt_event_mask(CommandEnvironment env)
{ {
// TODO unsigned int event_mask = m_device.get_timetagger_event_mask();
return "ERROR"; return std::to_string(event_mask);
} }
/** Handle command IPCFG[:SAVED]? */ /** Handle command IPCFG[:SAVED]? */
std::string qry_ipcfg(CommandEnvironment env) std::string qry_ipcfg(CommandEnvironment env)
{ {
// TODO // TODO - read network config from file
return "ERROR"; return "ERROR";
} }
/** Handle command RESET */ /** Handle command RESET */
std::string cmd_reset(CommandEnvironment env) std::string cmd_reset(CommandEnvironment env)
{ {
// TODO -- reset reset();
return "OK"; return "OK";
} }
@ -467,102 +650,320 @@ private:
/** Handle command AIN:MINMAX:CLEAR */ /** Handle command AIN:MINMAX:CLEAR */
std::string cmd_minmax_clear(CommandEnvironment env) std::string cmd_minmax_clear(CommandEnvironment env)
{ {
// TODO m_device.clear_adc_range();
return "ERROR"; return "OK";
} }
/** Handle command TT:MARK */ /** Handle command TT:MARK */
std::string cmd_tt_mark(CommandEnvironment env) std::string cmd_tt_mark(CommandEnvironment env)
{ {
// TODO m_device.timetagger_mark();
return "ERROR"; return "OK";
} }
/** Handle command AIN:CHANNELS:ACTIVE */ /** Handle command AIN:CHANNELS:ACTIVE */
std::string cmd_channels_active(CommandEnvironment env, std::string cmd_channels_active(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO unsigned int n;
return "ERROR"; 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 */ /** Handle command AIN:CHn:RANGE */
std::string cmd_channel_range(CommandEnvironment env, std::string cmd_channel_range(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO std::string range_name = str_to_lower(arg);
return "ERROR";
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] */ /** Handle command AIN:CHn:OFFS[:range] */
std::string cmd_channel_offs(CommandEnvironment env, std::string cmd_channel_offs(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO double offs;
return "ERROR"; 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] */ /** Handle command AIN:CHn:GAIN[:range] */
std::string cmd_channel_gain(CommandEnvironment env, std::string cmd_channel_gain(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO double gain;
return "ERROR"; 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 */ /** Handle command AIN:SRATE */
std::string cmd_srate(CommandEnvironment env, std::string cmd_srate(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO double v;
return "ERROR"; 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 */ /** Handle command AIN:SRATE:DIVISOR */
std::string cmd_srate_divisor(CommandEnvironment env, std::string cmd_srate_divisor(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO unsigned int divisor;
return "ERROR"; if (! parse_uint(arg, divisor)) {
return err_invalid_argument();
}
return set_srate_divisor(divisor);
} }
/** Handle command AIN:SRATE:MODE */ /** Handle command AIN:SRATE:MODE */
std::string cmd_srate_mode(CommandEnvironment env, std::string cmd_srate_mode(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO std::string srate_mode = str_to_lower(arg);
return "ERROR";
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 */ /** Handle command AIN:NSAMPLES */
std::string cmd_nsamples(CommandEnvironment env, std::string cmd_nsamples(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO unsigned int n;
return "ERROR"; 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 */ /** Handle command AIN:TRIGGER:MODE */
std::string cmd_trigger_mode(CommandEnvironment env, std::string cmd_trigger_mode(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO std::string trigger_mode = str_to_lower(arg);
return "ERROR";
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 */ /** Handle command AIN:TRIGGER:DELAY */
std::string cmd_trigger_delay(CommandEnvironment env, std::string cmd_trigger_delay(CommandEnvironment env,
const std::string& arg) const std::string& arg)
{ {
// TODO unsigned int n;
return "ERROR"; 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 */ /** Handle command TT:EVENT:MASK */
std::string cmd_tt_channel_mask(CommandEnvironment env, std::string cmd_tt_event_mask(CommandEnvironment env,
const std::string& arg) 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"; return "ERROR";
} }
@ -572,8 +973,9 @@ private:
const std::string& netmask, const std::string& netmask,
const std::string& gateway) const std::string& gateway)
{ {
// TODO // TODO -- parse and check address
return "OK"; // TODO -- activate or save address, restart networking if needed
return "ERROR";
} }
static const inline std::map< static const inline std::map<
@ -591,24 +993,29 @@ private:
{ "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 },
{ "ain:chN:sample:raw?", &CommandHandler::qry_channel_sample }, { "ain:chN:sample:raw?", &CommandHandler::qry_channel_sample },
{ "ain:chN:minman?", &CommandHandler::qry_channel_minmax }, { "ain:chN:minmax?", &CommandHandler::qry_channel_minmax },
{ "ain:chN:manmax:raw?", &CommandHandler::qry_channel_minmax }, { "ain:chN:minmax:raw?", &CommandHandler::qry_channel_minmax },
{ "ain:srate?", &CommandHandler::qry_srate }, { "ain:srate?", &CommandHandler::qry_srate },
{ "ain:srate:divisor?", &CommandHandler::qry_srate_divisor }, { "ain:srate:divisor?", &CommandHandler::qry_srate_divisor },
{ "ain:srate:mode?", &CommandHandler::qry_srate_mode }, { "ain:srate:mode?", &CommandHandler::qry_srate_mode },
{ "ain:srate:gain?", &CommandHandler::qry_srate_gain }, { "ain:srate:gain?", &CommandHandler::qry_srate_gain },
{ "ain:nsamples?", &CommandHandler::qry_nsamples }, { "ain:nsamples?", &CommandHandler::qry_nsamples },
{ "ain:trigger:mode?", &CommandHandler::qry_trigger_mode }, { "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:delay?", &CommandHandler::qry_trigger_delay },
{ "ain:trigger:status?", &CommandHandler::qry_trigger_status }, { "ain:trigger:status?", &CommandHandler::qry_trigger_status },
{ "tt:sample?", &CommandHandler::qry_tt_sample }, { "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?", &CommandHandler::qry_ipcfg },
{ "ipcfg:saved?", &CommandHandler::qry_ipcfg }, { "ipcfg:saved?", &CommandHandler::qry_ipcfg },
{ "reset", &CommandHandler::cmd_reset }, { "reset", &CommandHandler::cmd_reset },
{ "halt", &CommandHandler::cmd_halt }, { "halt", &CommandHandler::cmd_halt },
{ "reboot", &CommandHandler::cmd_reboot }, { "reboot", &CommandHandler::cmd_reboot },
{ "ain:cal:save", &CommandHandler::cmd_cal_save },
{ "ain:minmax:clear", &CommandHandler::cmd_minmax_clear }, { "ain:minmax:clear", &CommandHandler::cmd_minmax_clear },
{ "ain:trigger", &CommandHandler::cmd_trigger },
{ "tt:mark", &CommandHandler::cmd_tt_mark } { "tt:mark", &CommandHandler::cmd_tt_mark }
}; };
@ -627,8 +1034,10 @@ private:
{ "ain:srate:mode", &CommandHandler::cmd_srate_mode }, { "ain:srate:mode", &CommandHandler::cmd_srate_mode },
{ "ain:nsamples", &CommandHandler::cmd_nsamples }, { "ain:nsamples", &CommandHandler::cmd_nsamples },
{ "ain:trigger:mode", &CommandHandler::cmd_trigger_mode }, { "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 }, { "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; asio::io_context& m_io;
@ -637,6 +1046,7 @@ private:
std::string m_serial_number; std::string m_serial_number;
ControlServer* m_control_server; ControlServer* m_control_server;
std::vector<DataServer*> m_data_servers; std::vector<DataServer*> m_data_servers;
Calibration m_calibration;
bool m_shutting_down; bool m_shutting_down;
ExitStatus m_exit_status; ExitStatus m_exit_status;
}; };
@ -663,6 +1073,12 @@ public:
ControlServer(const ControlServer&) = delete; ControlServer(const ControlServer&) = delete;
ControlServer& operator=(const ControlServer&) = delete; ControlServer& operator=(const ControlServer&) = delete;
/** Return the Asio strand that runs all handlers for this object. */
asio::strand<asio::io_context::executor_type> get_executor()
{
return m_strand;
}
/** /**
* Start the server. * Start the server.
* *
@ -956,6 +1372,75 @@ private:
}; };
/* ******** Methods for class CommandHandler ******** */
void CommandHandler::stop_server(bool stop_control,
std::function<void()> 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<void()> 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. */ /** Run remote control server. */
int run_remote_control_server( int run_remote_control_server(
puzzlefw::PuzzleFwDevice& device, puzzlefw::PuzzleFwDevice& device,
@ -1011,13 +1496,19 @@ int run_remote_control_server(
command_handler.add_data_server(acq_server); command_handler.add_data_server(acq_server);
command_handler.add_data_server(timetagger_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 { struct ScopeGuard {
PuzzleFwDevice& m_device; PuzzleFwDevice& m_device;
ScopeGuard(PuzzleFwDevice& device) : m_device(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); } scope_guard(device);
// Reset instrument.
command_handler.reset();
// Clear DMA errors, then enable DMA engine. // Clear DMA errors, then enable DMA engine.
device.clear_dma_errors(); device.clear_dma_errors();
device.set_dma_enabled(true); device.set_dma_enabled(true);