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`.
|
Set the baud rate to 115200 bps, character format to `8N1`.
|
||||||
|
|
||||||
Press Enter to get a login prompt on the console.
|
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
|
## 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.
|
It is possible to run an SSH server on the Red Pitaya.
|
||||||
This can be used to remotely log in on the Linux system.
|
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.
|
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.
|
For security reasons, the SSH server is disabled by default.
|
||||||
To enable the SSH server, login on the USB console as described above.
|
If you want to login via SSH, it must be enabled explicitly.
|
||||||
Then run the following command: `puzzle-sshcfg enable` .
|
Use the following steps to enable the SSH server:
|
||||||
Finally, run `reboot` to reboot the Red Pitaya.
|
|
||||||
|
- 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.
|
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
|
## Data stream protocol
|
||||||
|
|
@ -330,6 +374,7 @@ In the response string, such data elements are separated by space characters.
|
||||||
| `*IDN?` | Instrument identification. |
|
| `*IDN?` | Instrument identification. |
|
||||||
| `RESET` | Restore default settings. |
|
| `RESET` | Restore default settings. |
|
||||||
| `TIMESTAMP?` | Timestamp counter. |
|
| `TIMESTAMP?` | Timestamp counter. |
|
||||||
|
| `REALTIME?` | System time and timestamp counter. |
|
||||||
| `AIN:CHANNELS:COUNT?` | Number of input channels. |
|
| `AIN:CHANNELS:COUNT?` | Number of input channels. |
|
||||||
| `AIN:CHANNELS:ACTIVE` | Number of active input channels. |
|
| `AIN:CHANNELS:ACTIVE` | Number of active input channels. |
|
||||||
| `AIN:CHn:RANGE` | Analog input range. |
|
| `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:CHANNEL` | External trigger channel. |
|
||||||
| `AIN:TRIGGER:EXT:EDGE` | External trigger edge. |
|
| `AIN:TRIGGER:EXT:EDGE` | External trigger edge. |
|
||||||
| `AIN:ACQUIRE:ENABLE` | Enable analog acquisition. |
|
| `AIN:ACQUIRE:ENABLE` | Enable analog acquisition. |
|
||||||
|
| `AIN:CLEAR` | Clear buffer and drop data connection. |
|
||||||
| `TT:SAMPLE?` | Digital input state. |
|
| `TT:SAMPLE?` | Digital input state. |
|
||||||
| `TT:EVENT:MASK` | Timetagger event mask. |
|
| `TT:EVENT:MASK` | Timetagger event mask. |
|
||||||
| `TT:MARK` | Emit timetagger marker. |
|
| `TT:MARK` | Emit timetagger marker. |
|
||||||
|
| `TT:CLEAR` | Clear buffer and drop data connection. |
|
||||||
| `TEMP:FPGA?` | FPGA temperature. |
|
| `TEMP:FPGA?` | FPGA temperature. |
|
||||||
| `IPCFG[:SAVED]` | IP address configuration. |
|
| `IPCFG[:SAVED]` | IP address configuration. |
|
||||||
| `HALT` | Shut down system. |
|
| `HALT` | Shut down system. |
|
||||||
|
|
@ -389,6 +436,23 @@ Any ongoing analog acquisition is stopped.
|
||||||
Query: `TIMESTAMP?` <br>
|
Query: `TIMESTAMP?` <br>
|
||||||
Response: decimal integer, representing the current timestamp in units of 8 ns.
|
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?`
|
### `AIN:CHANNELS:COUNT?`
|
||||||
|
|
||||||
Query: `AIN:CHANNELS:COUNT?` <br>
|
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>
|
Query: `AIN:ACQUIRE:ENABLE?` <br>
|
||||||
Response: either `0` or `1`.
|
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?`
|
### `TT:SAMPLE?`
|
||||||
|
|
||||||
Query: `TT:SAMPLE?` <br>
|
Query: `TT:SAMPLE?` <br>
|
||||||
|
|
@ -662,6 +737,17 @@ Command: `TT:MARK`
|
||||||
|
|
||||||
This command emits a marker record in the timetagger event stream.
|
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?`
|
### `TEMP:FPGA?`
|
||||||
|
|
||||||
Query: `TEMP:FPGA?` <br>
|
Query: `TEMP:FPGA?` <br>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
include /etc/chrony_dhcp.conf
|
sourcedir /etc/chrony/sources.d
|
||||||
pool pool.ntp.org iburst
|
|
||||||
makestep 0.1 3
|
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
|
. /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 from configuration partition.
|
||||||
copy_ssh_host_key() {
|
copy_ssh_host_key() {
|
||||||
|
|
||||||
|
|
@ -25,12 +40,24 @@ copy_ssh_host_key() {
|
||||||
chmod 0600 /etc/dropbear/dropbear_ed25519_host_key || true
|
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
|
case "$1" in
|
||||||
start)
|
start)
|
||||||
echo "Reading configuration files from SD card ..."
|
echo "Reading configuration files from SD card ..."
|
||||||
lock_config || exit 1
|
lock_config || exit 1
|
||||||
read_config || exit 1
|
read_config || exit 1
|
||||||
|
copy_passwd
|
||||||
copy_ssh_host_key
|
copy_ssh_host_key
|
||||||
|
copy_ntp_server
|
||||||
;;
|
;;
|
||||||
stop|restart|reload)
|
stop|restart|reload)
|
||||||
true
|
true
|
||||||
|
|
@ -276,7 +276,7 @@ case "$1" in
|
||||||
*)
|
*)
|
||||||
script="${0##*/}"
|
script="${0##*/}"
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $script {init|show|config|save|restart}
|
Usage: $script {init|show|config|save}
|
||||||
|
|
||||||
Manage IP address configuration.
|
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
|
echo "start_ssh=1" > ${CONFIG_DIR}/start_ssh.conf.new
|
||||||
sync_config start_ssh.conf || exit 1
|
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.
|
# Disable starting SSH server during boot.
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ read_config() {
|
||||||
|
|
||||||
# Copy config files to RAM filesystem.
|
# Copy config files to RAM filesystem.
|
||||||
cp -a ${CONFIG_MOUNTPOINT}/*.conf $CONFIG_DIR || true
|
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
|
cp -a ${CONFIG_MOUNTPOINT}/dropbear_* $CONFIG_DIR || true
|
||||||
|
|
||||||
umount $CONFIG_MOUNTPOINT
|
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);
|
asio::ip::tcp::endpoint addr(asio::ip::address_v6::any(), m_tcp_port);
|
||||||
m_acceptor.bind(addr);
|
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.
|
// Start listening for connections.
|
||||||
m_acceptor.listen();
|
m_acceptor.listen();
|
||||||
|
|
||||||
|
|
@ -133,9 +139,6 @@ public:
|
||||||
*/
|
*/
|
||||||
void stop_server()
|
void stop_server()
|
||||||
{
|
{
|
||||||
// Disable DMA stream and clear buffer.
|
|
||||||
m_dma_stream.init();
|
|
||||||
|
|
||||||
// Stop accepting connections.
|
// Stop accepting connections.
|
||||||
if (m_acceptor.is_open()) {
|
if (m_acceptor.is_open()) {
|
||||||
log(LOG_INFO, "Closing TCP server on port %d", m_tcp_port);
|
log(LOG_INFO, "Closing TCP server on port %d", m_tcp_port);
|
||||||
|
|
@ -149,6 +152,34 @@ public:
|
||||||
m_stale_receive = true;
|
m_stale_receive = true;
|
||||||
m_stale_send = m_send_in_progress;
|
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); });
|
[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.
|
// Prepare to send data.
|
||||||
transmit_data(false);
|
transmit_data(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -732,7 +732,6 @@ public:
|
||||||
// Set up DMA buffer segment.
|
// Set up DMA buffer segment.
|
||||||
m_device.write_reg(m_reg.addr_start, m_buf_start);
|
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_end, m_buf_end);
|
||||||
m_device.write_reg(m_reg.addr_limit, m_buf_end - POINTER_MARGIN);
|
|
||||||
|
|
||||||
// Initialize DMA stream and reset write pointer.
|
// Initialize DMA stream and reset write pointer.
|
||||||
init();
|
init();
|
||||||
|
|
@ -791,6 +790,9 @@ public:
|
||||||
|
|
||||||
// Reset read pointer.
|
// Reset read pointer.
|
||||||
m_read_pointer = m_buf_start;
|
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. */
|
/** Return the number of bytes available in the DMA buffer. */
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
@ -481,6 +482,8 @@ public:
|
||||||
, m_model_name(model_name)
|
, m_model_name(model_name)
|
||||||
, m_serial_number(serial_number)
|
, m_serial_number(serial_number)
|
||||||
, m_control_server(nullptr)
|
, m_control_server(nullptr)
|
||||||
|
, m_acquisition_server(nullptr)
|
||||||
|
, m_timetagger_server(nullptr)
|
||||||
, m_shutting_down(false)
|
, m_shutting_down(false)
|
||||||
, m_exit_status(EXIT_ERROR)
|
, m_exit_status(EXIT_ERROR)
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -495,10 +498,13 @@ public:
|
||||||
m_control_server = &control_server;
|
m_control_server = &control_server;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Register a data server with the command handler. */
|
/** Register the data servers with the command handler. */
|
||||||
void add_data_server(DataServer& data_server)
|
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. */
|
/** 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
|
* If a multi-threaded Asio event loop is running, this method may only
|
||||||
* be called through the strand returned by "get_executor()".
|
* be called through the strand returned by "get_executor()".
|
||||||
*
|
*
|
||||||
* Returns:
|
* The specified response function will be called exactly once to pass
|
||||||
* Response without line terminator,
|
* the command response (without line terminator). An empty string is
|
||||||
* or an empty string if no response must be sent.
|
* 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) {
|
if (m_shutting_down) {
|
||||||
// The server is shutting down.
|
// The server is shutting down.
|
||||||
// Ignore new commands and return an empty string to indicate
|
// Ignore new commands and send no response.
|
||||||
// that no response must be sent.
|
respond(std::string());
|
||||||
return std::string();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split words.
|
// Split words.
|
||||||
|
|
@ -569,7 +578,8 @@ public:
|
||||||
|
|
||||||
// Ignore empty command line without response.
|
// Ignore empty command line without response.
|
||||||
if (tokens.empty()) {
|
if (tokens.empty()) {
|
||||||
return std::string();
|
respond(std::string());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert command to lower case.
|
// Convert command to lower case.
|
||||||
|
|
@ -584,7 +594,8 @@ public:
|
||||||
&& action[7] == ':') {
|
&& action[7] == ':') {
|
||||||
char channel_digit = action[6];
|
char channel_digit = action[6];
|
||||||
if (channel_digit < '1' || channel_digit > '4') {
|
if (channel_digit < '1' || channel_digit > '4') {
|
||||||
return err_unknown_command();
|
respond(err_unknown_command());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
env.channel = channel_digit - '1';
|
env.channel = channel_digit - '1';
|
||||||
action[6] = 'N'; // mark channel index
|
action[6] = 'N'; // mark channel index
|
||||||
|
|
@ -622,10 +633,13 @@ public:
|
||||||
const auto it = command_table_no_args.find(action);
|
const auto it = command_table_no_args.find(action);
|
||||||
if (it != command_table_no_args.end()) {
|
if (it != command_table_no_args.end()) {
|
||||||
if (tokens.size() != 1) {
|
if (tokens.size() != 1) {
|
||||||
return err_unexpected_argument();
|
respond(err_unexpected_argument());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
auto func = it->second;
|
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);
|
const auto it = command_table_one_arg.find(action);
|
||||||
if (it != command_table_one_arg.end()) {
|
if (it != command_table_one_arg.end()) {
|
||||||
if (tokens.size() < 2) {
|
if (tokens.size() < 2) {
|
||||||
return err_missing_argument();
|
respond(err_missing_argument());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (tokens.size() > 2) {
|
if (tokens.size() > 2) {
|
||||||
return err_unexpected_argument();
|
respond(err_unexpected_argument());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
auto func = it->second;
|
auto func = it->second;
|
||||||
return (this->*func)(env, tokens[1]);
|
std::string resp = (this->*func)(env, tokens[1]);
|
||||||
|
respond(resp);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle command IPCFG.
|
// Handle command IPCFG.
|
||||||
if (action == "ipcfg" || action == "ipcfg:saved") {
|
if (action == "ipcfg" || action == "ipcfg:saved") {
|
||||||
if (tokens.size() < 2) {
|
if (tokens.size() < 2) {
|
||||||
return err_missing_argument();
|
respond(err_missing_argument());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (tokens.size() > 5) {
|
if (tokens.size() > 5) {
|
||||||
return err_unexpected_argument();
|
respond(err_unexpected_argument());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
tokens.erase(tokens.begin());
|
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:
|
private:
|
||||||
/** Asynchronously stop control and/or data servers. */
|
/** Asynchronously stop control and data servers. */
|
||||||
void stop_server(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 data servers. */
|
||||||
void start_server();
|
void start_server();
|
||||||
void start_data_servers(unsigned int idx);
|
void start_data_servers(unsigned int idx);
|
||||||
|
|
||||||
|
|
@ -743,6 +777,38 @@ private:
|
||||||
return std::to_string(timestamp);
|
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? */
|
/** Handle command AIN:CHANNELS:COUNT? */
|
||||||
std::string qry_channels_count(CommandEnvironment env)
|
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<
|
static const inline std::map<
|
||||||
std::string,
|
std::string,
|
||||||
std::string (CommandHandler::*)(CommandEnvironment)>
|
std::string (CommandHandler::*)(CommandEnvironment)>
|
||||||
command_table_no_args = {
|
command_table_no_args = {
|
||||||
{ "*idn?", &CommandHandler::qry_idn },
|
{ "*idn?", &CommandHandler::qry_idn },
|
||||||
{ "timestamp?", &CommandHandler::qry_timestamp },
|
{ "timestamp?", &CommandHandler::qry_timestamp },
|
||||||
|
{ "realtime?", &CommandHandler::qry_realtime },
|
||||||
{ "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 },
|
||||||
|
|
@ -1436,6 +1523,8 @@ private:
|
||||||
std::string m_model_name;
|
std::string m_model_name;
|
||||||
std::string m_serial_number;
|
std::string m_serial_number;
|
||||||
ControlServer* m_control_server;
|
ControlServer* m_control_server;
|
||||||
|
DataServer* m_acquisition_server;
|
||||||
|
DataServer* m_timetagger_server;
|
||||||
std::vector<DataServer*> m_data_servers;
|
std::vector<DataServer*> m_data_servers;
|
||||||
Calibration m_calibration;
|
Calibration m_calibration;
|
||||||
bool m_shutting_down;
|
bool m_shutting_down;
|
||||||
|
|
@ -1715,17 +1804,22 @@ private:
|
||||||
m_command_handler.get_executor(),
|
m_command_handler.get_executor(),
|
||||||
[this,conn,command]() {
|
[this,conn,command]() {
|
||||||
// This code runs in the command handler strand.
|
// 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.
|
// Define helper function which will be called by
|
||||||
asio::post(
|
// the command handler to pass a response.
|
||||||
m_strand,
|
auto respond = [this,conn](std::string response) {
|
||||||
[this,conn,response]() {
|
// Post the response back to our own strand.
|
||||||
// This code runs in our own strand.
|
asio::post(
|
||||||
// Handle the response.
|
m_strand,
|
||||||
process_response(conn, response);
|
[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);
|
ControlServer control_server(io, command_handler, 5025);
|
||||||
|
|
||||||
command_handler.set_control_server(control_server);
|
command_handler.set_control_server(control_server);
|
||||||
command_handler.add_data_server(acq_server);
|
command_handler.set_data_servers(acq_server, timetagger_server);
|
||||||
command_handler.add_data_server(timetagger_server);
|
|
||||||
|
|
||||||
// Disable DMA on exit from this function.
|
// Disable DMA on exit from this function.
|
||||||
struct ScopeGuard {
|
struct ScopeGuard {
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
#define PUZZLEFW_SW_MAJOR 1
|
#define PUZZLEFW_SW_MAJOR 1
|
||||||
#define PUZZLEFW_SW_MINOR 0
|
#define PUZZLEFW_SW_MINOR 2
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue