Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. In terminal 1, start the emulator on the default port:

    ./tc32_emulator.bash
    

    Wait for Emulator running on port 9399. Leave it running.

  2. In terminal 2, connect a client:

    socat - TCP:127.0.0.1:9399
    

    You must see repeated channel numbers CH01 through CH32, each followed by a number between 10 and 90.

  3. 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.

  4. To select another port, start another instance:

    ./tc32_emulator.bash --port 10001
    

    Connect 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.