skill / instrument-viewer
!

Not installable via adompkg

This skill has no published release. adompkg install kyle/instrument-viewer will not work until a maintainer publishes a tarball with install.sh and uninstall.sh.

See the publishing docs for the package.json schema and tarball layout required to ship this skill.


name: instrument-viewer
description: Use when the user wants to view instruments in the Gallia Viewer, display their oscilloscope or DAQ in the GV, show live waveforms in the viewer, or open the instrument monitor panel. Also use when the user wants to control instruments — toggle relays, configure scans, change scope settings, connect/disconnect instruments. Trigger phrases include "show scope in viewer", "instrument viewer", "show scope in gv", "display DAQ in viewer", "live waveform in gv", "open instrument monitor", "toggle relay", "close relay", "open relay", "start scan", "set timebase", "set channel", "connect instrument", "isolated mode".

Instrument Viewer — Display & Control Instruments in Gallia Viewer

Displays and controls the pyvisa-testscript live instrument monitor (oscilloscope waveforms, DAQ readings, relay matrices) inside the Gallia Viewer panel.

Source code: /home/adom/pyvisa-testscript
Server: Flask on port 5000
Config: /home/adom/pyvisa-testscript/config.py

Display in Viewer (Quick Start)

1. Ensure the Flask Server is Running

curl -s --max-time 2 http://localhost:5000/ping -o /dev/null -w "%{http_code}"

If NOT 204, start it:

cd /home/adom/pyvisa-testscript && nohup python3 main.py > /tmp/flask.log 2>&1 &

2. Fetch and Display

PROXY_URL="https://coder.$(cat /etc/hostname)-containers.adom.inc/proxy/5000"

Note: The workspace ID is found from the Coder URL pattern. Currently it is:
https://coder.noah-gallia-799ae51bab88845f.containers.adom.inc/proxy/5000

PROXY_URL="https://coder.noah-gallia-799ae51bab88845f.containers.adom.inc/proxy/5000"
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${PROXY_URL}', safe=''))")
curl -s "http://localhost:5000/viewer?proxy=${ENCODED}" > /tmp/instrument-viewer.html

Then call: gv_clear followed by gv_display_file(file_path='/tmp/instrument-viewer.html', title='Scope')

REST API Reference

All endpoints are at http://localhost:5000. Use curl from within the container.

Scope Control

Endpoint Method Body Description
/ping GET Health check (returns 204)
/data GET Latest waveform data, settings, measurements
/set_acquisition POST {"mode": "run|stop|single"} Start/stop/single-shot acquisition
/set_timebase POST {"value": 0.001} Set horizontal timebase (seconds/div)
/set_time_position POST {"value": 0.0} Set horizontal time offset
/set_channel POST {"channel": 1, ...} Set channel params (see below)
/set_trigger POST {"source": "1", ...} Set trigger params (see below)
/set_acquire POST {"type": "NORM|AVER|HRES|PEAK"} Set acquisition type
/set_measurements POST [{"id":0,"source":"1","type":"FREQ"}] Set active measurements
/export_csv GET Download waveform data as CSV

Channel Parameters (/set_channel)

{
  "channel": 1,
  "enabled": true,
  "scale": 1.0,
  "offset": 0.0,
  "coupling": "DC",
  "probe": 10.0,
  "bwlimit": false,
  "invert": false
}

All fields except channel are optional — include only what you want to change.

Trigger Parameters (/set_trigger)

{
  "source": "1",
  "slope": "POS",
  "level": 1.5,
  "mode": "AUTO"
}

All fields optional — include only what you want to change.

Instrument Management

Endpoint Method Body Description
/instruments GET List all connected instruments
/instruments POST {"ip": "10.0.3.83", "port": 5025} Connect a new instrument
/instruments/<ip> DELETE Disconnect an instrument
/instruments/<ip>/data GET Get instrument state (readings, relay states, scan config)

DAQ Scan Control

Endpoint Method Body Description
/instruments/<ip>/scan/configure POST {"channels": "220", "mtype": "VOLT:DC", "interval": 0.5, "nplc": 1.0} Configure and start scanning
/instruments/<ip>/scan/start POST {} Resume a previously configured scan
/instruments/<ip>/scan/stop POST {} Pause the scan loop

Scan Parameters

  • channels: Channel string — "220", "201-220", "201,205,210", or mixed "201-205,210,220"
  • mtype: Measurement type — "VOLT:DC", "VOLT:AC", "RES", "FRES", "TEMP:TC", "TEMP:RTD", "FREQ", "DIOD", "CURR:DC", "CURR:AC"
  • interval: Seconds between scans (min 0.1)
  • nplc: Integration time in power-line cycles — 0.02 (fast), 0.2, 1 (normal), 10, 100 (accurate)

Relay / Actuator Control

Endpoint Method Body Description
/instruments/<ip>/relay/toggle POST {"channel": 101, "close": true} Close or open one relay
/instruments/<ip>/relay/open_all POST {"slot": 1} Open all relays in a slot

4×4 Relay Matrix Channel Mapping

The relay matrix uses the formula: channel = base + x*4 + y

Where base = (slot - 1) * 100 + 101, x = column index (0–3), y = row index (0–3).

Slot 1 (base=101):

X1 (x=0) X2 (x=1) X3 (x=2) X4 (x=3)
Y1 (y=0) 101 105 109 113
Y2 (y=1) 102 106 110 114
Y3 (y=2) 103 107 111 115
Y4 (y=3) 104 108 112 116

Examples:

  • Y1 → X1: channel 101 (close: true)
  • Y2 → X4: channel 114 (close: true)
  • Y3 → X2: channel 107 (close: true)

Isolated Mode

Isolated mode is ON by default in the UI (one connection per row, one per column). When controlling relays via API, YOU must enforce isolation logic:

  • Before closing a relay at (X, Y), open any existing relay in column X and row Y first
  • Or use /relay/open_all to clear the slot, then close only the desired relays

Relay State Sync

The /instruments/<ip>/data endpoint includes closed_relays showing which relays are physically closed on the instrument:

{
  "closed_relays": {
    "1": [101, 114]
  }
}

The UI automatically syncs to match this hardware state on every poll cycle. Relay states are queried by the background DAQ loop thread (not the HTTP handler) to avoid VISA resource contention. This means relay toggle commands issued via the API will be reflected in the UI within one poll cycle (~0.25s when idle, or after the next scan cycle).

Common Commands (Copy-Paste Examples)

Connect the DAQ

curl -s -X POST http://localhost:5000/instruments -H 'Content-Type: application/json' -d '{"ip": "10.0.3.83", "port": 5025}'

Open all relays in slot 1, then close specific ones

curl -s -X POST http://localhost:5000/instruments/10.0.3.83/relay/open_all -H 'Content-Type: application/json' -d '{"slot": 1}'
curl -s -X POST http://localhost:5000/instruments/10.0.3.83/relay/toggle -H 'Content-Type: application/json' -d '{"channel": 101, "close": true}'
curl -s -X POST http://localhost:5000/instruments/10.0.3.83/relay/toggle -H 'Content-Type: application/json' -d '{"channel": 114, "close": true}'

Start scanning channel 220 at 0.5s

curl -s -X POST http://localhost:5000/instruments/10.0.3.83/scan/configure -H 'Content-Type: application/json' -d '{"channels": "220", "mtype": "VOLT:DC", "interval": 0.5}'

Stop scanning

curl -s -X POST http://localhost:5000/instruments/10.0.3.83/scan/stop -H 'Content-Type: application/json' -d '{}'

Set scope timebase to 1ms/div

curl -s -X POST http://localhost:5000/set_timebase -H 'Content-Type: application/json' -d '{"value": 0.001}'

Enable channel 2 at 500mV/div

curl -s -X POST http://localhost:5000/set_channel -H 'Content-Type: application/json' -d '{"channel": 2, "enabled": true, "scale": 0.5}'

Set trigger to channel 1, rising edge, 1.5V

curl -s -X POST http://localhost:5000/set_trigger -H 'Content-Type: application/json' -d '{"source": "1", "slope": "POS", "level": 1.5}'

Single-shot acquisition

curl -s -X POST http://localhost:5000/set_acquisition -H 'Content-Type: application/json' -d '{"mode": "single"}'

Instrument Configuration (config.py)

Edit /home/adom/pyvisa-testscript/config.py:

IP_ADDRESS   = "10.0.3.127"                          # Primary oscilloscope IP
VISA_ADDRESS = f"TCPIP0::{IP_ADDRESS}::5025::SOCKET"
DRIVER_NAME  = "keysight_dsox1204g"                   # Must match drivers/__init__.py REGISTRY

STARTUP_INSTRUMENTS: list[dict] = [
    {"ip": "10.0.3.83", "port": 5025},   # Keysight DAQ970A
]

After editing config.py, restart the Flask server.

Troubleshooting

Symptom Cause Fix
"Disconnected" in viewer Flask server not running or proxy URL wrong Check curl http://localhost:5000/ping returns 204
Scope shows flat line Instrument not reachable python3 -c "import socket; s=socket.socket(); s.settimeout(3); s.connect(('10.0.3.127', 5025)); print('OK')"
DAQ tab but no readings Scan not started Use /scan/configure endpoint
Relay matrix doesn't match hardware UI not synced The UI auto-syncs via closed_relays in /data. Refresh the viewer page.
Relay flickering on/off in UI VISA contention (fixed) Relay queries now run in the DAQ background thread only. If still seen, ensure routes.py does NOT query relay states in the HTTP handler.
CORS errors Missing headers Ensure server/__init__.py has the CORS @app.after_request handler
Low FPS in viewer Polling overhead (250ms) Expected. For high-speed, open proxy URL directly in new browser tab