pxnode HTTP API
Every setting of a pxnode, and its live state, as JSON over HTTP. Read a section with
GET, change it with PUT, trigger an action with POST.
http://<node-ip>/api/Connecting
Each node serves the API on TCP port 80 of its own IP address, shown on its screen. Talk to every node directly:
start from any one of them, and GET /api/nodes lists the others.
- HTTP/1.0, one request per connection (
Connection: close). A request with a body must sendContent-Length. - Bodies are JSON objects of at most 2560 bytes (413 beyond). Two exceptions take larger bodies:
/api/config(32 KB) and/api/firmware. - Responses are
application/jsonand not cached (Cache-Control: no-store)./api/nodes,/api/artnetand/api/configcome withoutContent-Length: read until the connection closes. - Concurrency: up to 8 connections at a time. When the node is busy it answers 503
busy: retry shortly. An idle connection is closed after 10 s. - CORS:
Access-Control-Allow-Origin: *;OPTIONSanswers 204, allowingGET,PUT,POSTand theContent-TypeandAuthorizationheaders. A web page can use the API from any origin.
Paths outside /api/ serve the node's own web page.
Authentication
A node is open by default: no password, nothing to send. If someone sets a password on it
(PUT /api/password), every API request must then carry that password. The one
exception is GET /api/node, which always answers.
1. Does this node need a password?
Read GET /api/node: "password": true means yes.
2. Send it in every request
Add this header, where the value is the password with a colon in front, base64-encoded:
Authorization: Basic base64(":" + password)
For the password stage left: ":stage left" encodes to OnN0YWdlIGxlZnQ=, so
the header is:
Authorization: Basic OnN0YWdlIGxlZnQ=
This is standard HTTP Basic authentication with an empty user name, so most tools build the header for you:
# curl: an empty user name, then the password
curl -u ':stage left' http://10.2.1.99/api/name
// JavaScript
fetch("http://10.2.1.99/api/name", {headers: {Authorization: "Basic " + btoa(":stage left")}})
3. When it is refused
| Status | error | Meaning |
|---|---|---|
| 401 | password-required | the node has a password and the request had none |
| 401 | password-wrong | the password is wrong |
| 429 | password-wait | too soon after a wrong password: the node did not check it. Wait and retry. The wait is 1 s after a wrong password, then doubles with each wrong one in a row, up to 16 s |
A browser will not show its own login box for these: ask the user for the password in your interface.
Conventions
- Sections. Each settings route is a section of the configuration.
GETreturns all its fields; aPUTreturns the section as saved, like aGET. - Partial writes. A
PUTchanges only the fields it carries. The others keep their value. - All or nothing. Every field is checked before anything is saved. An invalid write changes nothing, and the error names the first invalid field.
- Saved at once. A
PUTon a section is saved to flash and survives a restart. Live routes (presets/active, blackout, the previews) are not saved. - Revision. Every section carries
"revision", a number that grows by one with each save. Send the revision your view was based on with a write: if someone saved since, the write is refused with 409 and you can reload instead of overwriting their change. Leave it out to write unconditionally. - Forward compatible. Unknown fields in a request are ignored. Ignore unknown fields in responses: new versions add fields, and a field is never renamed or changes its unit.
- Mode independent. Fields that do not apply to the current mode are still returned and kept.
Errors
Errors are JSON objects with an error code, often a message, and for invalid input the
field at fault:
{"error":"invalid","field":"groups[0].pixels","message":"integer from 0 to 512"}
| Status | error | When |
|---|---|---|
| 400 | json | the body is not a JSON object |
| 400 | invalid | a field is out of range, of the wrong type, or breaks a rule |
| 400 | request | malformed request |
| 400 | firmware | a firmware file refused (/api/firmware) |
| 401 | password-required | the node has a password, the request none |
| 401 | password-wrong | wrong password |
| 404 | not-found | unknown path |
| 405 | method | method not allowed on this path |
| 409 | conflict | the revision sent is not the current one |
| 409 | unsupported | the node cannot do it (recovery on an old bootloader) |
| 413 | too-large | request body too large |
| 429 | password-wait | a password sent too soon after a wrong one, not checked |
| 500 | flash | the settings could not be saved |
| 503 | busy | the node is busy: retry shortly |
The node's identity. The one request that never needs the password.
| Field | Type | Description |
|---|---|---|
uuid | string | unique ID, 24 hex digits |
name | string | node name |
zone | string | its zone (/api/network),
"" for none |
manufacturer | string | Kinora |
model | string | pxnode 4 or px4i |
firmware | string | firmware version |
api | integer | API version: 1. Changes only for an incompatible change |
capabilities | array | optional features: rgbw (RGBW
outputs), fx (effects), sync (nodes kept in step) |
mac | string | MAC address, aa:bb:cc:dd:ee:ff |
slot | string | firmware slot running: A or B |
bootloader | string | bootloader version; "" for one
without HTTP recovery |
http | integer | HTTP port, 80 |
password | boolean | the other requests need a password |
ip, mode, revision | as in their sections |
Older firmware may leave out zone, bootloader and password:
read them as "", "" and false.
{"uuid":"003000513431500d20343556","name":"Stage left","zone":"","manufacturer":"Kinora","model":"pxnode 4",
"firmware":"0.1.8","api":1,"capabilities":["rgbw","fx","sync"],"mac":"00:80:e1:62:b2:52","slot":"B",
"bootloader":"1.0.0","http":80,"password":false,"ip":"10.2.1.99","mode":"standalone","revision":479}
Health and live counters: what the node is doing now, and what went wrong since it started. Nothing here is saved; everything restarts at boot.
Node
| Field | Description |
|---|---|
link | Ethernet link up |
uptime_s | seconds since start |
reset_cause | why it last started: power-on, brown-out (supply
dipped), watchdog, window-watchdog, low-power, software (a reboot
or firmware update), reset-pin, unknown |
temperature_c | processor temperature in °C (not the air around the board);
null during the first second |
cpu_load | % of the last second the processor was busy |
stack_free | bytes of stack never used since start |
Input
| Field | Description |
|---|---|
input | ok: data the node uses arrived in the last 4 s; lost:
none for 4 s; none: standalone mode, or nothing received since start |
input_age_ms | ms since that data last arrived, -1: never |
artnet_source | IP of the last Art-Net sender, "" if none. A value that keeps
changing means two controllers are sending |
artsync | boolean |
blackout | the outputs are dark or fading to it (/api/blackout) |
Rates, over the last second
| Field | Description |
|---|---|
artnet_fps | ArtDmx packets received |
artnet_universes | universes of those packets |
artnet_frame_rate | the controller's frame rate |
artsync_fps | packets per second |
pixel_fps | pixel frames sent |
dmx_in_fps, dmx_out_fps | DMX frames received and sent |
network_fps | Ethernet frames received, all traffic |
artnet_ignored_fps | Art-Net packets dropped because sent to the broadcast address
(ignore_broadcast, /api/mode) |
Pixel output
| Field | Description |
|---|---|
frame_us | frame period in µs: 16666 at 60 frames/s |
frame_rate_max | the frame rate it gives (/api/timing) |
render_us | slowest effect frame of the last second, in µs. Must stay well under
frame_us |
pixel_overruns | frames started before the previous one was sent, since start |
Network errors, since start
| Field | Description |
|---|---|
net_rx_missed | Ethernet frames dropped under a traffic burst. Universes lost show here |
net_rx_errors | frames received damaged (cable, connector, switch) |
pbuf_exhausted, heap_exhausted | times the network stack ran out of memory |
Totals and settings storage
| Field | Description |
|---|---|
totals | counters since start: artnet_packets, dmx_in_frames,
dmx_out_frames, pixel_frames, pixel_overruns. A short problem still shows
after it is over |
config.saves | settings saves, ever (the revision) |
config.erases | saves since start that erased the settings sector (1 to 2 s of frozen outputs each) |
config.free_bytes | room before the next erase; a save takes about 1.2 KB |
sync.events, sync.last_age_ms | input events that restarted the effects to keep nodes in step, and ms since the last (-1: none) |
Zone sync
The pxsync object: how the node keeps in step with the other nodes of its zone
(pxsync in /api/timing).
| Field | Description |
|---|---|
enabled | as pxsync in /api/timing |
role | leader, follower, alone (no other node of
the zone heard) or off |
nodes, peers | the other nodes of the zone heard in the last second, and
their name, ip, leader; more_peers: how many more than
listed |
leader, leader_name, leader_ip, leader_frame_rate |
the node followed |
frames, frame_error_us | frames started with the leader's, and the last phase difference measured |
frames_reason | why frames are not followed: measuring, artsync,
leader-artsync, frame-rate (another frame rate than the leader's) |
effects, effects_reason | effects in phase with the leader's; why not:
measuring, not-standalone, leader-not-standalone, other-preset |
{"link":true,"uptime_s":3612,"reset_cause":"power-on","temperature_c":46.4,"cpu_load":36,"stack_free":17192,
"input":"ok","input_age_ms":24,"artnet_source":"10.2.1.215","artsync":false,"blackout":false,
"artnet_fps":468,"artnet_universes":12,"artnet_frame_rate":40,"artsync_fps":0,"pixel_fps":60,"dmx_in_fps":0,
"dmx_out_fps":43,"network_fps":470,"artnet_ignored_fps":0,"frame_us":16666,"frame_rate_max":60,"render_us":702,
"pixel_overruns":1,"net_rx_missed":0,"net_rx_errors":0,"pbuf_exhausted":0,"heap_exhausted":0,
"totals":{"artnet_packets":1690245,"dmx_in_frames":0,"dmx_out_frames":155316,"pixel_frames":216720,"pixel_overruns":1},
"config":{"saves":76,"erases":0,"free_bytes":43520},"sync":{"events":0,"last_age_ms":-1},
"pxsync":{"enabled":true,"role":"alone","nodes":0,"peers":[],"more_peers":0}}
The Art-Net controllers sending to the node, up to 10, most recent first. When several send the same universe, the node takes the last packet received.
| Field | Description |
|---|---|
ip | the sender's address |
active | a packet in the last 4 s |
last_seen_ms | ms since its last packet |
packets_per_s | its ArtDmx packets over the last second |
dropped_per_s | of them, dropped by ignore_broadcast |
broadcast | its last packet went to the broadcast address |
artsync | boolean |
universes | the universes it sent in the last 4 s, up to 16: universe,
packets_per_s, used (the node uses its data), shared (another active sender
sends it too) |
more_universes | more universes than listed |
{"senders":[{"ip":"10.2.1.215","active":true,"last_seen_ms":12,"packets_per_s":480,"dropped_per_s":0,
"broadcast":false,"artsync":true,"universes":[{"universe":0,"packets_per_s":40,"used":true,"shared":false}],
"more_universes":false}]}
The nodes of the network this node has found, itself first, up to 32. Only nodes of its own zone are listed.
Each entry has the fields of GET /api/node, plus mask,
online (heard in the last 32 s) and last_seen_ms. A node that went silent stays listed,
offline, until this node restarts. The list refreshes every 10 s.
{"nodes":[{"uuid":"003000513431500d20343556","name":"Stage left","zone":"","manufacturer":"Kinora",
"model":"pxnode 4","firmware":"0.1.8","api":1,"capabilities":["rgbw","fx","sync"],"mac":"00:80:e1:62:b2:52",
"slot":"B","bootloader":"1.0.0","http":80,"password":false,"ip":"10.2.1.99","mask":"255.255.255.0",
"mode":"standalone","revision":479,"online":true,"last_seen_ms":0}]}
Searches the network now instead of waiting for the next refresh. Replies arrive within milliseconds: read
/api/nodes a few hundred ms later. Answers {"ok":true}.
| Field | Type | Description | Default |
|---|---|---|---|
name | string | 1 to 32 characters, Latin-1, no control characters | pxnode 4 |
curl -X PUT -d '{"name":"Stage left"}' http://10.2.1.99/api/name
{"name":"Stage left","revision":480}
| Field | Type | Description | Default |
|---|---|---|---|
ip | IPv4 | not 0.0.0.0 | 10.0.0.99 |
mask | IPv4 | contiguous, not 0.0.0.0 or 255.255.255.255 | 255.255.255.0 |
gateway | IPv4 | 10.0.0.1 | |
zone | string | 0 to 16 printable ASCII characters. Nodes of a zone are listed together and keep their effects and frames in step. It separates rigs sharing a network (two stages); it is not a security boundary | "" |
{"ip":"10.2.1.99","mask":"255.255.255.0","gateway":"10.2.1.1","zone":"","revision":479}
What drives the pixels.
| Field | Type | Description | Default |
|---|---|---|---|
mode | string | standalone (the node's own
presets), dmx-shape, artnet-standard,
artnet-advanced, artnet-shape | dmx-shape |
input_address | 1-512 | first channel of the input profile (shape modes) | 1 |
input_profile | string | rgb (dimmer, R, G, B, and W on
RGBW outputs: 4 or 5 channels) or fx (effects: 12 or 14 channels) | fx |
input_universe | 0-32767 | universe read in Art-Net shape mode. Same
setting as output_universe of /api/dmx: writing one changes the other | 0 |
control_universe | 0-32767 | universe of the control channels (Art-Net advanced mode) | 28 |
control_address | 1-512 | first control channel: global dimmer, shutter, then one dimmer per group | 1 |
ignore_broadcast | boolean | only Art-Net data sent to the node's own address drives it; data sent to a broadcast address is dropped | false |
curl -X PUT -d '{"mode":"artnet-shape","input_profile":"fx","input_address":101}' http://10.2.1.99/api/mode
{"mode":"artnet-shape","input_address":101,"input_profile":"fx","input_universe":0,"control_universe":28,
"control_address":1,"ignore_broadcast":false,"revision":480}
The DMX output, fed from an Art-Net universe.
| Field | Type | Description | Default |
|---|---|---|---|
output_enabled | boolean | the DMX output sends (Art-Net modes) | true |
output_universe | 0-32767 | the Art-Net universe sent on the DMX output | 0 |
{"output_enabled":true,"output_universe":0,"revision":479}
The node's screen and keys.
| Field | Type | Description | Default |
|---|---|---|---|
brightness | 0-100 | backlight %. Rounded to the nearest of 5 levels (20 to 100) and read back as that level | 100 |
screensaver_s | 0-3600 | the screen turns off after that many seconds without a key press; 0: never | 10 |
rotation | 0 or 180 | pxnode 4 mounted upside down: the screen and the keys turn | 0 |
lock | boolean | the keys are locked. Holding back and enter for 2 s
unlocks them for a minute; false unlocks for good. It protects the screen, not the API | false |
{"brightness":100,"screensaver_s":10,"rotation":0,"lock":false,"revision":479}
| Field | Type | Description | Default |
|---|---|---|---|
frame_rate_max | 20-120 | pixel frames per second. Above 60 each output
sends fewer pixels (max_pixels, /api/outputs/{X}) | 60 |
artsync | boolean | Art-Net standard and advanced modes | true |
effect_sync | boolean | effects restart together on every node on an input event (effect or shutter change, dimmer leaving 0) | true |
pxsync | boolean | keep in step with the other nodes of the zone: frames started together, effects in phase, zone-wide preset changes received | true |
{"frame_rate_max":60,"artsync":true,"effect_sync":true,"pxsync":true,"revision":479}
Sets, changes or removes the node's password (see Authentication). Changing it needs the current one. It protects the settings, not the show: Art-Net, DMX and the presets keep working.
| Field | Type | Description |
|---|---|---|
password | string | required: 1 to 64 printable ASCII characters, or
"" to remove it |
The password is never read back, and GET /api/config leaves it out. Forgotten: on the node's screen,
Menu, Factory reset, Network only (the address returns to 10.0.0.99).
curl -X PUT -d '{"password":"stage left"}' http://10.2.1.99/api/password
{"password":true,"revision":481}
The four pixel outputs at a glance.
{"outputs":[{"output":"A","color_mode":"rgb","pixels":340,"groups":2,"max_pixels":512},
{"output":"B","color_mode":"rgb","pixels":340,"groups":2,"max_pixels":512},
{"output":"C","color_mode":"rgb","pixels":340,"groups":2,"max_pixels":512},
{"output":"D","color_mode":"rgb","pixels":340,"groups":2,"max_pixels":512}],"revision":479}
One output: its pixel type and its patch, up to 16 groups of pixels, each on an Art-Net universe and address.
| Field | Type | Description | Default |
|---|---|---|---|
output | string | read only: A to D | |
color_mode | string | rgb (WS2812, 3 channels a pixel) or
rgbw (SK6812, 4 channels) | rgb |
max_pixels | integer | read only: pixels the output can send, 512 RGB or 384 RGBW up to 60 frames/s, fewer above | |
groups | array | 16 groups, below | 6 groups of 64 pixels, two per universe (A on universes 0-2, B 3-5, C 6-8, D 9-11) |
Group
| Field | Type | Description | Default |
|---|---|---|---|
pixels | 0-512 | pixels in the group, 0: unused | 64 (groups 0-5), 0 |
universe | 0-32767 | Art-Net universe | 0 |
address | 1-512 | first channel in the universe | 1 |
order | string | colour order: rgb, bgr,
gbr, grb, rbg. W always comes last | grb |
flip | boolean | pixels in reverse order | false |
brightness | 0-255 | cap on the group's levels | 255 |
highlight | boolean | the group lights red to find it on the rig. Preview only, never saved | false |
Writing a patch
- Element i of
groupsupdates group i; its missing fields, and the groups past the end of the array, are unchanged. Send all 16 groups to set a whole patch. - A group does not continue on the next universe: pixels past channel 512 stay black (170 RGB or 128 RGBW pixels fit from address 1).
- More pixels than
max_pixelsare accepted; the extra ones are not sent. - The other outputs are not changed.
curl -X PUT -d '{"color_mode":"rgbw","groups":[{"pixels":128,"universe":4},{"pixels":128,"universe":5},{"pixels":0}]}' \
http://10.2.1.99/api/outputs/B
{"output":"A","color_mode":"rgb","max_pixels":512,"groups":[
{"pixels":170,"universe":0,"address":1,"order":"grb","flip":false,"brightness":255,"highlight":false},
{"pixels":170,"universe":1,"address":1,"order":"grb","flip":false,"brightness":255,"highlight":false},
{"pixels":0,"universe":0,"address":1,"order":"grb","flip":false,"brightness":255,"highlight":false}],
"revision":479}
Same body and rules as the PUT, plus highlight, applied to the output at once and
not saved: for live patching in an editor, each change sent as it is made. Answers with the output
as previewed. A restart, or a PUT of the output, returns to the saved patch.
curl -X POST -d '{"groups":[{},{"highlight":true}]}' http://10.2.1.99/api/outputs/A/preview
In standalone mode the pixels show one of the node's presets: a colour, or an effect between two colours. Up to 12 presets; the first is always there and always a single colour.
| Field | Type | Description | Default |
|---|---|---|---|
active | 0-11 | the default preset, shown at start: an index of
presets (also /api/presets/default) | 0 |
presets | array | 1 to 12 presets, below. A PUT with it
replaces the list; element i starts from preset i (a new one from the first), and its missing
fields keep their value | 6 factory presets |
Preset
| Field | Type | Description |
|---|---|---|
name | string | up to 15 characters |
profile | string | rgb (one colour) or shape
(an effect between two colours). The first preset is always rgb |
dimmer | 0-255 | overall level |
color | hex | rgb: "rrggbbww" (or
"rrggbb"); W drives the white LED of RGBW outputs |
bg, fg | hex | shape: background and effect
colours, as color |
effect | string | shape: sine,
gauss, triangle, tan, square, random,
twinkle, fire, rainbow, noise, bounce,
lightning, drops, segment-flash, glitter, ocean,
rain, plasma, none |
speed, scale, fx_dimmer | 0-255 | shape:
effect speed, size and level |
Factory presets: Solid colour (white), Fire, Ocean, Rainbow, Starry night, Plasma.
{"active":0,"presets":[{"name":"Solid colour","profile":"rgb","dimmer":255,"color":"ffffff00"},
{"name":"Fire","profile":"shape","dimmer":255,"bg":"46000000","fg":"ff820000","effect":"fire","speed":150,
"scale":60,"fx_dimmer":255}],"revision":479}
The preset shown now, for show control: changed with a crossfade, not saved. After a restart the node shows its default. In a mode other than standalone, the choice is kept for when the node returns to it.
| Field | Type | Description |
|---|---|---|
preset | integer | index in /api/presets.
Required in a PUT |
fade_ms | 0-600000 | crossfade from what the outputs show now; 0 or absent: at once |
group | boolean | PUT: show it on every node of the zone,
this one included, each with its own preset at that index. Default false |
save | boolean | PUT: also make it the default (a flash
write). Default false. Prefer /api/presets/default |
name, fading, saved, previewing | read only | its name, a crossfade running, it is the default, a preview is shown instead |
curl -X PUT -d '{"preset":2,"fade_ms":4000}' http://10.2.1.99/api/presets/active
{"preset":2,"name":"Ocean","fading":true,"saved":false,"previewing":false}
The default preset, shown when the node starts. Setting it saves it (one flash write, none if it already is the default) and does not change the preset shown now.
| Field | Type | Description |
|---|---|---|
preset | integer | index in /api/presets.
Required in a PUT |
name | read only | its name |
curl -X PUT -d '{"preset":3}' http://10.2.1.99/api/presets/default
{"preset":3,"name":"Rainbow"}
Shows a preset being edited instead of the preset shown, without saving it. {"preset": {...}} takes
the fields of a preset; the ones left out come from the previous preview, or from the preset shown. The preview ends
with {"stop": true}, when a preset is shown, or 10 s after the last preview: an editor resends it every
few seconds while it stays open. Answers like GET /api/presets/active.
curl -X POST -d '{"preset":{"effect":"plasma","speed":200}}' http://10.2.1.99/api/presets/preview
Every output dark, pixels and DMX, whatever the input. Not saved: a restart releases it. Inputs are still read and effects keep running, so a release shows the outputs as they are now.
| Field | Type | Description |
|---|---|---|
on | boolean | dark. Required in a PUT |
fade_ms | 0-600000 | fade to black (or back) over that time; 0 or
absent: at once. A new PUT during a fade starts from the level reached |
level | read only | output level now, 255 (as the input) to 0 (black) |
curl -X PUT -d '{"on":true,"fade_ms":3000}' http://10.2.1.99/api/blackout
{"on":true,"level":255,"fade_ms":3000}
The whole configuration in one document, to back up a node or copy it onto another. Its sections are those of
their own routes: name, network, mode, dmx, display,
timing, presets, and outputs (A to D, as /api/outputs/{X}).
About 7 KB.
- GET: one
revisionat the top; read-only and preview fields are left out. - PUT: up to 32 KB. Each section is checked like its own
PUT, then everything is saved once and applied: all or nothing. A section left out keeps its settings, so a document with onlymodeandoutputschanges only those. Members other than the sections are ignored. - With
revision, the import is refused (409) if the node was changed since. Leave it out to restore a backup. - Answers
{"ok":true,"revision":152}. An error names its section:"field":"outputs[1].groups[0].pixels". A new network address applies once the answer is sent.
{"api":1,"model":"pxnode 4","firmware":"0.1.8","revision":151,
"name":{"name":"Stage left"},
"network":{"ip":"10.2.1.99","mask":"255.255.255.0","gateway":"10.2.1.1","zone":""},
"mode":{...},"dmx":{...},"display":{...},"timing":{...},"presets":{"active":0,"presets":[...]},
"outputs":[{"output":"A","color_mode":"rgb","groups":[{"pixels":170,"universe":0,"address":1,...}]}]}
curl http://10.2.1.99/api/config > stage-left.json
curl -X PUT --data-binary @stage-left.json http://10.2.1.100/api/config
network (and name) from the file to copy everything else.Shows an identify screen on the node and lights its identify LED, to find it on the rig. Answers
{"ok":true}.
Restarts the node about 200 ms after answering {"ok":true}.
Restarts into recovery: the node stays at the same address and serves a page to install a firmware. The outputs
stay dark until it restarts. Needs bootloader 1.0 or later (bootloader in
/api/node); 409 unsupported otherwise.
Restores factory settings, then restarts. Without a body: everything, the name included (address
10.0.0.99/24, no zone, no password, DMX shape mode, default patch and presets). With {"sections": [...]}, only those
parts:
| Section | Resets |
|---|---|
network | IP, mask, gateway, zone and password |
mode | mode, input, control channels, DMX output |
patch | groups and colour modes of the outputs |
advanced | /api/timing, the display and its lock, the broadcast filter |
presets | the factory presets |
curl -X POST -d '{"sections":["patch","presets"]}' http://10.2.1.99/api/actions/factory-reset
Installs a firmware. The body is the .pxfw file of a release, sent as is with its
Content-Length. The node writes it next to the firmware it runs, checks it, answers
{"ok":true,"slot":"A"} and restarts on it. During the write the outputs freeze for a few seconds.
If the new firmware fails to start three times, the node goes back to the previous one by itself. A file that is
not a valid firmware for the node is refused with 400 firmware and a message, and nothing
changes.
curl --data-binary @pxnode-2.0.0.pxfw -H 'Content-Type: application/octet-stream' http://10.2.1.99/api/firmware
{"ok":true,"slot":"A"}
Recipes
# who is this node
curl http://10.2.1.99/api/node
# rename it: only this field changes
curl -X PUT -d '{"name":"Stage left"}' http://10.2.1.99/api/name
# show preset 2 with a 2 s crossfade on every node of the zone
curl -X PUT -d '{"preset":2,"fade_ms":2000,"group":true}' http://10.2.1.99/api/presets/active
# fade everything to black over 3 s, then back
curl -X PUT -d '{"on":true,"fade_ms":3000}' http://10.2.1.99/api/blackout
curl -X PUT -d '{"on":false,"fade_ms":3000}' http://10.2.1.99/api/blackout
# a write based on revision 10: refused if someone saved since
curl -X PUT -d '{"name":"x","revision":10}' http://10.2.1.99/api/name
{"error":"conflict","message":"the configuration changed since this revision"}
# an invalid value: nothing is changed
curl -X PUT -d '{"groups":[{"pixels":600}]}' http://10.2.1.99/api/outputs/A
{"error":"invalid","field":"groups[0].pixels","message":"integer from 0 to 512"}
# a node with a password
curl -u ':stage left' http://10.2.1.99/api/stats