fixed and improved ui
This commit is contained in:
+206
-67
@@ -4,28 +4,40 @@
|
||||
# A producer only: no StreamHub, no clients. Point whatever consumer you like at
|
||||
# the UDP port (StreamHub, the Go hub, or Test/E2E tooling).
|
||||
#
|
||||
# Each channel is a 1000-element float32 array published every 1 ms by a 1 kHz
|
||||
# real-time thread — 1000 samples x 1000 Hz = 1 Msps per channel. A parallel
|
||||
# uint64 time array gives every sample its own timestamp (TimeMode=FullArray),
|
||||
# so consumers reconstruct the waveform at full rate rather than one point per
|
||||
# cycle.
|
||||
# Each channel is an N-element float32 array published every cycle by a
|
||||
# real-time thread — 10000 samples x 100 Hz = 1 Msps per channel by default. A
|
||||
# parallel uint64 time array gives every sample its own timestamp
|
||||
# (TimeMode=FullArray), so consumers reconstruct the waveform at full rate
|
||||
# rather than one point per cycle. When a cycle exceeds the UDP datagram limit
|
||||
# it is split across several datagrams (fragmented) and reassembled by the
|
||||
# receiver.
|
||||
#
|
||||
# One extra channel, HV, emulates a charged-capacitor pulse discharge on a
|
||||
# high-voltage bus: an emulated EPICS "start" setpoint (SlowControlGAM) raises
|
||||
# a rising edge, on which PulseGeneratorGAM ramps the output to -40 kV in 1 ms,
|
||||
# holds it flat for the emulated EPICS "duration" setpoint, then discharges
|
||||
# back to 0 over 100 ms. Always-on gaussian noise (3-sigma ~ +-1000 V) and
|
||||
# random +-5000 V EMI spikes (on and off phase) ride on top.
|
||||
#
|
||||
# Usage:
|
||||
# ./run_udp_producer.sh [OPTIONS]
|
||||
#
|
||||
# Options:
|
||||
# -n <CHANNELS> Number of 1 Msps channels (default 4, max 13 — see below)
|
||||
# -n <CHANNELS> Number of 1 Msps sine channels (default 4, max 12 — see below)
|
||||
# -p <PORT> UDP port to stream on (default 44501)
|
||||
# -t <MS> HV trigger period (time between pulses, default 5000)
|
||||
# -d <MS> HV plateau duration (EPICS "duration" setpoint, default 500)
|
||||
# -b <TARGET> Build target (default: $TARGET or x86-linux)
|
||||
# -s Skip the component rebuild
|
||||
# -k Keep the generated .cfg on exit and print its path
|
||||
# -h Show this help
|
||||
#
|
||||
# Why 13 channels max: one cycle is TimeArray(8000 B) + CHANNELS x 4000 B, and
|
||||
# it is sent as a single datagram to keep the receiver's fragment-reassembly
|
||||
# pool from evicting in-flight cycles (which shows up as periodic gaps in the
|
||||
# trace). A UDP datagram tops out at 65507 B, so 8000 + 4000*13 + headroom fits
|
||||
# and 14 does not.
|
||||
# Channels are capped at 12 as a practical bound on the bytes the background
|
||||
# thread copies and sends each cycle (the uint64 time array plus (CHANNELS+1)
|
||||
# float32 arrays). Cycles larger than one UDP datagram are fragmented into
|
||||
# 60000 B datagrams and reassembled by the receiver by sequence counter, so the
|
||||
# datagram limit no longer caps the channel count — only the per-cycle send
|
||||
# budget does.
|
||||
#
|
||||
# Environment:
|
||||
# MARTe2_DIR must be set (or source env.sh first)
|
||||
@@ -39,63 +51,87 @@ CHANNELS=4
|
||||
PORT=44501
|
||||
SKIP_BUILD=0
|
||||
KEEP_CFG=0
|
||||
HV_TRIG_PERIOD=5000
|
||||
HV_PLATEAU_MS=500
|
||||
|
||||
MAX_CHANNELS=13
|
||||
MAX_CHANNELS=12
|
||||
|
||||
while getopts "n:p:b:skh" opt; do
|
||||
case "$opt" in
|
||||
n) CHANNELS="$OPTARG" ;;
|
||||
p) PORT="$OPTARG" ;;
|
||||
b) BUILD_TARGET="$OPTARG" ;;
|
||||
s) SKIP_BUILD=1 ;;
|
||||
k) KEEP_CFG=1 ;;
|
||||
h) sed -n '2,33p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
||||
*) echo "Unknown option: -$OPTARG" >&2; exit 1 ;;
|
||||
esac
|
||||
while getopts "n:p:t:d:b:skh" opt; do
|
||||
case "$opt" in
|
||||
n) CHANNELS="$OPTARG" ;;
|
||||
p) PORT="$OPTARG" ;;
|
||||
t) HV_TRIG_PERIOD="$OPTARG" ;;
|
||||
d) HV_PLATEAU_MS="$OPTARG" ;;
|
||||
b) BUILD_TARGET="$OPTARG" ;;
|
||||
s) SKIP_BUILD=1 ;;
|
||||
k) KEEP_CFG=1 ;;
|
||||
h)
|
||||
sed -n '2,43p' "$0" | sed 's/^# \?//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: -$OPTARG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Validate ──────────────────────────────────────────────────────────────────
|
||||
if ! [[ "$CHANNELS" =~ ^[0-9]+$ ]] || (( CHANNELS < 1 || CHANNELS > MAX_CHANNELS )); then
|
||||
echo "ERROR: -n must be 1..${MAX_CHANNELS} (got '${CHANNELS}')." >&2
|
||||
exit 1
|
||||
if ! [[ "$CHANNELS" =~ ^[0-9]+$ ]] || ((CHANNELS < 1 || CHANNELS > MAX_CHANNELS)); then
|
||||
echo "ERROR: -n must be 1..${MAX_CHANNELS} (got '${CHANNELS}')." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${MARTe2_DIR:-}" || -z "${MARTe2_Components_DIR:-}" ]]; then
|
||||
echo "ERROR: MARTe2_DIR / MARTe2_Components_DIR not set." >&2
|
||||
echo " source ${SCRIPT_DIR}/env.sh" >&2
|
||||
exit 1
|
||||
echo "ERROR: MARTe2_DIR / MARTe2_Components_DIR not set." >&2
|
||||
echo " source ${SCRIPT_DIR}/env.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MARTE2_BIN="${MARTe2_DIR}/Build/${BUILD_TARGET}/App/MARTeApp.ex"
|
||||
if [[ ! -x "$MARTE2_BIN" ]]; then
|
||||
echo "ERROR: MARTeApp.ex not found at ${MARTE2_BIN}" >&2
|
||||
exit 1
|
||||
echo "ERROR: MARTeApp.ex not found at ${MARTE2_BIN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Build ─────────────────────────────────────────────────────────────────────
|
||||
if [[ "$SKIP_BUILD" -eq 0 ]]; then
|
||||
echo "==> Building components (TARGET=${BUILD_TARGET})..."
|
||||
make -C "${SCRIPT_DIR}" -f Makefile.gcc TARGET="${BUILD_TARGET}" core 2>&1 | tail -5
|
||||
echo "==> Building components (TARGET=${BUILD_TARGET})..."
|
||||
make -C "${SCRIPT_DIR}" -f Makefile.gcc TARGET="${BUILD_TARGET}" core 2>&1 | tail -5
|
||||
fi
|
||||
|
||||
# ── Generate the config ───────────────────────────────────────────────────────
|
||||
# Distinct amplitude/frequency/phase per channel so traces stay tellable apart
|
||||
# (and so a shared-Y-axis view has a spread of magnitudes to cope with).
|
||||
AMPS=(1.0 2.5 0.5 5.0 1.5 3.0 0.8 4.0 2.0 0.3 6.0 1.2 3.5)
|
||||
FREQS=(1000 2000 5000 500 10000 3000 20000 1500 7000 50000 800 4000 15000)
|
||||
AMPS=(1.0 2.5 0.5 5.0 1.5 3.0 0.8 4.0 2.0 0.3 6.0 1.2 3.5)
|
||||
FREQS=(1000 2000 5000 500 10000 3000 20000 1500 7000 50000 800 4000 15000)
|
||||
PHASES=(0.0 0.7854 1.5708 2.3562 3.1416 3.9270 4.7124 5.4978 0.3927 1.1781 1.9635 2.7489 3.5343)
|
||||
|
||||
ELEMS=1000 # samples per cycle
|
||||
RATE=1000 # cycles per second -> 1 Msps
|
||||
CYCLE_BYTES=$(( 8 * ELEMS + CHANNELS * 4 * ELEMS ))
|
||||
PAYLOAD=$(( CYCLE_BYTES + 2000 )) # headroom for header + descriptors
|
||||
ELEMS=10000 # samples per cycle
|
||||
RATE=100 # cycles per second -> 1 Msps
|
||||
CYCLE_BYTES=$((8 * ELEMS + (CHANNELS + 1) * 4 * ELEMS + 8))
|
||||
# UDP datagrams cap at 65507 B (IPv4) minus the 17 B UDPS header; keep a margin
|
||||
# and use 60000 B of payload per fragment.
|
||||
MAX_UDP_PAYLOAD=60000
|
||||
if ((CYCLE_BYTES + 2000 <= MAX_UDP_PAYLOAD)); then
|
||||
PAYLOAD=$((CYCLE_BYTES + 2000)) # whole cycle in one datagram
|
||||
else
|
||||
PAYLOAD=${MAX_UDP_PAYLOAD} # fragment the cycle across datagrams
|
||||
fi
|
||||
FRAGMENTS=$(((CYCLE_BYTES + MAX_UDP_PAYLOAD - 1) / MAX_UDP_PAYLOAD))
|
||||
|
||||
sine_gams=""; iogam_in=""; iogam_out=""; stream_sigs=""; func_list="TimerGAM"
|
||||
sine_gams=""
|
||||
iogam_in=""
|
||||
iogam_out=""
|
||||
stream_sigs=""
|
||||
func_list="TimerGAM"
|
||||
|
||||
for (( i = 1; i <= CHANNELS; i++ )); do
|
||||
k=$(( i - 1 ))
|
||||
amp="${AMPS[$k]}"; frq="${FREQS[$k]}"; pha="${PHASES[$k]}"
|
||||
for ((i = 1; i <= CHANNELS; i++)); do
|
||||
k=$((i - 1))
|
||||
amp="${AMPS[$k]}"
|
||||
frq="${FREQS[$k]}"
|
||||
pha="${PHASES[$k]}"
|
||||
|
||||
sine_gams+="
|
||||
sine_gams+="
|
||||
+SineGAM${i} = {
|
||||
Class = SineArrayGAM
|
||||
Frequency = ${frq}.0
|
||||
@@ -113,21 +149,21 @@ for (( i = 1; i <= CHANNELS; i++ )); do
|
||||
}
|
||||
}
|
||||
"
|
||||
iogam_in+="
|
||||
iogam_in+="
|
||||
Ch${i} = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
NumberOfDimensions = 1
|
||||
NumberOfElements = ${ELEMS}
|
||||
}"
|
||||
iogam_out+="
|
||||
iogam_out+="
|
||||
Ch${i} = {
|
||||
DataSource = Streamer
|
||||
Type = float32
|
||||
NumberOfDimensions = 1
|
||||
NumberOfElements = ${ELEMS}
|
||||
}"
|
||||
stream_sigs+="
|
||||
stream_sigs+="
|
||||
Ch${i} = {
|
||||
Type = float32
|
||||
Unit = \"V\"
|
||||
@@ -138,13 +174,113 @@ for (( i = 1; i <= CHANNELS; i++ )); do
|
||||
TimeMode = \"FullArray\"
|
||||
TimeSignal = TimeArray
|
||||
}"
|
||||
func_list+=", SineGAM${i}"
|
||||
func_list+=", SineGAM${i}"
|
||||
done
|
||||
func_list+=", TimeArrayGAM1, StreamerGAM"
|
||||
hv_gams="
|
||||
+SlowControlGAM1 = {
|
||||
Class = SlowControlGAM
|
||||
TriggerPeriodMs = ${HV_TRIG_PERIOD}
|
||||
TriggerWidthMs = 10
|
||||
PlateauMs = ${HV_PLATEAU_MS}
|
||||
CycleFrequency = ${RATE}
|
||||
OutputSignals = {
|
||||
HVTrigger = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
}
|
||||
HVPlateauMs = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+PulseGeneratorGAM1 = {
|
||||
Class = PulseGeneratorGAM
|
||||
SamplingRate = 1000000.0
|
||||
RampUpMs = 1.0
|
||||
RampDownMs = 100.0
|
||||
HighLevel = -40000.0
|
||||
NoiseStdDev = 333.33
|
||||
EMIAmplitude = 5000.0
|
||||
EMIProbabilityPerSample = 0.00001
|
||||
EMISpikeSamples = 5
|
||||
AutoTriggerPeriodMs = 0.0
|
||||
InputSignals = {
|
||||
HVTrigger = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
}
|
||||
HVPlateauMs = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
}
|
||||
}
|
||||
OutputSignals = {
|
||||
HV = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
NumberOfDimensions = 1
|
||||
NumberOfElements = ${ELEMS}
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
|
||||
iogam_in+="
|
||||
HV = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
NumberOfDimensions = 1
|
||||
NumberOfElements = ${ELEMS}
|
||||
}
|
||||
HVTrigger = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
}
|
||||
HVPlateauMs = {
|
||||
DataSource = DDB1
|
||||
Type = float32
|
||||
}"
|
||||
iogam_out+="
|
||||
HV = {
|
||||
DataSource = Streamer
|
||||
Type = float32
|
||||
NumberOfDimensions = 1
|
||||
NumberOfElements = ${ELEMS}
|
||||
}
|
||||
HVTrigger = {
|
||||
DataSource = Streamer
|
||||
Type = float32
|
||||
}
|
||||
HVPlateauMs = {
|
||||
DataSource = Streamer
|
||||
Type = float32
|
||||
}"
|
||||
stream_sigs+="
|
||||
HV = {
|
||||
Type = float32
|
||||
Unit = \"V\"
|
||||
NumberOfDimensions = 1
|
||||
NumberOfElements = ${ELEMS}
|
||||
RangeMin = -50000.0
|
||||
RangeMax = 10000.0
|
||||
TimeMode = \"FullArray\"
|
||||
TimeSignal = TimeArray
|
||||
}
|
||||
HVTrigger = {
|
||||
Type = float32
|
||||
Unit = \"1\"
|
||||
}
|
||||
HVPlateauMs = {
|
||||
Type = float32
|
||||
Unit = \"ms\"
|
||||
}"
|
||||
func_list+=", TimeArrayGAM1, SlowControlGAM1, PulseGeneratorGAM1, StreamerGAM"
|
||||
|
||||
CFG="$(mktemp /tmp/udp_producer_XXXXXX.cfg)"
|
||||
|
||||
cat > "$CFG" <<EOF
|
||||
cat >"$CFG" <<EOF
|
||||
/**
|
||||
* udp_producer — auto-generated by run_udp_producer.sh
|
||||
* ${CHANNELS} channel(s), ${ELEMS} elem x ${RATE} Hz = 1 Msps each, port ${PORT}.
|
||||
@@ -171,7 +307,7 @@ cat > "$CFG" <<EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
${sine_gams}
|
||||
${sine_gams}${hv_gams}
|
||||
// Expands the cycle's scalar timestamp into one timestamp per sample, so
|
||||
// consumers place all ${ELEMS} samples instead of collapsing them to a point.
|
||||
+TimeArrayGAM1 = {
|
||||
@@ -239,9 +375,8 @@ ${sine_gams}
|
||||
+Streamer = {
|
||||
Class = UDPStreamer
|
||||
Port = ${PORT}
|
||||
// One cycle is ${CYCLE_BYTES} B; sizing the payload above that sends each
|
||||
// cycle as a single datagram, which keeps the receiver's reassembly pool
|
||||
// from evicting in-flight cycles and gapping the trace.
|
||||
// One cycle is ${CYCLE_BYTES} B; it is sent as ${FRAGMENTS} datagram(s)
|
||||
// of up to ${PAYLOAD} B each (the receiver reassembles by counter).
|
||||
MaxPayloadSize = ${PAYLOAD}
|
||||
PublishingMode = "Strict"
|
||||
Signals = {
|
||||
@@ -292,27 +427,31 @@ ${MARTe2_Components_DIR}/Build/${BUILD_TARGET}/Components/GAMs/IOGAM:\
|
||||
${BUILD_DIR}/Components/DataSources/UDPStreamer:\
|
||||
${BUILD_DIR}/Components/GAMs/SineArrayGAM:\
|
||||
${BUILD_DIR}/Components/GAMs/TimeArrayGAM:\
|
||||
${BUILD_DIR}/Components/GAMs/PulseGeneratorGAM:\
|
||||
${BUILD_DIR}/Components/GAMs/SlowControlGAM:\
|
||||
${BUILD_DIR}/Components/Interfaces/UDPStream:\
|
||||
${LD_LIBRARY_PATH:-}"
|
||||
|
||||
cleanup() {
|
||||
if [[ "$KEEP_CFG" -eq 1 ]]; then
|
||||
echo ""
|
||||
echo "==> Config kept at ${CFG}"
|
||||
else
|
||||
rm -f "$CFG"
|
||||
fi
|
||||
if [[ "$KEEP_CFG" -eq 1 ]]; then
|
||||
echo ""
|
||||
echo "==> Config kept at ${CFG}"
|
||||
else
|
||||
rm -f "$CFG"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
echo ""
|
||||
echo "==> Streaming on udp/${PORT}"
|
||||
echo " Channels : ${CHANNELS} x 1 Msps (${ELEMS} elem @ ${RATE} Hz)"
|
||||
for (( i = 1; i <= CHANNELS; i++ )); do
|
||||
k=$(( i - 1 ))
|
||||
printf ' Ch%-2d %8s Hz %s V\n' "$i" "${FREQS[$k]}" "${AMPS[$k]}"
|
||||
for ((i = 1; i <= CHANNELS; i++)); do
|
||||
k=$((i - 1))
|
||||
printf ' Ch%-2d %8s Hz %s V\n' "$i" "${FREQS[$k]}" "${AMPS[$k]}"
|
||||
done
|
||||
echo " Cycle : ${CYCLE_BYTES} B (MaxPayloadSize ${PAYLOAD})"
|
||||
echo " HV pulse: -40000 V, ramp up 1 ms, flat ${HV_PLATEAU_MS} ms, discharge 100 ms"
|
||||
echo " noise +-1000 V (3 sigma), EMI +-5000 V, trigger every ${HV_TRIG_PERIOD} ms"
|
||||
echo " Cycle : ${CYCLE_BYTES} B (${FRAGMENTS} x ${PAYLOAD} B datagram)"
|
||||
echo " Config : ${CFG}"
|
||||
echo ""
|
||||
echo " Consume with e.g.:"
|
||||
@@ -322,7 +461,7 @@ echo " Press Ctrl-C to stop."
|
||||
echo ""
|
||||
|
||||
exec "${MARTE2_BIN}" \
|
||||
-l RealTimeLoader \
|
||||
-f "${CFG}" \
|
||||
-s Running \
|
||||
-m StateMachine:START
|
||||
-l RealTimeLoader \
|
||||
-f "${CFG}" \
|
||||
-s Running \
|
||||
-m StateMachine:START
|
||||
|
||||
Reference in New Issue
Block a user