You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit db2debe
Browse filesBrowse the repository at this point in the historyBrowse files
fix: keep table handles alive across live re-syncs, plus dashboard fixes
- Data tables: an identical layout no longer rebuilds the store, and a real
rebuild under a live session reloads the frame parsers, so cached handles
never go stale and table-driven boards stop going silent after a save
- API: io.getLatestFrame reports mirrorAgeMs so scripts can tell GUI lag
from bus silence
- Checksum API: accept typed arrays in JS (fixes tst_checksum_api)
- Annunciator: bell icon and count text share one animated colour
- Widget menu: icons tinted with the theme text colour, resolved via the
icon registry
- Freeze title: new "both" mode so the title bar and face title combine;
only "hidden" is exclusive
- Tests: tst_data_table_layout, freeze-title integration cases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: app/rcc/ai/docs/control_script_js.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,7 +39,7 @@ nothing executes (no `apiCall`, no `tableSet` side effects).
39
39
|---|---|
40
40
|`apiCall(method, params)`| Call ANY API command by name; marshalled (blocking) to the GUI thread. Returns the command's response object (`{ok, result?, error?}`). |
41
41
|`delay(ms)`| Sleep without tripping the watchdog. |
42
-
|`newFrame(sourceId?)`| Latest received frame, returned exactly once per arrival; `null` when nothing new. Fields: `sourceId`, `sequence`, `timestampMs` (monotonic clock; never compare with `Date.now()`), `ageMs` (milliseconds since arrival; use this for staleness/watchdogs), `values` (the parser tokens, with `valueCount` and optional `base64`), `text`. |
42
+
|`newFrame(sourceId?)`| Latest received frame, returned exactly once per arrival; `null` when nothing new. Fields: `sourceId`, `sequence`, `timestampMs` (monotonic clock; never compare with `Date.now()`), `ageMs` (milliseconds since arrival; use this for staleness/watchdogs), `mirrorAgeMs` (how long ago the host refreshed the copy you read; subtract it from `ageMs` so GUI lag never reads as bus silence), `values` (the parser tokens, with `valueCount` and optional `base64`), `text`. |
43
43
|`refreshDashboard()`| Re-runs every dataset transform from the last received values and republishes to the dashboard (no export side effects). Call after `tableSet()` writes so they render while the device is silent. |
44
44
|`ensureDashboard(spec)`| Declaratively create missing groups/datasets (matched by title / parser index). Existing items are never modified; memoized, so calling it every `loop()` is free. |
45
45
|`dashboardTick()`| Wraps `dashboard.tick`: synthesizes a frame from current dataset values and fans it out to every consumer (dashboard AND CSV/MDF4/Historian/MQTT/API exports), unlike `refreshDashboard()`, which has no export side effects. |
@@ -66,7 +66,8 @@ mirrored as `raw:<alias>` / `final:<alias>`.
66
66
67
67
Use `ageMs` instead of bookkeeping arrival times with `Date.now()`: it is computed by the
68
68
host from the same monotonic clock that stamps frames, needs no per-script state, and is
69
-
immune to reconnect races:
69
+
immune to reconnect races. Judge silence with `ageMs - mirrorAgeMs`: the copy a script reads
70
+
refreshes on the display tick, so a GUI busy with plots adds lag that is not bus silence:
Copy file name to clipboardExpand all lines: app/rcc/api/api-schema.json
+6-2Lines changed: 6 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -3555,7 +3555,7 @@
3555
3555
]
3556
3556
},
3557
3557
{
3558
-
"description": "Set the freeze-mode title presentation for one widget (widgetType + uniqueId): bar shows the panel header, painted shows the title on the instrument face (Bar/Gauge/Meter only, their default), hidden shows no title while the dashboard is frozen. Every other widget type defaults to bar; writing a widget's default clears its stored entry.",
3558
+
"description": "Set the freeze-mode title presentation for one widget (widgetType + uniqueId): bar shows the panel header, painted shows the title on the instrument face (Bar/Gauge/Meter only, their default), both shows the header and the face title (Bar/Gauge/Meter only), hidden shows no title while the dashboard is frozen. Every other widget type defaults to bar; writing a widget's default clears its stored entry.",
3559
3559
"name": "project.dashboard.setWidgetFreezeTitle",
3560
3560
"properties": {
3561
3561
"mode": {
@@ -3852,7 +3852,7 @@
3852
3852
]
3853
3853
},
3854
3854
{
3855
-
"description": "Add a dataset to a group. A dataset is one channel of incoming data: it gets one column in CSV/MDF4 export, one entry in the frame parser's output array (its `index` field), and zero or more dashboard widgets driven by the `options` bitflags.\nPass `options` as the bitwise OR of the visualizations you want:\n 1 = Plot (time-series line)\n 2 = FFT (frequency-domain plot)\n 4 = Bar\n 8 = Gauge (radial dial; needs widgetMin/widgetMax)\n 16 = Compass (heading 0-360)\n 32 = LED (binary indicator with ledHigh threshold)\n 64 = Waterfall (Pro; spectrogram, FFT-driven)\n 0 = no widget (raw column for export only)\nCombine: 1|8 = 9 = plot AND gauge. After creation, set title, units, ranges, and transformCode via project.dataset.update.",
3855
+
"description": "Add a dataset to a group. A dataset is one channel of incoming data: it gets one column in CSV/MDF4 export, one entry in the frame parser's output array (its `index` field), and zero or more dashboard widgets driven by the `options` bitflags.\nPass `options` as the bitwise OR of the visualizations you want:\n 1 = Plot (time-series line)\n 2 = FFT (frequency-domain plot)\n 4 = Bar\n 8 = Gauge (radial dial; needs widgetMin/widgetMax)\n 16 = Compass (heading 0-360)\n 32 = LED (binary indicator with ledHigh threshold)\n 64 = Waterfall (Pro; spectrogram, FFT-driven)\n 0 = no widget (raw column for export only)\nCombine: 1|8 = 9 = plot AND gauge. After creation, set title, units, ranges, and transformCode via project.dataset.update.\nOnly plain, Multi-Plot, Data Grid and Bar Panel groups accept datasets; a widget that fixes its layout (Plot3D, Gyroscope, GPS, ...) refuses.",
3856
3856
"name": "project.dataset.add",
3857
3857
"properties": {
3858
3858
"groupId": {
@@ -4327,6 +4327,10 @@
4327
4327
"description": "Patch dataset fields by id: params are groupId, datasetId, plus any of the writable fields declared in this command's schema -- read the typed 'properties' block for each field's type, description and enum domain instead of guessing from prose.\nFields whose project-file spelling differs from the API name (plotMin, plotMax, widgetMin, widgetMax, xAxis, datasetSourceId) are accepted as aliases, so an object read back from the API can be written straight through without loss.\nNested collections: alarmBands takes an array of {min,max,severity,color,label,blink} objects (severity=0..3 for Info/Ok/Warning/Critical), and legacy alarmLow/alarmHigh/alarmEnabled still drive the 2-band simple mode; fftMarkers takes an array of {freq,endFreq,label,color,warningDb,alarmDb} objects (see project.dataset.setFFTMarkers for field semantics).\nThe boolean fields graph/fft/led/waterfall toggle the same flags as project.dataset.setOption -- use them here when patching multiple fields at once. Multiplot groups follow the group's FIRST dataset for plotLogX/plotLogY, so set those flags on every member to keep the group coherent.\nUnknown fields are accepted but ignored, and surfaced in result.warnings[].fields with code 'unknown_field' so the caller can self-correct on the next turn instead of silently mis-applying a typo.\nREMINDERS for compute-only datasets:\n - Set virtual=true when the dataset's value comes from transformCode rather than from a slot in the parser output. Without virtual=true the dataset still tries to read channels[index-1] and ends up empty.\n - `index` is the 1-based parser-output slot (0 = unassigned). Patchable via this call; bulk-renumbering 40 datasets is best done through project.batch to avoid round-trip overhead.\n - Set transformLanguage explicitly (0=JavaScript, 1=Lua, -1=inherit from source). Mismatched language vs code = silent compile failure. Lua is the recommended default; it's faster on the hotpath.",
4328
4328
"name": "project.dataset.update",
4329
4329
"properties": {
4330
+
"alarmDeadband": {
4331
+
"description": "Hysteresis in the dataset's units: the active band holds until the value leaves it by this much, so a reading hovering on a threshold does not re-alarm on every frame (0 = none)",
4332
+
"type": "number"
4333
+
},
4330
4334
"alias": {
4331
4335
"description": "Stable name for getDataset-style script/API lookups; must be unique (optional)",
0 commit comments