Five additional reference docs covering common TD use cases that were not yet documented in any reference (operators.md lists the ops, but no usage patterns). - external-data.md: webDAT, webclientDAT, webserverDAT, websocketDAT, mqttClientDAT, serialDAT, tcpipDAT — auth, polling, push, JSON parsing - panel-ui.md: custom parameter pages, button/slider/field/list COMPs, containerCOMP layouts, panelExecuteDAT callbacks - replicator.md: replicatorCOMP for data-driven cloning, per-row overrides, recreatemissing pattern, replicator vs Python loop - dat-scripting.md: full Execute DAT family — chopExecuteDAT, datExecuteDAT, parameterExecuteDAT, panelExecuteDAT, opExecuteDAT, executeDAT lifecycle - 3d-scene.md: light types, three-point rigs, shadows, IBL/cubemaps, PBR materials with idiom table, multi-camera, DOF Same conventions as existing refs: code-first, verify param names with td_get_par_info, no token-budget impact (load on demand).
9 KiB
External Data Reference
Network and device I/O — HTTP requests, WebSockets, MQTT, Serial, TCP, UDP. For MIDI/OSC specifically see midi-osc.md.
Common production needs:
- API polling / webhook ingestion
- Real-time data streams (sensors, market data, chat)
- IoT device control (Arduino, ESP32, smart lights)
- Inter-application messaging
- Hosting a tiny TD-side HTTP server for remote control
Web DAT — HTTP Requests
web = root.create(webDAT, 'api_call')
web.par.url = 'https://api.example.com/v1/status'
web.par.fetchmethod = 'get' # 'get' | 'post' | 'put' | 'delete'
web.par.format = 'auto' # 'auto' | 'text' | 'json'
web.par.timeout = 5.0
Triggering a request:
webDAT does NOT auto-fetch on cook. Trigger explicitly:
web.par.fetch.pulse()
Or via expression on a CHOP value-change (chopExecuteDAT — see dat-scripting.md).
Authentication headers:
Use webclientDAT (more flexible) or set webDAT headers via the headers DAT:
web_headers = root.create(tableDAT, 'headers')
web_headers.appendRow(['Authorization', 'Bearer YOUR_TOKEN'])
web_headers.appendRow(['Accept', 'application/json'])
web.par.headers = web_headers.path
Parsing JSON response:
import json
def onTableChange(dat):
response = dat.text # raw response body
data = json.loads(response)
# Update a tableDAT or store in a constantCHOP for downstream use
op('/project1/api_status').par.value0 = data['count']
return
Wire this in a datExecuteDAT watching the webDAT.
Polling pattern:
# timerCHOP fires every N seconds
timer = root.create(timerCHOP, 'poll_timer')
timer.par.length = 5.0
timer.par.cycle = True
# chopExecuteDAT on the timer's 'cycles' channel pulses the webDAT
def offToOn(channel, sampleIndex, val, prev):
op('/project1/api_call').par.fetch.pulse()
return
Web Client DAT — More Robust HTTP
webclientDAT is the modern replacement for webDAT — supports streaming responses, chunked transfer, custom auth.
client = root.create(webclientDAT, 'api')
client.par.method = 'POST'
client.par.url = 'https://api.example.com/events'
client.par.uploadtype = 'json'
client.par.uploaddata = '{"event": "scene_change", "scene": 3}'
client.par.request.pulse()
Output goes to its child webclient1_response DAT. Use a datExecuteDAT to react.
Web Server DAT — TD as HTTP Server
Hosts a tiny HTTP server inside TD. Useful for:
- Status/health endpoints
- Remote control from a phone or another machine
- Webhook receivers from external services
server = root.create(webserverDAT, 'control_server')
server.par.port = 8080
server.par.active = True
# Define handler in the docked callback DAT
In the auto-created webserver1_callbacks DAT:
def onHTTPRequest(webServerDAT, request, response):
path = request['uri']
if path == '/status':
response['statusCode'] = 200
response['data'] = '{"fps": 60, "scene": "active"}'
elif path == '/scene':
idx = int(request['args'].get('index', 0))
op('/project1/scene_switch').par.index = idx
response['statusCode'] = 200
response['data'] = 'OK'
else:
response['statusCode'] = 404
response['data'] = 'Not Found'
return response
Test from terminal: curl http://localhost:8080/status.
Security: No auth by default. Bind to localhost only or add a token check in the callback. Never expose to the public internet without auth.
WebSocket DAT — Bidirectional Real-Time
For low-latency bidirectional streams (chat, live data feeds, controllers).
Client
ws = root.create(websocketDAT, 'ws_client')
ws.par.netaddress = 'wss://api.example.com/socket'
ws.par.active = True
In the docked callbacks DAT:
def onConnect(dat):
dat.sendText('{"action": "subscribe", "channel": "ticks"}')
return
def onReceiveText(dat, rowIndex, message):
# message is a string; parse JSON, dispatch to ops
import json
data = json.loads(message)
op('/project1/price_chop').par.value0 = data['price']
return
def onDisconnect(dat):
# Optionally schedule a reconnect
return
Server
ws = root.create(websocketDAT, 'ws_server')
ws.par.mode = 'server'
ws.par.port = 9001
ws.par.active = True
Same callback structure with an additional clientID arg.
MQTT — Pub/Sub for IoT
mqtt = root.create(mqttClientDAT, 'iot')
mqtt.par.brokeraddress = 'broker.hivemq.com'
mqtt.par.brokerport = 1883
mqtt.par.clientid = 'td_install_01'
mqtt.par.connect.pulse()
# Subscribe in callbacks DAT:
def onConnect(dat):
dat.subscribe('home/lights/+', qos=1)
return
def onReceive(dat, topic, payload, qos, retained, dup):
# payload is bytes — decode if JSON
msg = payload.decode('utf-8')
# Dispatch by topic
return
# Publish from anywhere:
op('iot').publish('show/scene', 'sunset', qos=0, retain=False)
For Mosquitto / HiveMQ self-hosted brokers use the same setup with tcp://192.168.x.x and your local port.
Serial DAT — Arduino, USB Devices
serial = root.create(serialDAT, 'arduino')
serial.par.port = '/dev/cu.usbmodem14101' # macOS — check Arduino IDE
# Windows: 'COM3', 'COM4', etc.
serial.par.baudrate = 115200
serial.par.active = True
In callbacks:
def onReceive(dat, rowIndex, line):
# Each newline-terminated line from Arduino arrives here
parts = line.split(',')
op('/project1/sensors').par.value0 = float(parts[0])
op('/project1/sensors').par.value1 = float(parts[1])
return
Send to Arduino:
op('arduino').send('LED_ON\n')
TCP/IP DAT — Custom Protocols
For talking to non-HTTP servers (game servers, custom protocols, legacy systems).
tcp = root.create(tcpipDAT, 'show_control')
tcp.par.netaddress = '192.168.1.50'
tcp.par.port = 7000
tcp.par.protocol = 'tcp' # 'tcp' | 'udp'
tcp.par.active = True
Send / receive via callbacks similar to websocketDAT.
For UDP-only (fire-and-forget, no connection), use udpoutDAT + udpinDAT — simpler but unreliable across networks.
Common Patterns
REST API → Visual
timerCHOP (5s loop)
→ chopExecuteDAT (pulse webDAT.par.fetch on cycle)
→ webDAT (returns JSON)
→ datExecuteDAT (parse, write to constantCHOP)
→ CHOP drives glsl uniform → visuals
Webhook receiver
webserverDAT (port 8080, /webhook endpoint)
→ callback writes to a tableDAT log + triggers a scene change
Real-time stock/crypto ticker
websocketDAT (subscribe to feed)
→ onReceiveText callback parses JSON
→ writes to constantCHOP
→ drives bar chart / typography animation
IoT-controlled installation
MQTT → callback dispatches by topic
→ /lights/main → constantCHOP drives lighting render
→ /audio/volume → mathCHOP for master fader
Two-way phone control
WebSocket server in TD
→ simple HTML page on phone connects, sends slider values
→ callback writes to ops
→ TD pushes status back via dat.sendText() to phone UI
Pitfalls
webDATdoesn't auto-fetch — must explicitly pulsepar.fetch. Easy to forget.- Blocking on slow APIs —
webDATruns on the cook thread. A 30s API call freezes TD for 30s. UsewebclientDAT(async) for anything potentially slow. - WebSocket reconnection — TD does NOT auto-reconnect on disconnect. Implement backoff in
onDisconnect. - Serial port permissions on macOS — TD needs Full Disk Access OR the port needs to be unlocked via
sudo chmod 666 /dev/cu.usbmodem...per session. - MQTT broker connection state —
mqttClientDATmay showconnected=truebut messages don't flow if QoS is wrong or topic ACL blocks. Check broker logs. - JSON parse errors crash callbacks silently — wrap parses in try/except and log to textport. Otherwise the callback just stops firing.
- Firewall on Windows — first time
webserverDATbinds, Windows pops a firewall dialog. Approve it or the server is unreachable. - CORS —
webserverDATdoesn't add CORS headers by default. If serving a webapp from a different origin, addAccess-Control-Allow-Origin: *in the response. - Polling vs push — polling burns API quota. Always prefer WebSocket / webhook / MQTT for high-frequency data.
- Floating-point parsing — sensor data over Serial often comes as strings.
float()will crash on'\n'or'NaN'. Validate before converting.
Quick Recipes
| Goal | Op chain |
|---|---|
| Periodic API fetch | timerCHOP → chopExecuteDAT pulses → webDAT → datExecuteDAT parses |
| Webhook receiver | webserverDAT (port + path), callback writes to ops |
| Real-time stream | websocketDAT client → onReceiveText → CHOP/DAT |
| Arduino sensor → visual | serialDAT → callback → constantCHOP → expression on visual op |
| TD ↔ phone control | websocketDAT server + simple HTML page on phone |
| MQTT IoT integration | mqttClientDAT subscribe → callback dispatches by topic |