Compare commits
12 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
51fb79a9d1 | |
|
|
66444a3067 | |
|
|
4eb6ada765 | |
|
|
9687b65b6f | |
|
|
d38617c98f | |
|
|
051f24dc2d | |
|
|
7fcf233489 | |
|
|
2a1cf374b3 | |
|
|
2ab072b254 | |
|
|
98058d6291 | |
|
|
b32200ba2f | |
|
|
7cf66e031e |
|
|
@ -211,7 +211,20 @@ to open the USB serial port of the Red Pitaya.
|
|||
Set the baud rate to 115200 bps, character format to `8N1`.
|
||||
|
||||
Press Enter to get a login prompt on the console.
|
||||
Use login `root` with password `root`.
|
||||
|
||||
Login as user `root`.
|
||||
The default password is `root`.
|
||||
|
||||
### Changing the password
|
||||
|
||||
The default login password is `root`, but this can be changed.
|
||||
|
||||
To change the password, login on the console and run the command `puzzle-passwd`.
|
||||
The tool will prompt to type a new root password.
|
||||
The changed password will be persistently stored on the SD card.
|
||||
|
||||
Do not use the normal Unix command `passwd`.
|
||||
This tool does not write the new password to the SD card; therefore the changes will be forgotten after reboot.
|
||||
|
||||
|
||||
## Network access
|
||||
|
|
@ -241,16 +254,47 @@ This is the same host name as used by the official Red Pitaya software.
|
|||
It is possible to run an SSH server on the Red Pitaya.
|
||||
This can be used to remotely log in on the Linux system.
|
||||
|
||||
To login via SSH, use username `root` with password `root`.
|
||||
To login via SSH, use username `root` and the same password used for logging in via the USB console.
|
||||
|
||||
For security reasons, the SSH server is disabled by default.
|
||||
The default password is `root`.
|
||||
It is recommended to change the password before enabling SSH access.
|
||||
An SSH server with an easy-to-guess password should never be connected to an untrusted network.
|
||||
|
||||
If you want to use the SSH server, you have to enable it explicitly.
|
||||
To enable the SSH server, login on the USB console as described above.
|
||||
Then run the following command: `puzzle-sshcfg enable` .
|
||||
Finally, run `reboot` to reboot the Red Pitaya.
|
||||
For security reasons, the SSH server is disabled by default.
|
||||
If you want to login via SSH, it must be enabled explicitly.
|
||||
Use the following steps to enable the SSH server:
|
||||
|
||||
- Login on the USB console as described above.
|
||||
- Set a safe password by running command: `puzzle-passwd`
|
||||
- Enable the SSH server by running command: `puzzle-sshcfg enable`
|
||||
- Reboot the Red Pitaya by running command: `reboot`
|
||||
|
||||
From this point onward, the SSH server will be started automatically during boot.
|
||||
You can again use the command `puzzle-sshcfg` to disable SSH access.
|
||||
|
||||
### NTP configuration
|
||||
|
||||
Optionally, an NTP server may be configured to synchronize the system clock via the network.
|
||||
Without an NTP server, the system clock of the Red Pitaya is initialized to the year 1970 on power-on.
|
||||
|
||||
The only feature that uses the system clock is the remote control command `REALTIME?` .
|
||||
If this command is used, configuring an NTP server is necessary to get correct results.
|
||||
If this command is not used, synchronizing the system clock is not important.
|
||||
|
||||
To configure an NTP server, use the following steps:
|
||||
|
||||
- Login on the Red Pitaya via USB console or SSH as described above.
|
||||
- Run the command `puzzle-ntpcfg server <IP-address>` .
|
||||
|
||||
This command configures Chrony to synchronize to the specified NTP server.
|
||||
The configuration is also written to the SD card and used during boot.
|
||||
|
||||
Optionally, a poll interval can be specified as a power of 2 in seconds.
|
||||
For example: `puzzle-ntpcfg server <IP-address> poll 5` specifies a poll interval of 32 seconds.
|
||||
|
||||
Sub-millisecond accuracy can be achieved with an NTP server in the local network and `poll 0` (poll each second).
|
||||
Poll intervals shorter than 64 seconds must only be used with a dedicated NTP server in the local network.
|
||||
Using such short poll intervals with a public NTP server is considered abuse.
|
||||
|
||||
|
||||
## Data stream protocol
|
||||
|
|
@ -330,6 +374,7 @@ In the response string, such data elements are separated by space characters.
|
|||
| `*IDN?` | Instrument identification. |
|
||||
| `RESET` | Restore default settings. |
|
||||
| `TIMESTAMP?` | Timestamp counter. |
|
||||
| `REALTIME?` | System time and timestamp counter. |
|
||||
| `AIN:CHANNELS:COUNT?` | Number of input channels. |
|
||||
| `AIN:CHANNELS:ACTIVE` | Number of active input channels. |
|
||||
| `AIN:CHn:RANGE` | Analog input range. |
|
||||
|
|
@ -351,9 +396,11 @@ In the response string, such data elements are separated by space characters.
|
|||
| `AIN:TRIGGER:EXT:CHANNEL` | External trigger channel. |
|
||||
| `AIN:TRIGGER:EXT:EDGE` | External trigger edge. |
|
||||
| `AIN:ACQUIRE:ENABLE` | Enable analog acquisition. |
|
||||
| `AIN:CLEAR` | Clear buffer and drop data connection. |
|
||||
| `TT:SAMPLE?` | Digital input state. |
|
||||
| `TT:EVENT:MASK` | Timetagger event mask. |
|
||||
| `TT:MARK` | Emit timetagger marker. |
|
||||
| `TT:CLEAR` | Clear buffer and drop data connection. |
|
||||
| `TEMP:FPGA?` | FPGA temperature. |
|
||||
| `IPCFG[:SAVED]` | IP address configuration. |
|
||||
| `HALT` | Shut down system. |
|
||||
|
|
@ -389,6 +436,23 @@ Any ongoing analog acquisition is stopped.
|
|||
Query: `TIMESTAMP?` <br>
|
||||
Response: decimal integer, representing the current timestamp in units of 8 ns.
|
||||
|
||||
### `REALTIME?`
|
||||
|
||||
Query: `REALTIME?` <br>
|
||||
Response: system time and firmware timestamp, separated by a space character.
|
||||
|
||||
The system time is represented as a floating point number denoting the number of seconds since 1970-01-01 00:00 UTC (ignoring leap seconds).
|
||||
|
||||
The firmware timestamp is represented as a decimal integer denoting the current timestamp in units of 8 ns.
|
||||
|
||||
This command is only useful if the Red Pitaya is configured to synchronize its system clock via NTP.
|
||||
Without NTP, the system time of the Red Pitaya will be incorrect and essentially meaningless.
|
||||
|
||||
The system time and firmware timestamp are captured nearly simultaneous, but not exactly simultaneous.
|
||||
The mismatch between these two time values is almost always less than 10 microseconds.
|
||||
The overall accuracy of the system time is limited by the synchronization via NTP.
|
||||
In practice, sub-millisecond accuracy is possible with an NTP server in the local network.
|
||||
|
||||
### `AIN:CHANNELS:COUNT?`
|
||||
|
||||
Query: `AIN:CHANNELS:COUNT?` <br>
|
||||
|
|
@ -626,6 +690,17 @@ When disabled, all triggers are ignored and any ongoing analog acquisition stops
|
|||
Query: `AIN:ACQUIRE:ENABLE?` <br>
|
||||
Response: either `0` or `1`.
|
||||
|
||||
### `AIN:CLEAR`
|
||||
|
||||
Command: `AIN:CLEAR`
|
||||
|
||||
This command clears internal buffers holding analog acquisition data.
|
||||
It also drops the current data connection to port 5001, if any.
|
||||
|
||||
Clients should send this command just before connecting to the analog
|
||||
sample data port and enabling analog data acquisition.
|
||||
This prevents the transmission of stale data from a previous run.
|
||||
|
||||
### `TT:SAMPLE?`
|
||||
|
||||
Query: `TT:SAMPLE?` <br>
|
||||
|
|
@ -662,6 +737,17 @@ Command: `TT:MARK`
|
|||
|
||||
This command emits a marker record in the timetagger event stream.
|
||||
|
||||
### `TT:CLEAR`
|
||||
|
||||
Command: `TT:CLEAR`
|
||||
|
||||
This command clears internal buffers holding timetagger data.
|
||||
It also drops the current data connection to port 5002, if any.
|
||||
|
||||
Clients should send this command just before connecting to the timetagger
|
||||
data port and enabling timetagger events.
|
||||
This prevents the transmission of stale data from a previous run.
|
||||
|
||||
### `TEMP:FPGA?`
|
||||
|
||||
Query: `TEMP:FPGA?` <br>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
include /etc/chrony_dhcp.conf
|
||||
pool pool.ntp.org iburst
|
||||
sourcedir /etc/chrony/sources.d
|
||||
makestep 0.1 3
|
||||
sched_priority 1
|
||||
cmdport 0
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# Disable chrony (NTP client)
|
||||
# Only start Chrony if an NTP server is configured.
|
||||
|
||||
if [ ! -f /etc/chrony/sources.d/ntp.sources ]; then
|
||||
echo "NTP server not configured, not starting chrony."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit
|
||||
|
|
|
|||
|
|
@ -5,6 +5,21 @@
|
|||
|
||||
. /opt/puzzlefw/lib/functions.sh
|
||||
|
||||
# Copy root password from configuration partition.
|
||||
copy_passwd() {
|
||||
|
||||
# Do nothing if there is no password on the configuration partition.
|
||||
[ -f ${CONFIG_DIR}/passwd.conf ] || return
|
||||
|
||||
# Do nothing if there is no root password entry in the config file.
|
||||
grep "^root:" ${CONFIG_DIR}/passwd.conf > /etc/shadow.new || return
|
||||
|
||||
grep -v "^root:" /etc/shadow >> /etc/shadow.new
|
||||
|
||||
chmod 0600 /etc/shadow.new
|
||||
mv /etc/shadow.new /etc/shadow
|
||||
}
|
||||
|
||||
# Copy SSH host key from configuration partition.
|
||||
copy_ssh_host_key() {
|
||||
|
||||
|
|
@ -25,12 +40,24 @@ copy_ssh_host_key() {
|
|||
chmod 0600 /etc/dropbear/dropbear_ed25519_host_key || true
|
||||
}
|
||||
|
||||
# Take NTP server from configuration partition.
|
||||
copy_ntp_server() {
|
||||
|
||||
# If an NTP server is configured, copy it to Chrony configuration.
|
||||
if [ -s ${CONFIG_DIR}/ntp.sources ]; then
|
||||
mkdir -p /etc/chrony/sources.d
|
||||
cp -p ${CONFIG_DIR}/ntp.sources /etc/chrony/sources.d
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo "Reading configuration files from SD card ..."
|
||||
lock_config || exit 1
|
||||
read_config || exit 1
|
||||
copy_passwd
|
||||
copy_ssh_host_key
|
||||
copy_ntp_server
|
||||
;;
|
||||
stop|restart|reload)
|
||||
true
|
||||
|
|
@ -276,7 +276,7 @@ case "$1" in
|
|||
*)
|
||||
script="${0##*/}"
|
||||
cat <<EOF
|
||||
Usage: $script {init|show|config|save|restart}
|
||||
Usage: $script {init|show|config|save}
|
||||
|
||||
Manage IP address configuration.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Manage NTP configuration.
|
||||
#
|
||||
|
||||
. /opt/puzzlefw/lib/functions.sh
|
||||
|
||||
# Show current configuration.
|
||||
ntpcfg_show() {
|
||||
|
||||
echo "Active NTP configuration:"
|
||||
if [ -s /etc/chrony/sources.d/ntp.sources ]; then
|
||||
cat /etc/chrony/sources.d/ntp.sources
|
||||
else
|
||||
echo "disabled"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "Saved NTP configuration:"
|
||||
if [ -s ${CONFIG_DIR}/ntp.sources ]; then
|
||||
cat ${CONFIG_DIR}/ntp.sources
|
||||
else
|
||||
echo "disabled"
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
# Check that parameter is a well-formed IPv4 address.
|
||||
check_ipaddr() {
|
||||
IFS="." read a b c d <<EOF
|
||||
$1
|
||||
EOF
|
||||
for i in "$a" "$b" "$c" "$d" ; do
|
||||
if ! [ "$i" -ge 0 -a "$i" -le 255 ]; then
|
||||
echo "ERROR: Invalid IP address '$1'" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Configure and enable Chrony.
|
||||
ntpcfg_server() {
|
||||
|
||||
if [ "$1" != "server" ]; then
|
||||
echo "ERROR: Invalid command '$1'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NTPSERVER="$2"
|
||||
|
||||
if [ -z "$NTPSERVER" ]; then
|
||||
echo "ERROR: Server IP address not specified" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_ipaddr "$NTPSERVER"
|
||||
|
||||
POLLOPTS=""
|
||||
|
||||
if [ "$#" -gt 2 ]; then
|
||||
|
||||
if [ "$3" != "poll" ]; then
|
||||
echo "ERROR: Unknown option '$3'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
POLLINT="$4"
|
||||
|
||||
if [ -z "$POLLINT" ]; then
|
||||
echo "ERROR: Poll interval not specified" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ "$POLLINT" -ge "-1" -a "$POLLINT" -le "10" ]; then
|
||||
echo "ERROR: Invalid poll interval '$POLLINT', must be between -1 and 10" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
POLLOPTS="minpoll $POLLINT maxpoll $POLLINT"
|
||||
|
||||
if [ "$#" -gt 4 ]; then
|
||||
echo "ERROR: Unexpected option '$5'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# Lock to avoid conflicting changes.
|
||||
lock_config || exit 1
|
||||
|
||||
echo "Configuring Chrony to start on boot with server $NTPSERVER ..."
|
||||
|
||||
SERVERLINE="server $NTPSERVER $POLLOPTS iburst prefer"
|
||||
echo "$SERVERLINE" > ${CONFIG_DIR}/ntp.sources.new
|
||||
|
||||
sync_config ntp.sources || exit 1
|
||||
|
||||
mkdir -p /etc/chrony/sources.d
|
||||
cp -p ${CONFIG_DIR}/ntp.sources /etc/chrony/sources.d
|
||||
|
||||
echo
|
||||
echo "New NTP configuration:"
|
||||
cat /etc/chrony/sources.d/ntp.sources
|
||||
echo
|
||||
|
||||
echo "Restarting Chrony ..."
|
||||
/etc/init.d/S49chrony restart
|
||||
}
|
||||
|
||||
# Disable starting Chrony during boot.
|
||||
ntpcfg_disable() {
|
||||
|
||||
# Lock to avoid conflicting changes.
|
||||
lock_config || exit 1
|
||||
|
||||
echo "Disabling Chrony startup on boot ..."
|
||||
|
||||
echo -n "" > ${CONFIG_DIR}/ntp.sources.new
|
||||
sync_config ntp.sources || exit 1
|
||||
|
||||
echo "Stopping Chrony ..."
|
||||
/etc/init.d/S49chrony stop
|
||||
|
||||
rm -f /etc/chrony/sources.d/ntp.sources
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
show)
|
||||
ntpcfg_show
|
||||
;;
|
||||
server)
|
||||
ntpcfg_server "$@"
|
||||
;;
|
||||
disable)
|
||||
ntpcfg_disable
|
||||
;;
|
||||
*)
|
||||
script="${0##*/}"
|
||||
cat <<EOF
|
||||
Usage: $script {server|disable|show}
|
||||
|
||||
Manage NTP server configuration.
|
||||
|
||||
$script server {IP-address} [poll N]
|
||||
Enable starting Chrony during boot and specify NTP server to use.
|
||||
|
||||
Optionally, the poll interval can be specified as a power of 2 in seconds,
|
||||
for example "poll 5" means a polling interval of 32 seconds. By default,
|
||||
the poll interval starts at 6 (64 seconds) and slowly steps up to
|
||||
10 (1024 seconds).
|
||||
|
||||
Sub-millisecond accuracy can be achieved with an NTP server in the local
|
||||
network and "poll 0" (poll each second).
|
||||
|
||||
Do not use a poll interval shorter than 64 seconds unless the NTP server
|
||||
is in the local network. Doing this with a public NTP server is
|
||||
considered abuse.
|
||||
|
||||
$script disable
|
||||
Disable starting Chrony during boot and stop Chrony if it was running.
|
||||
|
||||
$script show
|
||||
Display NTP configuration.
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $?
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Password change tool.
|
||||
#
|
||||
|
||||
. /opt/puzzlefw/lib/functions.sh
|
||||
|
||||
# Store changed password on the SD card.
|
||||
store_password() {
|
||||
|
||||
# Lock to avoid conflicting changes.
|
||||
lock_config || exit 1
|
||||
|
||||
echo "Writing new password to SD card ..."
|
||||
|
||||
grep "^root:" /etc/shadow > ${CONFIG_DIR}/passwd.conf.new
|
||||
sync_config passwd.conf || exit 1
|
||||
}
|
||||
|
||||
if [ $# -ne 0 ]; then
|
||||
script="${0##*/}"
|
||||
cat <<EOF
|
||||
Usage: $script
|
||||
|
||||
Change root password and write the new password hash to the SD card
|
||||
to make it persistent accross reboot.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
passwd && store_password
|
||||
|
||||
|
|
@ -62,6 +62,11 @@ enable() {
|
|||
|
||||
echo "start_ssh=1" > ${CONFIG_DIR}/start_ssh.conf.new
|
||||
sync_config start_ssh.conf || exit 1
|
||||
|
||||
echo
|
||||
echo "NOTE: Please remember to set a non-default root password."
|
||||
echo " Use 'puzzle-passwd' to change the root password."
|
||||
|
||||
}
|
||||
|
||||
# Disable starting SSH server during boot.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ read_config() {
|
|||
|
||||
# Copy config files to RAM filesystem.
|
||||
cp -a ${CONFIG_MOUNTPOINT}/*.conf $CONFIG_DIR || true
|
||||
cp -a ${CONFIG_MOUNTPOINT}/ntp.sources $CONFIG_DIR || true
|
||||
cp -a ${CONFIG_MOUNTPOINT}/dropbear_* $CONFIG_DIR || true
|
||||
|
||||
umount $CONFIG_MOUNTPOINT
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Configure chrony to use NTP server advertised by DHCP server.
|
||||
#
|
||||
|
||||
configure_ntp() {
|
||||
|
||||
# Do nothing if the DHCP server does not advertise any NTP server.
|
||||
[ -z "$ntpsrv" ] && return
|
||||
|
||||
# Write config snippet with NTP servers.
|
||||
for srv in $ntpsrv ; do
|
||||
echo "server $srv iburst"
|
||||
done > /etc/chrony_dhcp.conf.new
|
||||
|
||||
# Compare to currently configured NTP servers.
|
||||
if ! diff -q /etc/chrony_dhcp.conf /etc/chrony_dhcp.conf.new >/dev/null ; then
|
||||
# Update configuration and restart Chrony.
|
||||
mv /etc/chrony_dhcp.conf.new /etc/chrony_dhcp.conf
|
||||
/etc/init.d/S49chrony reload
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
renew|bound)
|
||||
configure_ntp
|
||||
;;
|
||||
esac
|
||||
|
||||
|
|
@ -115,6 +115,12 @@ public:
|
|||
asio::ip::tcp::endpoint addr(asio::ip::address_v6::any(), m_tcp_port);
|
||||
m_acceptor.bind(addr);
|
||||
|
||||
// Clear buffer, then enable DMA stream.
|
||||
if (! m_connection.is_open()) {
|
||||
discard_stale_data();
|
||||
m_dma_stream.set_enabled(true);
|
||||
}
|
||||
|
||||
// Start listening for connections.
|
||||
m_acceptor.listen();
|
||||
|
||||
|
|
@ -133,9 +139,6 @@ public:
|
|||
*/
|
||||
void stop_server()
|
||||
{
|
||||
// Disable DMA stream and clear buffer.
|
||||
m_dma_stream.init();
|
||||
|
||||
// Stop accepting connections.
|
||||
if (m_acceptor.is_open()) {
|
||||
log(LOG_INFO, "Closing TCP server on port %d", m_tcp_port);
|
||||
|
|
@ -149,6 +152,34 @@ public:
|
|||
m_stale_receive = true;
|
||||
m_stale_send = m_send_in_progress;
|
||||
}
|
||||
|
||||
// Disable DMA stream and clear buffer.
|
||||
m_dma_stream.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the current client and discard all pending data.
|
||||
*
|
||||
* If a multi-threaded Asio event loop is running, this method may only
|
||||
* be called through the strand returned by "get_executor()".
|
||||
*/
|
||||
void clear_data()
|
||||
{
|
||||
// Close the current connection.
|
||||
if (m_connection.is_open()) {
|
||||
log(LOG_INFO, "Closing connection to port %d", m_tcp_port);
|
||||
m_connection.close();
|
||||
m_stale_receive = true;
|
||||
m_stale_send = m_send_in_progress;
|
||||
}
|
||||
|
||||
// Disable DMA stream and clear buffer.
|
||||
discard_stale_data();
|
||||
|
||||
// Re-enable DMA stream.
|
||||
if (m_acceptor.is_open()) {
|
||||
m_dma_stream.set_enabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -272,10 +303,6 @@ private:
|
|||
[this](auto ec, size_t n){ handle_receive(ec, n); });
|
||||
}
|
||||
|
||||
// Clear buffer, then enable DMA stream.
|
||||
discard_stale_data();
|
||||
m_dma_stream.set_enabled(true);
|
||||
|
||||
// Prepare to send data.
|
||||
transmit_data(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -732,7 +732,6 @@ public:
|
|||
// Set up DMA buffer segment.
|
||||
m_device.write_reg(m_reg.addr_start, m_buf_start);
|
||||
m_device.write_reg(m_reg.addr_end, m_buf_end);
|
||||
m_device.write_reg(m_reg.addr_limit, m_buf_end - POINTER_MARGIN);
|
||||
|
||||
// Initialize DMA stream and reset write pointer.
|
||||
init();
|
||||
|
|
@ -791,6 +790,9 @@ public:
|
|||
|
||||
// Reset read pointer.
|
||||
m_read_pointer = m_buf_start;
|
||||
|
||||
// Reset write limit pointer.
|
||||
m_device.write_reg(m_reg.addr_limit, m_buf_end - POINTER_MARGIN);
|
||||
}
|
||||
|
||||
/** Return the number of bytes available in the DMA buffer. */
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
|
|
@ -481,6 +482,8 @@ public:
|
|||
, m_model_name(model_name)
|
||||
, m_serial_number(serial_number)
|
||||
, m_control_server(nullptr)
|
||||
, m_acquisition_server(nullptr)
|
||||
, m_timetagger_server(nullptr)
|
||||
, m_shutting_down(false)
|
||||
, m_exit_status(EXIT_ERROR)
|
||||
{ }
|
||||
|
|
@ -495,10 +498,13 @@ public:
|
|||
m_control_server = &control_server;
|
||||
}
|
||||
|
||||
/** Register a data server with the command handler. */
|
||||
void add_data_server(DataServer& data_server)
|
||||
/** Register the data servers with the command handler. */
|
||||
void set_data_servers(DataServer& acquisition_server,
|
||||
DataServer& timetagger_server)
|
||||
{
|
||||
m_data_servers.push_back(&data_server);
|
||||
m_acquisition_server = &acquisition_server;
|
||||
m_timetagger_server = &timetagger_server;
|
||||
m_data_servers = {m_acquisition_server, m_timetagger_server};
|
||||
}
|
||||
|
||||
/** Return the exit status of the command handler. */
|
||||
|
|
@ -551,17 +557,20 @@ public:
|
|||
* If a multi-threaded Asio event loop is running, this method may only
|
||||
* be called through the strand returned by "get_executor()".
|
||||
*
|
||||
* Returns:
|
||||
* Response without line terminator,
|
||||
* or an empty string if no response must be sent.
|
||||
* The specified response function will be called exactly once to pass
|
||||
* the command response (without line terminator). An empty string is
|
||||
* passed if no response must be sent.
|
||||
*
|
||||
* The call to the response function may be deferred.
|
||||
*/
|
||||
std::string handle_command(const std::string& command)
|
||||
void handle_command(const std::string& command,
|
||||
std::function<void(std::string)> respond)
|
||||
{
|
||||
if (m_shutting_down) {
|
||||
// The server is shutting down.
|
||||
// Ignore new commands and return an empty string to indicate
|
||||
// that no response must be sent.
|
||||
return std::string();
|
||||
// Ignore new commands and send no response.
|
||||
respond(std::string());
|
||||
return;
|
||||
}
|
||||
|
||||
// Split words.
|
||||
|
|
@ -569,7 +578,8 @@ public:
|
|||
|
||||
// Ignore empty command line without response.
|
||||
if (tokens.empty()) {
|
||||
return std::string();
|
||||
respond(std::string());
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert command to lower case.
|
||||
|
|
@ -584,7 +594,8 @@ public:
|
|||
&& action[7] == ':') {
|
||||
char channel_digit = action[6];
|
||||
if (channel_digit < '1' || channel_digit > '4') {
|
||||
return err_unknown_command();
|
||||
respond(err_unknown_command());
|
||||
return;
|
||||
}
|
||||
env.channel = channel_digit - '1';
|
||||
action[6] = 'N'; // mark channel index
|
||||
|
|
@ -622,10 +633,13 @@ public:
|
|||
const auto it = command_table_no_args.find(action);
|
||||
if (it != command_table_no_args.end()) {
|
||||
if (tokens.size() != 1) {
|
||||
return err_unexpected_argument();
|
||||
respond(err_unexpected_argument());
|
||||
return;
|
||||
}
|
||||
auto func = it->second;
|
||||
return (this->*func)(env);
|
||||
std::string resp = (this->*func)(env);
|
||||
respond(resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -634,37 +648,57 @@ public:
|
|||
const auto it = command_table_one_arg.find(action);
|
||||
if (it != command_table_one_arg.end()) {
|
||||
if (tokens.size() < 2) {
|
||||
return err_missing_argument();
|
||||
respond(err_missing_argument());
|
||||
return;
|
||||
}
|
||||
if (tokens.size() > 2) {
|
||||
return err_unexpected_argument();
|
||||
respond(err_unexpected_argument());
|
||||
return;
|
||||
}
|
||||
auto func = it->second;
|
||||
return (this->*func)(env, tokens[1]);
|
||||
std::string resp = (this->*func)(env, tokens[1]);
|
||||
respond(resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle command IPCFG.
|
||||
if (action == "ipcfg" || action == "ipcfg:saved") {
|
||||
if (tokens.size() < 2) {
|
||||
return err_missing_argument();
|
||||
respond(err_missing_argument());
|
||||
return;
|
||||
}
|
||||
if (tokens.size() > 5) {
|
||||
return err_unexpected_argument();
|
||||
respond(err_unexpected_argument());
|
||||
return;
|
||||
}
|
||||
tokens.erase(tokens.begin());
|
||||
return cmd_ipcfg(env, tokens);
|
||||
std::string resp = cmd_ipcfg(env, tokens);
|
||||
respond(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
return err_unknown_command();
|
||||
// Handle command AIN:CLEAR.
|
||||
if (action == "ain:clear") {
|
||||
cmd_ain_clear(respond);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle command TT:CLEAR.
|
||||
if (action == "tt:clear") {
|
||||
cmd_tt_clear(respond);
|
||||
return;
|
||||
}
|
||||
|
||||
respond(err_unknown_command());
|
||||
}
|
||||
|
||||
private:
|
||||
/** Asynchronously stop control and/or data servers. */
|
||||
/** Asynchronously stop control and data servers. */
|
||||
void stop_server(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 data servers. */
|
||||
void start_server();
|
||||
void start_data_servers(unsigned int idx);
|
||||
|
||||
|
|
@ -743,6 +777,38 @@ private:
|
|||
return std::to_string(timestamp);
|
||||
}
|
||||
|
||||
/** Handle command REALTIME? */
|
||||
std::string qry_realtime(CommandEnvironment env)
|
||||
{
|
||||
struct timespec tp1, tp2, tp3;
|
||||
uint64_t ts1, ts2, ts3, ts4;
|
||||
|
||||
// Read approximately simultaneous real time and timestamp.
|
||||
ts1 = m_device.get_timestamp();
|
||||
clock_gettime(CLOCK_REALTIME, &tp1);
|
||||
ts2 = m_device.get_timestamp();
|
||||
clock_gettime(CLOCK_REALTIME, &tp2);
|
||||
ts3 = m_device.get_timestamp();
|
||||
clock_gettime(CLOCK_REALTIME, &tp3);
|
||||
ts4 = m_device.get_timestamp();
|
||||
|
||||
// Choose shortest interval out of three.
|
||||
const uint64_t timestamp_mask = 0xffffffffffff;
|
||||
uint64_t d1 = (ts2 - ts1) & timestamp_mask;
|
||||
uint64_t d2 = (ts3 - ts2) & timestamp_mask;
|
||||
uint64_t d3 = (ts4 - ts3) & timestamp_mask;
|
||||
if (d3 < d1 && d3 < d2) {
|
||||
ts1 = ts3;
|
||||
tp1 = tp3;
|
||||
} else if (d2 < d1) {
|
||||
ts1 = ts2;
|
||||
tp1 = tp2;
|
||||
}
|
||||
|
||||
return str_format("%lld.%09ld %llu",
|
||||
(long long)tp1.tv_sec, (long)tp1.tv_nsec, ts1);
|
||||
}
|
||||
|
||||
/** Handle command AIN:CHANNELS:COUNT? */
|
||||
std::string qry_channels_count(CommandEnvironment env)
|
||||
{
|
||||
|
|
@ -1365,12 +1431,33 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
/** Handle command AIN:CLEAR */
|
||||
void cmd_ain_clear(std::function<void(std::string)> respond)
|
||||
{
|
||||
asio::post(m_acquisition_server->get_executor(),
|
||||
[this,respond]() {
|
||||
m_acquisition_server->clear_data();
|
||||
respond("OK");
|
||||
});
|
||||
}
|
||||
|
||||
/** Handle command TT:CLEAR */
|
||||
void cmd_tt_clear(std::function<void(std::string)> respond)
|
||||
{
|
||||
asio::post(m_timetagger_server->get_executor(),
|
||||
[this,respond]() {
|
||||
m_timetagger_server->clear_data();
|
||||
respond("OK");
|
||||
});
|
||||
}
|
||||
|
||||
static const inline std::map<
|
||||
std::string,
|
||||
std::string (CommandHandler::*)(CommandEnvironment)>
|
||||
command_table_no_args = {
|
||||
{ "*idn?", &CommandHandler::qry_idn },
|
||||
{ "timestamp?", &CommandHandler::qry_timestamp },
|
||||
{ "realtime?", &CommandHandler::qry_realtime },
|
||||
{ "ain:channels:count?", &CommandHandler::qry_channels_count },
|
||||
{ "ain:channels:active?", &CommandHandler::qry_channels_active },
|
||||
{ "ain:chN:range?", &CommandHandler::qry_channel_range },
|
||||
|
|
@ -1436,6 +1523,8 @@ private:
|
|||
std::string m_model_name;
|
||||
std::string m_serial_number;
|
||||
ControlServer* m_control_server;
|
||||
DataServer* m_acquisition_server;
|
||||
DataServer* m_timetagger_server;
|
||||
std::vector<DataServer*> m_data_servers;
|
||||
Calibration m_calibration;
|
||||
bool m_shutting_down;
|
||||
|
|
@ -1715,17 +1804,22 @@ private:
|
|||
m_command_handler.get_executor(),
|
||||
[this,conn,command]() {
|
||||
// This code runs in the command handler strand.
|
||||
// Tell the command handler to run the command.
|
||||
auto response = m_command_handler.handle_command(command);
|
||||
|
||||
// Post the response back to our own strand.
|
||||
asio::post(
|
||||
m_strand,
|
||||
[this,conn,response]() {
|
||||
// This code runs in our own strand.
|
||||
// Handle the response.
|
||||
process_response(conn, response);
|
||||
});
|
||||
// Define helper function which will be called by
|
||||
// the command handler to pass a response.
|
||||
auto respond = [this,conn](std::string response) {
|
||||
// Post the response back to our own strand.
|
||||
asio::post(
|
||||
m_strand,
|
||||
[this,conn,response]() {
|
||||
// This code runs in our own strand.
|
||||
// Handle the response.
|
||||
process_response(conn, response);
|
||||
});
|
||||
};
|
||||
|
||||
// Tell the command handler to run the command.
|
||||
m_command_handler.handle_command(command, respond);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1878,8 +1972,7 @@ int run_remote_control_server(
|
|||
ControlServer control_server(io, command_handler, 5025);
|
||||
|
||||
command_handler.set_control_server(control_server);
|
||||
command_handler.add_data_server(acq_server);
|
||||
command_handler.add_data_server(timetagger_server);
|
||||
command_handler.set_data_servers(acq_server, timetagger_server);
|
||||
|
||||
// Disable DMA on exit from this function.
|
||||
struct ScopeGuard {
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
#define PUZZLEFW_SW_MAJOR 1
|
||||
#define PUZZLEFW_SW_MINOR 0
|
||||
#define PUZZLEFW_SW_MINOR 2
|
||||
|
|
|
|||
Loading…
Reference in New Issue