4.4 Run a 32-channel temperature simulator
Simulate a device that streams temperatures without waiting for commands. You will use this stream to test the 32-channel database in the next lesson.
Prerequisites
Install socat, bc, and procps (pgrep) using the
package instructions.
Use one TCP client per emulator instance. Multiple clients share the same
PTY and do not receive independent copies of every sample.
Create the emulator
In your application’s simulator/ directory, save this file as
tc32_emulator.bash:
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0-or-later
# Stream 32 temperature channels through a TCP-to-PTY bridge.
set -euo pipefail
PORT=9399
SOCAT_PID=""
WORK_DIR="$(mktemp -d)"
SERIAL_DEV="$WORK_DIR/serial"
declare -a temps=()
while [[ $# -gt 0 ]]; do
case "$1" in
--port)
[[ $# -ge 2 ]] || { printf 'Usage: %s [--port PORT]\n' "$0" >&2; exit 2; }
PORT="$2"
shift 2
;;
*)
printf 'Usage: %s [--port PORT]\n' "$0" >&2
exit 2
;;
esac
done
[[ "$PORT" =~ ^[0-9]{1,5}$ ]] || { printf 'Invalid port\n' >&2; exit 2; }
PORT="$((10#$PORT))"
(( PORT >= 1 && PORT <= 65535 )) || { printf 'Invalid port\n' >&2; exit 2; }
for cmd in socat bc mktemp pgrep; do
command -v "$cmd" >/dev/null || { printf '%s is required\n' "$cmd" >&2; exit 1; }
done
function cleanup {
local rc=$?
local child=""
if [[ -n "$SOCAT_PID" ]]; then
while IFS= read -r child; do
kill "$child" 2>/dev/null || true
done < <(pgrep -P "$SOCAT_PID" || true)
kill "$SOCAT_PID" 2>/dev/null || true
wait "$SOCAT_PID" 2>/dev/null || true
fi
printf 'Diagnostic directory: %s\n' "$WORK_DIR" >&2
return "$rc"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
socat -d -d "PTY,raw,echo=0,link=$SERIAL_DEV" "TCP-LISTEN:$PORT,bind=127.0.0.1,reuseaddr,fork" \
2>"$WORK_DIR/socat.log" &
SOCAT_PID=$!
for ((attempt=0; attempt<100; attempt++)); do
[[ -c "$SERIAL_DEV" ]] && break
kill -0 "$SOCAT_PID" 2>/dev/null || { cat "$WORK_DIR/socat.log" >&2; exit 1; }
sleep 0.05
done
[[ -c "$SERIAL_DEV" ]] || { printf 'PTY creation timed out\n' >&2; exit 1; }
for ((channel=0; channel<32; channel++)); do
temps[channel]="$(printf 'scale=4; 10 + %d*80/32767\n' "$RANDOM" | bc)"
done
function generate_temp {
local idx="$1"
local change=""
local next=""
change="$(printf 'scale=4; (%d/32767 - 0.5)*1.5\n' "$RANDOM" | bc)"
next="$(printf 't=%s+(%s); if(t<10)t=10; if(t>90)t=90; t\n' "${temps[idx]}" "$change" | bc)"
temps[idx]="$next"
}
printf 'Emulator running on port %s\n' "$PORT"
printf 'Serial emulated at: %s\n' "$SERIAL_DEV"
while true; do
for ((channel=0; channel<32; channel++)); do
generate_temp "$channel"
printf 'CH%02d: %s\n' "$((channel+1))" "${temps[channel]}" > "$SERIAL_DEV"
done
done
Make it executable:
chmod +x tc32_emulator.bash
The script maintains 32 temperatures between 10 and 90 degrees Celsius.
Each update changes a channel by at most 0.75 degrees and limits it to that
range. The stream format is CH01: <temperature> through CH32: <temperature>,
one channel per line. The update interval depends on the host.
Run and inspect the stream
-
In terminal 1, start the emulator on the default port:
./tc32_emulator.bashWait for
Emulator running on port 9399. Leave it running. -
In terminal 2, connect a client:
socat - TCP:127.0.0.1:9399You must see repeated channel numbers
CH01throughCH32, each followed by a number between 10 and 90. -
Stop the client with Ctrl+C before connecting the IOC from the next lesson. Stop the emulator with Ctrl+C when the exercise is finished.
-
To select another port, start another instance:
./tc32_emulator.bash --port 10001Connect its client to the same port:
socat - TCP:127.0.0.1:10001
Run several independent devices
In three terminals, start one emulator per port:
./tc32_emulator.bash --port 9399
./tc32_emulator.bash --port 9400
./tc32_emulator.bash --port 9401
Each process has its own temperatures and PTY. Stop each process with Ctrl+C. If GNU Parallel is installed, the equivalent launch is:
parallel ./tc32_emulator.bash --port ::: 9399 9400 9401
Verification
Check that the chosen ports are listening while the emulators run:
ss -ltn 'sport = :9399 or sport = :9400 or sport = :9401'
After stopping them, repeat the command; their listeners must be absent.
The emulator prints its retained diagnostic directory on exit. Each run
creates another one; remove a printed directory when you no longer need its
socat.log.