readerboard 0.4.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {readerboard-0.4.0/readerboard.egg-info → readerboard-0.7.0}/PKG-INFO +216 -21
- {readerboard-0.4.0 → readerboard-0.7.0}/README.md +214 -19
- {readerboard-0.4.0 → readerboard-0.7.0}/pyproject.toml +11 -2
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/__init__.py +1 -1
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/api/app.py +105 -28
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/api/deps.py +4 -4
- readerboard-0.7.0/readerboard/api/errors.py +77 -0
- readerboard-0.7.0/readerboard/api/models.py +439 -0
- readerboard-0.7.0/readerboard/api/routes.py +516 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/config.py +97 -24
- readerboard-0.7.0/readerboard/icons.py +520 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/protocol/constants.py +119 -9
- readerboard-0.7.0/readerboard/protocol/frames.py +604 -0
- readerboard-0.7.0/readerboard/protocol/markup.py +460 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/protocol/replies.py +87 -8
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/protocol/tokens.py +6 -5
- readerboard-0.7.0/readerboard/services/alerts.py +647 -0
- readerboard-0.7.0/readerboard/services/registry.py +1834 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/sign/controller.py +75 -21
- readerboard-0.7.0/readerboard/sign/layout.py +216 -0
- readerboard-0.7.0/readerboard/sign/pool.py +165 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/sign/state.py +145 -9
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/transport/base.py +1 -1
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/transport/serial_link.py +21 -4
- {readerboard-0.4.0 → readerboard-0.7.0/readerboard.egg-info}/PKG-INFO +216 -21
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard.egg-info/SOURCES.txt +8 -1
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard.egg-info/requires.txt +1 -1
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_alerts.py +52 -1
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_api.py +572 -50
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_component_names.py +7 -0
- readerboard-0.7.0/tests/test_config.py +107 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_constant_values.py +127 -11
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_controller.py +110 -13
- readerboard-0.7.0/tests/test_frames.py +447 -0
- readerboard-0.7.0/tests/test_icons.py +141 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_markup.py +205 -1
- readerboard-0.7.0/tests/test_pictures.py +1671 -0
- readerboard-0.7.0/tests/test_pool.py +192 -0
- readerboard-0.7.0/tests/test_registry.py +1000 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_run_against_a_sign.py +102 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_run_with_simulator.py +47 -3
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_state.py +110 -20
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_transport.py +70 -1
- readerboard-0.7.0/tests/test_variables.py +618 -0
- readerboard-0.4.0/readerboard/api/errors.py +0 -40
- readerboard-0.4.0/readerboard/api/models.py +0 -240
- readerboard-0.4.0/readerboard/api/routes.py +0 -289
- readerboard-0.4.0/readerboard/protocol/frames.py +0 -317
- readerboard-0.4.0/readerboard/protocol/markup.py +0 -214
- readerboard-0.4.0/readerboard/services/alerts.py +0 -239
- readerboard-0.4.0/readerboard/services/registry.py +0 -432
- readerboard-0.4.0/readerboard/sign/layout.py +0 -116
- readerboard-0.4.0/tests/test_frames.py +0 -224
- readerboard-0.4.0/tests/test_registry.py +0 -479
- {readerboard-0.4.0 → readerboard-0.7.0}/LICENSE +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/__main__.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/api/__init__.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/logging_setup.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/names.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/protocol/__init__.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/py.typed +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/services/__init__.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/services/clock.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/services/commands.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/sign/__init__.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/transport/__init__.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard/transport/fake.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard.egg-info/dependency_links.txt +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard.egg-info/entry_points.txt +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/readerboard.egg-info/top_level.txt +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/setup.cfg +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_clock.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_launch_configurations.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_logging_setup.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_open_docs.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_replies.py +0 -0
- {readerboard-0.4.0 → readerboard-0.7.0}/tests/test_tool_icons.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: readerboard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: An HTTP service for BetaBrite and Alpha protocol LED signs: several sources share one sign, with alerts, expiring messages and clock sync
|
|
5
5
|
Author: mjaksn
|
|
6
6
|
License-Expression: MIT
|
|
@@ -28,7 +28,7 @@ Requires-Python: >=3.11
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
License-File: LICENSE
|
|
30
30
|
Requires-Dist: fastapi<0.142,>=0.141.1
|
|
31
|
-
Requires-Dist: uvicorn<0.
|
|
31
|
+
Requires-Dist: uvicorn<0.54,>=0.52.1
|
|
32
32
|
Requires-Dist: pydantic<3,>=2.13.4
|
|
33
33
|
Requires-Dist: pydantic-settings<3,>=2.15.0
|
|
34
34
|
Requires-Dist: pyserial<4,>=3.5
|
|
@@ -55,8 +55,12 @@ An HTTP service that drives a BetaBrite Classic sign, either through a serial ca
|
|
|
55
55
|
through an Ethernet to RS-232 adapter.
|
|
56
56
|
|
|
57
57
|
Several sources can share the sign at once. Each registers a named **slot**, and the sign
|
|
58
|
-
rotates through the
|
|
59
|
-
|
|
58
|
+
rotates through the slots that are showing by itself. A slot can be hidden without being
|
|
59
|
+
given up, so a message can be taken off the display and put back without being sent again,
|
|
60
|
+
unless it was the last one showing. A **variable** is a value that messages call by name,
|
|
61
|
+
such as a temperature, and changing it does not blank the sign or restart the message
|
|
62
|
+
showing it. An **alert** takes the whole display over until it is released, after which
|
|
63
|
+
the rotation resumes.
|
|
60
64
|
|
|
61
65
|
## What it does
|
|
62
66
|
|
|
@@ -64,7 +68,13 @@ until it is released, after which the rotation resumes.
|
|
|
64
68
|
automation owns `doorbell`, without either knowing about the other.
|
|
65
69
|
- **The sign does the rotating.** Each message lives in its own sign file and the sign
|
|
66
70
|
cycles them on its own, so rotation costs no serial traffic at all.
|
|
71
|
+
- **Live values without a blink.** A message such as `Outside <var:temp><degree>F` calls
|
|
72
|
+
the variable `temp`, and a new value is one small write that the sign shows the next
|
|
73
|
+
time it draws the message, with no blank and no restart. One variable can appear in
|
|
74
|
+
any number of messages, and a value that stops arriving can be made to go stale.
|
|
67
75
|
- **Alerts.** Take the display over, optionally with a deadline, then hand it back.
|
|
76
|
+
A caller that must not overwrite somebody else's alert can ask to be refused
|
|
77
|
+
instead.
|
|
68
78
|
- **It keeps the sign's clock right**, at startup, hourly, and whenever the link comes
|
|
69
79
|
back. That last trigger is the one that matters: a sign returning from a power cut
|
|
70
80
|
does so at no particular minute. The sign is set one minute fast on purpose: the
|
|
@@ -76,8 +86,10 @@ until it is released, after which the rotation resumes.
|
|
|
76
86
|
flicker.
|
|
77
87
|
- **It survives restarts and outages.** The registered messages are persisted and
|
|
78
88
|
pushed to the sign again whenever the link returns, so a restart or a power cut leaves
|
|
79
|
-
the rotation intact. A write that arrives while the sign is
|
|
80
|
-
a 503 rather than silently held, so the caller learns it did
|
|
89
|
+
the rotation intact. A message or a variable write that arrives while the sign is
|
|
90
|
+
unreachable is refused with a 503 rather than silently held, so the caller learns it did
|
|
91
|
+
not land. Deleting or hiding a message, or deleting a variable, is accepted and carried
|
|
92
|
+
out when the link returns.
|
|
81
93
|
- **Errors are errors.** A dead serial link is a 503 and a message the sign cannot
|
|
82
94
|
render is a 400, each with the reason in the body. Nothing here reports a failure
|
|
83
95
|
under a 200.
|
|
@@ -139,8 +151,8 @@ python scripts/run_against_a_sign.py --serial-url socket://192.168.2.51:4001
|
|
|
139
151
|
|
|
140
152
|
That starts the service and the client together, with no simulator. The service
|
|
141
153
|
comes up on <http://127.0.0.1:5001> with `/docs` beside it, the client comes up
|
|
142
|
-
pointed at that address
|
|
143
|
-
the same window. `--no-client` leaves the client out. Ctrl+C stops everything,
|
|
154
|
+
pointed at that address with the API key already in its box, and the key is
|
|
155
|
+
printed in the same window for anything else that needs it. `--no-client` leaves the client out. Ctrl+C stops everything,
|
|
144
156
|
and closing the client leaves the service running.
|
|
145
157
|
|
|
146
158
|
Both editors carry it as a launch configuration named "readerboard against the
|
|
@@ -213,6 +225,19 @@ again after pulling a new version: your config file and key are left alone.
|
|
|
213
225
|
your registered messages, so reinstalling puts the sign back as it was. Add `--purge` to
|
|
214
226
|
remove those too.
|
|
215
227
|
|
|
228
|
+
The unit restarts the service whenever it stops, and gives up after ten failed starts in
|
|
229
|
+
five minutes. Almost nothing here fails permanently, which is what makes the exceptions
|
|
230
|
+
worth stopping for: a memory pool too big for the sign fails identically every time, and
|
|
231
|
+
each attempt puts a read on the wire that stalls a scrolling message. `systemctl status
|
|
232
|
+
readerboard` says which failure it was. Once the configuration is fixed, clearing the
|
|
233
|
+
give-up and starting it again are two commands, because `reset-failed` clears the counter
|
|
234
|
+
and leaves the unit stopped:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
sudo systemctl reset-failed readerboard
|
|
238
|
+
sudo systemctl start readerboard
|
|
239
|
+
```
|
|
240
|
+
|
|
216
241
|
### With Docker
|
|
217
242
|
|
|
218
243
|
The image is published to both registries on every release, for `linux/amd64`,
|
|
@@ -257,7 +282,7 @@ service's other reads and `GET /health` do not. In the Swagger UI at `/docs`, th
|
|
|
257
282
|
Register a message:
|
|
258
283
|
|
|
259
284
|
```
|
|
260
|
-
curl -X PUT http://localhost:5001/
|
|
285
|
+
curl -X PUT http://localhost:5001/slots/temperature \
|
|
261
286
|
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
262
287
|
-d '{"message": "<green>18.4<degree> <red><time>", "display_mode": "HOLD"}'
|
|
263
288
|
```
|
|
@@ -265,11 +290,70 @@ curl -X PUT http://localhost:5001/messages/temperature \
|
|
|
265
290
|
Register a second one and the sign rotates between them:
|
|
266
291
|
|
|
267
292
|
```
|
|
268
|
-
curl -X PUT http://localhost:5001/
|
|
293
|
+
curl -X PUT http://localhost:5001/slots/doorbell \
|
|
269
294
|
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
270
295
|
-d '{"message": "<amber>Someone at the door", "ttl_seconds": 300}'
|
|
271
296
|
```
|
|
272
297
|
|
|
298
|
+
Take a message off the display without giving up its slot, and put it back later:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
curl -X PUT http://localhost:5001/slots/doorbell/active \
|
|
302
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
303
|
+
-d '{"active": false}'
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
A hidden message keeps its slot, its place in the order and its text, so showing it again
|
|
307
|
+
takes `{"active": true}` and no copy of what it said. Hiding or showing one is a single run
|
|
308
|
+
sequence write. That does disturb the display briefly, but far less than rewriting a
|
|
309
|
+
message does: enough less that it is easy to miss unless you are watching for it on a
|
|
310
|
+
static screen. The exception is the last message showing: its file is emptied as it goes,
|
|
311
|
+
because a sign whose run sequence names nothing freezes on what it was drawing, so
|
|
312
|
+
putting that one back costs the text as well as the sequence.
|
|
313
|
+
|
|
314
|
+
`active` is a field on the message endpoint too, where leaving it out is the point: a
|
|
315
|
+
source re-sending the same content every few minutes says nothing about it and so cannot
|
|
316
|
+
switch back on something that was deliberately hidden. Sending it moves the message.
|
|
317
|
+
|
|
318
|
+
A `ttl_seconds` can hide a message instead of deleting it, which suits anything that comes
|
|
319
|
+
back later, such as a bin day or a school notice:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
curl -X PUT http://localhost:5001/slots/bins \
|
|
323
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
324
|
+
-d '{"message": "<green>BINS OUT TONIGHT", "ttl_seconds": 43200,
|
|
325
|
+
"delete_on_expiry": false}'
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Put those together and a recurring notification is one call per event, sent in the same
|
|
329
|
+
shape every time. This shows the alarm's state for a minute whenever it changes, then takes
|
|
330
|
+
it off the rotation until the next one:
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
curl -X PUT http://localhost:5001/slots/alarm \
|
|
334
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
335
|
+
-d '{"message": "<red>ALARM NOW <var:arm_state>", "ttl_seconds": 60,
|
|
336
|
+
"delete_on_expiry": false, "active": true}'
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
The deadline clears itself as it hides the message, so each event gets a fresh minute
|
|
340
|
+
rather than the message vanishing again on a deadline the last one left behind.
|
|
341
|
+
|
|
342
|
+
Show a live value. Create the variable first, then a message that calls it:
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
curl -X PUT http://localhost:5001/variables/temp \
|
|
346
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
347
|
+
-d '{"value": "72", "ttl_seconds": 1800, "stale_value": "--"}'
|
|
348
|
+
|
|
349
|
+
curl -X PUT http://localhost:5001/slots/weather \
|
|
350
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
351
|
+
-d '{"message": "Outside <var:temp><degree>F", "display_mode": "ROTATE"}'
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
From then on only the variable needs writing, and the message picks each new value up
|
|
355
|
+
on its next pass across the sign.
|
|
356
|
+
|
|
273
357
|
Take the sign over for thirty seconds:
|
|
274
358
|
|
|
275
359
|
```
|
|
@@ -296,8 +380,8 @@ Silence it with `{"command": "SPEAKER", "parameter": "OFF"}`, and turn it back o
|
|
|
296
380
|
lives on the sign and survives a restart, so it is also the first thing to check if
|
|
297
381
|
`SOUND` ever seems to do nothing.
|
|
298
382
|
|
|
299
|
-
The full API is at `/docs`. Every markup token, display mode and
|
|
300
|
-
command is listed by the `/enumerations` reads there, which answer at
|
|
383
|
+
The full API is at `/docs`. Every markup token, value token, display mode and
|
|
384
|
+
control command is listed by the `/enumerations` reads there, which answer at
|
|
301
385
|
request time rather than being frozen into the description.
|
|
302
386
|
|
|
303
387
|
### Writing messages
|
|
@@ -311,6 +395,93 @@ displays correctly. A character the sign cannot render is rejected with a 400, a
|
|
|
311
395
|
unknown token: a write is told what the sign would have made of it rather than being
|
|
312
396
|
shown something it did not ask for.
|
|
313
397
|
|
|
398
|
+
### Live values: variables
|
|
399
|
+
|
|
400
|
+
A variable lives in a small file of its own on the sign, and a message or an alert
|
|
401
|
+
calls it with `<var:name>`. Writing a new value rewrites that file and nothing else,
|
|
402
|
+
which the sign takes without blanking, so a message showing a temperature or a count can
|
|
403
|
+
change every minute and never restart. In a scrolling mode the new value appears on the
|
|
404
|
+
message's next pass. An alert carrying a live wind speed works the same way, and keeps
|
|
405
|
+
the sign while the number changes.
|
|
406
|
+
|
|
407
|
+
A few things are worth knowing, all of them measured on the sign:
|
|
408
|
+
|
|
409
|
+
- **Create the variable before a message calls it.** A message or an alert naming a
|
|
410
|
+
variable that does not exist is refused with a 400, and a variable that a message or
|
|
411
|
+
the alert still calls cannot be deleted: that is a 409 naming what calls it.
|
|
412
|
+
- **Formatting in a value carries on after it.** A value of `<red>DOWN` turns the rest
|
|
413
|
+
of the message red as well, and so does a character set or a speed. If the text after
|
|
414
|
+
the call matters, set it again in the message after the call.
|
|
415
|
+
- **A value takes the message markup, less two tokens.** `<week_day>` draws as a literal
|
|
416
|
+
`9` from inside a variable, and a variable cannot call another. `GET
|
|
417
|
+
/enumerations/value-tokens` lists what is allowed.
|
|
418
|
+
- **Keep a changing number from pushing the text around it.** With the sign's usual
|
|
419
|
+
proportional spacing, `11` and `88` are different widths and the text after them moves.
|
|
420
|
+
Put `<fixed_width>` in the message before the call and send values of the same length.
|
|
421
|
+
Fixed width also left-justifies the line.
|
|
422
|
+
- **A value that stops arriving can go stale.** Give a `ttl_seconds` and a `stale_value`
|
|
423
|
+
such as `--`, and once the time passes without a new value the sign shows the stale
|
|
424
|
+
value instead. The variable itself stays, since messages call it.
|
|
425
|
+
- **A value that does not fit is refused.** Each holds `variable_capacity` bytes after
|
|
426
|
+
rendering, 32 by default. The sign does not cut an overlong value short, it empties it,
|
|
427
|
+
so the service refuses one rather than send it.
|
|
428
|
+
|
|
429
|
+
A Home Assistant `rest_command` that keeps a sensor on the sign:
|
|
430
|
+
|
|
431
|
+
```yaml
|
|
432
|
+
rest_command:
|
|
433
|
+
sign_temperature:
|
|
434
|
+
url: http://readerboard.local:5001/variables/temp
|
|
435
|
+
method: put
|
|
436
|
+
headers:
|
|
437
|
+
X-API-Key: !secret readerboard_key
|
|
438
|
+
content_type: application/json
|
|
439
|
+
payload: '{"value": "{{ value }}", "ttl_seconds": 1800, "stale_value": "--"}'
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Call it from an automation triggered by the sensor, with the reading as `value`:
|
|
443
|
+
|
|
444
|
+
```yaml
|
|
445
|
+
actions:
|
|
446
|
+
- action: rest_command.sign_temperature
|
|
447
|
+
data:
|
|
448
|
+
value: "{{ states('sensor.outside_temperature') | round(0) | int }}"
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Icons
|
|
452
|
+
|
|
453
|
+
A message can draw one of 148 built-in bitmaps where a tag sits. `<icon:sun> FINE`
|
|
454
|
+
puts a sun in front of the word, and `<icon:lock:red>` retints an icon that is drawn
|
|
455
|
+
in a single ink; the colour words are the colour tokens' own, down to `dimred` and
|
|
456
|
+
`dimgreen`, so a message that can say `<red>` needs no second spelling. An icon
|
|
457
|
+
drawn in its own colours, such as the sun, takes no tint and asking for one is
|
|
458
|
+
refused rather than ignored. `GET /enumerations/icons` lists every icon with its
|
|
459
|
+
group, its width and whether it takes a tint.
|
|
460
|
+
|
|
461
|
+
Icons are off until `picture_count` is set, because each one on the sign needs a
|
|
462
|
+
picture file of its own and allocating those reallocates the sign's memory, which
|
|
463
|
+
erases every message on it. Set it once, alongside the other pool settings, and
|
|
464
|
+
16 is a comfortable number.
|
|
465
|
+
|
|
466
|
+
Four things are worth knowing, and the first is the one that decides how to use them:
|
|
467
|
+
|
|
468
|
+
- **An icon is not a live value.** Every write to a picture file blanks the display
|
|
469
|
+
and restarts a scrolling message. A weather slot stepping from sun to cloud to rain
|
|
470
|
+
pays that each time it lands on an icon the sign is not already holding. Something
|
|
471
|
+
that changes every minute belongs in a variable, which costs no blank at all.
|
|
472
|
+
- **The pool is smaller than the library, and that is the design.** A picture file is
|
|
473
|
+
claimed by whichever icon a message calls, and kept after its last caller goes, so a
|
|
474
|
+
source alternating between two icons costs nothing after the first write. When every
|
|
475
|
+
file is holding an icon something still calls and a new one is asked for, the write
|
|
476
|
+
is refused with a 409. `GET /health` reports pictures used against pictures total,
|
|
477
|
+
and a full pool is the resting state rather than a warning.
|
|
478
|
+
- **A tint makes a second picture.** `<icon:check:green>` and `<icon:check:red>` are
|
|
479
|
+
two bitmaps and take two files.
|
|
480
|
+
- **An icon can be parted from its word.** A line too wide for the display breaks onto
|
|
481
|
+
a second page in HOLD, and the last word can arrive there without the icon labelling
|
|
482
|
+
it. The service cannot warn about this: it would have to know the width of the sign's
|
|
483
|
+
proportional font.
|
|
484
|
+
|
|
314
485
|
### Recovering a sign that has stopped responding
|
|
315
486
|
|
|
316
487
|
A sign mounted out of reach can wedge: a stray bit corrupts what its decoder is
|
|
@@ -339,10 +510,10 @@ from the service's own record, so the display still comes back to what it was.
|
|
|
339
510
|
curl -X POST http://localhost:5001/sign/reboot -H 'X-API-Key: YOUR-KEY'
|
|
340
511
|
```
|
|
341
512
|
|
|
342
|
-
Reach for it only when a soft reset was not enough. The sign is blank for
|
|
343
|
-
|
|
344
|
-
that without resetting anything. The client
|
|
345
|
-
confirmation for the same reason.
|
|
513
|
+
Reach for it only when a soft reset was not enough. The sign is blank for twelve seconds
|
|
514
|
+
or more while it resets, longer with a lot of messages to put back. Neither is a way to
|
|
515
|
+
clear messages: `DELETE /slots` does that without resetting anything. The client
|
|
516
|
+
fronts the reboot with a warning-coloured confirmation for the same reason.
|
|
346
517
|
|
|
347
518
|
## Configuration
|
|
348
519
|
|
|
@@ -361,9 +532,32 @@ telnet serial protocol, `/dev/ttyUSB0` or `COM3` for a cable plugged straight in
|
|
|
361
532
|
and the port, and pyserial's answer to one that has a slash names neither the setting
|
|
362
533
|
nor the value.
|
|
363
534
|
|
|
364
|
-
|
|
365
|
-
on it**: `slot_count
|
|
366
|
-
the log, but they are not
|
|
535
|
+
Five settings reallocate the sign's memory when changed, and **that erases every message
|
|
536
|
+
on it**: `slot_count`, `slot_capacity`, `variable_count`, `variable_capacity` and
|
|
537
|
+
`picture_count`. The service will do it, and say so loudly in the log, but they are not
|
|
538
|
+
settings to fiddle with. With `variable_count` at 0, `variable_capacity` allocates
|
|
539
|
+
nothing, so changing it alone reallocates nothing either. `picture_count` starts at 0,
|
|
540
|
+
which switches icons off, so turning them on is one deliberate erase.
|
|
541
|
+
|
|
542
|
+
All five come out of one memory pool, which a BetaBrite Classic reported as 5482 bytes,
|
|
543
|
+
and each file costs thirteen bytes beyond its own size. The defaults take 2518 of that,
|
|
544
|
+
and each icon takes 69 on top, so the defaults with sixteen icons take 3622.
|
|
545
|
+
|
|
546
|
+
That 5482 is a ceiling, and it is checked in two places that do different jobs. A
|
|
547
|
+
configuration bigger than it is refused when the settings are read, on any machine,
|
|
548
|
+
whether or not a sign is attached; that is the ceiling, and no sign can raise it. Then,
|
|
549
|
+
on a start that is about to reallocate the sign's memory and only then, the service asks
|
|
550
|
+
the sign for its own figure, and a sign reporting less than 5482 is believed. So the
|
|
551
|
+
second check can lower the limit and never raise it. A sign with a bigger pool than this
|
|
552
|
+
hardware's would need `ASSUMED_SIGN_MEMORY_POOL` in `readerboard/sign/pool.py` raised
|
|
553
|
+
before it could use the extra.
|
|
554
|
+
|
|
555
|
+
A sign that answers and does not have the room stops the service starting, with a message
|
|
556
|
+
naming what was configured, what it needs and what there is; that is the one failure that
|
|
557
|
+
does stop it, because the alternative is erasing every message on the sign to write a pool
|
|
558
|
+
that could never work. A sign that says nothing does not stop anything. `POST /sign/reboot`
|
|
559
|
+
asks the same question before it clears the sign, and answers 409 rather than erasing it,
|
|
560
|
+
except when the sign is too wedged to answer, which is the case that endpoint exists for.
|
|
367
561
|
|
|
368
562
|
## Security
|
|
369
563
|
|
|
@@ -444,8 +638,9 @@ each is pinned separately so that nothing the service installs ever pulls Qt in.
|
|
|
444
638
|
with `--with-client` the client as well, so the whole loop comes up from one command.
|
|
445
639
|
Both editors carry it as a launch configuration under the same name, "readerboard and
|
|
446
640
|
the sign simulator", in `.vscode/launch.json` and in `.idea/runConfigurations/`, beside
|
|
447
|
-
configurations for running the pieces separately
|
|
448
|
-
"readerboard
|
|
641
|
+
configurations for running the pieces separately, the client among them as
|
|
642
|
+
"readerboard client". Both carry the three way one as "readerboard, the sign simulator
|
|
643
|
+
and the client" as well.
|
|
449
644
|
|
|
450
645
|
`scripts/run_against_a_sign.py` is the other one, for when the sign is real: the
|
|
451
646
|
service and the client, no simulator, and the sign's address passed as an argument so
|
|
@@ -11,8 +11,12 @@ An HTTP service that drives a BetaBrite Classic sign, either through a serial ca
|
|
|
11
11
|
through an Ethernet to RS-232 adapter.
|
|
12
12
|
|
|
13
13
|
Several sources can share the sign at once. Each registers a named **slot**, and the sign
|
|
14
|
-
rotates through the
|
|
15
|
-
|
|
14
|
+
rotates through the slots that are showing by itself. A slot can be hidden without being
|
|
15
|
+
given up, so a message can be taken off the display and put back without being sent again,
|
|
16
|
+
unless it was the last one showing. A **variable** is a value that messages call by name,
|
|
17
|
+
such as a temperature, and changing it does not blank the sign or restart the message
|
|
18
|
+
showing it. An **alert** takes the whole display over until it is released, after which
|
|
19
|
+
the rotation resumes.
|
|
16
20
|
|
|
17
21
|
## What it does
|
|
18
22
|
|
|
@@ -20,7 +24,13 @@ until it is released, after which the rotation resumes.
|
|
|
20
24
|
automation owns `doorbell`, without either knowing about the other.
|
|
21
25
|
- **The sign does the rotating.** Each message lives in its own sign file and the sign
|
|
22
26
|
cycles them on its own, so rotation costs no serial traffic at all.
|
|
27
|
+
- **Live values without a blink.** A message such as `Outside <var:temp><degree>F` calls
|
|
28
|
+
the variable `temp`, and a new value is one small write that the sign shows the next
|
|
29
|
+
time it draws the message, with no blank and no restart. One variable can appear in
|
|
30
|
+
any number of messages, and a value that stops arriving can be made to go stale.
|
|
23
31
|
- **Alerts.** Take the display over, optionally with a deadline, then hand it back.
|
|
32
|
+
A caller that must not overwrite somebody else's alert can ask to be refused
|
|
33
|
+
instead.
|
|
24
34
|
- **It keeps the sign's clock right**, at startup, hourly, and whenever the link comes
|
|
25
35
|
back. That last trigger is the one that matters: a sign returning from a power cut
|
|
26
36
|
does so at no particular minute. The sign is set one minute fast on purpose: the
|
|
@@ -32,8 +42,10 @@ until it is released, after which the rotation resumes.
|
|
|
32
42
|
flicker.
|
|
33
43
|
- **It survives restarts and outages.** The registered messages are persisted and
|
|
34
44
|
pushed to the sign again whenever the link returns, so a restart or a power cut leaves
|
|
35
|
-
the rotation intact. A write that arrives while the sign is
|
|
36
|
-
a 503 rather than silently held, so the caller learns it did
|
|
45
|
+
the rotation intact. A message or a variable write that arrives while the sign is
|
|
46
|
+
unreachable is refused with a 503 rather than silently held, so the caller learns it did
|
|
47
|
+
not land. Deleting or hiding a message, or deleting a variable, is accepted and carried
|
|
48
|
+
out when the link returns.
|
|
37
49
|
- **Errors are errors.** A dead serial link is a 503 and a message the sign cannot
|
|
38
50
|
render is a 400, each with the reason in the body. Nothing here reports a failure
|
|
39
51
|
under a 200.
|
|
@@ -95,8 +107,8 @@ python scripts/run_against_a_sign.py --serial-url socket://192.168.2.51:4001
|
|
|
95
107
|
|
|
96
108
|
That starts the service and the client together, with no simulator. The service
|
|
97
109
|
comes up on <http://127.0.0.1:5001> with `/docs` beside it, the client comes up
|
|
98
|
-
pointed at that address
|
|
99
|
-
the same window. `--no-client` leaves the client out. Ctrl+C stops everything,
|
|
110
|
+
pointed at that address with the API key already in its box, and the key is
|
|
111
|
+
printed in the same window for anything else that needs it. `--no-client` leaves the client out. Ctrl+C stops everything,
|
|
100
112
|
and closing the client leaves the service running.
|
|
101
113
|
|
|
102
114
|
Both editors carry it as a launch configuration named "readerboard against the
|
|
@@ -169,6 +181,19 @@ again after pulling a new version: your config file and key are left alone.
|
|
|
169
181
|
your registered messages, so reinstalling puts the sign back as it was. Add `--purge` to
|
|
170
182
|
remove those too.
|
|
171
183
|
|
|
184
|
+
The unit restarts the service whenever it stops, and gives up after ten failed starts in
|
|
185
|
+
five minutes. Almost nothing here fails permanently, which is what makes the exceptions
|
|
186
|
+
worth stopping for: a memory pool too big for the sign fails identically every time, and
|
|
187
|
+
each attempt puts a read on the wire that stalls a scrolling message. `systemctl status
|
|
188
|
+
readerboard` says which failure it was. Once the configuration is fixed, clearing the
|
|
189
|
+
give-up and starting it again are two commands, because `reset-failed` clears the counter
|
|
190
|
+
and leaves the unit stopped:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
sudo systemctl reset-failed readerboard
|
|
194
|
+
sudo systemctl start readerboard
|
|
195
|
+
```
|
|
196
|
+
|
|
172
197
|
### With Docker
|
|
173
198
|
|
|
174
199
|
The image is published to both registries on every release, for `linux/amd64`,
|
|
@@ -213,7 +238,7 @@ service's other reads and `GET /health` do not. In the Swagger UI at `/docs`, th
|
|
|
213
238
|
Register a message:
|
|
214
239
|
|
|
215
240
|
```
|
|
216
|
-
curl -X PUT http://localhost:5001/
|
|
241
|
+
curl -X PUT http://localhost:5001/slots/temperature \
|
|
217
242
|
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
218
243
|
-d '{"message": "<green>18.4<degree> <red><time>", "display_mode": "HOLD"}'
|
|
219
244
|
```
|
|
@@ -221,11 +246,70 @@ curl -X PUT http://localhost:5001/messages/temperature \
|
|
|
221
246
|
Register a second one and the sign rotates between them:
|
|
222
247
|
|
|
223
248
|
```
|
|
224
|
-
curl -X PUT http://localhost:5001/
|
|
249
|
+
curl -X PUT http://localhost:5001/slots/doorbell \
|
|
225
250
|
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
226
251
|
-d '{"message": "<amber>Someone at the door", "ttl_seconds": 300}'
|
|
227
252
|
```
|
|
228
253
|
|
|
254
|
+
Take a message off the display without giving up its slot, and put it back later:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
curl -X PUT http://localhost:5001/slots/doorbell/active \
|
|
258
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
259
|
+
-d '{"active": false}'
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
A hidden message keeps its slot, its place in the order and its text, so showing it again
|
|
263
|
+
takes `{"active": true}` and no copy of what it said. Hiding or showing one is a single run
|
|
264
|
+
sequence write. That does disturb the display briefly, but far less than rewriting a
|
|
265
|
+
message does: enough less that it is easy to miss unless you are watching for it on a
|
|
266
|
+
static screen. The exception is the last message showing: its file is emptied as it goes,
|
|
267
|
+
because a sign whose run sequence names nothing freezes on what it was drawing, so
|
|
268
|
+
putting that one back costs the text as well as the sequence.
|
|
269
|
+
|
|
270
|
+
`active` is a field on the message endpoint too, where leaving it out is the point: a
|
|
271
|
+
source re-sending the same content every few minutes says nothing about it and so cannot
|
|
272
|
+
switch back on something that was deliberately hidden. Sending it moves the message.
|
|
273
|
+
|
|
274
|
+
A `ttl_seconds` can hide a message instead of deleting it, which suits anything that comes
|
|
275
|
+
back later, such as a bin day or a school notice:
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
curl -X PUT http://localhost:5001/slots/bins \
|
|
279
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
280
|
+
-d '{"message": "<green>BINS OUT TONIGHT", "ttl_seconds": 43200,
|
|
281
|
+
"delete_on_expiry": false}'
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Put those together and a recurring notification is one call per event, sent in the same
|
|
285
|
+
shape every time. This shows the alarm's state for a minute whenever it changes, then takes
|
|
286
|
+
it off the rotation until the next one:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
curl -X PUT http://localhost:5001/slots/alarm \
|
|
290
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
291
|
+
-d '{"message": "<red>ALARM NOW <var:arm_state>", "ttl_seconds": 60,
|
|
292
|
+
"delete_on_expiry": false, "active": true}'
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The deadline clears itself as it hides the message, so each event gets a fresh minute
|
|
296
|
+
rather than the message vanishing again on a deadline the last one left behind.
|
|
297
|
+
|
|
298
|
+
Show a live value. Create the variable first, then a message that calls it:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
curl -X PUT http://localhost:5001/variables/temp \
|
|
302
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
303
|
+
-d '{"value": "72", "ttl_seconds": 1800, "stale_value": "--"}'
|
|
304
|
+
|
|
305
|
+
curl -X PUT http://localhost:5001/slots/weather \
|
|
306
|
+
-H 'X-API-Key: YOUR-KEY' -H 'Content-Type: application/json' \
|
|
307
|
+
-d '{"message": "Outside <var:temp><degree>F", "display_mode": "ROTATE"}'
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
From then on only the variable needs writing, and the message picks each new value up
|
|
311
|
+
on its next pass across the sign.
|
|
312
|
+
|
|
229
313
|
Take the sign over for thirty seconds:
|
|
230
314
|
|
|
231
315
|
```
|
|
@@ -252,8 +336,8 @@ Silence it with `{"command": "SPEAKER", "parameter": "OFF"}`, and turn it back o
|
|
|
252
336
|
lives on the sign and survives a restart, so it is also the first thing to check if
|
|
253
337
|
`SOUND` ever seems to do nothing.
|
|
254
338
|
|
|
255
|
-
The full API is at `/docs`. Every markup token, display mode and
|
|
256
|
-
command is listed by the `/enumerations` reads there, which answer at
|
|
339
|
+
The full API is at `/docs`. Every markup token, value token, display mode and
|
|
340
|
+
control command is listed by the `/enumerations` reads there, which answer at
|
|
257
341
|
request time rather than being frozen into the description.
|
|
258
342
|
|
|
259
343
|
### Writing messages
|
|
@@ -267,6 +351,93 @@ displays correctly. A character the sign cannot render is rejected with a 400, a
|
|
|
267
351
|
unknown token: a write is told what the sign would have made of it rather than being
|
|
268
352
|
shown something it did not ask for.
|
|
269
353
|
|
|
354
|
+
### Live values: variables
|
|
355
|
+
|
|
356
|
+
A variable lives in a small file of its own on the sign, and a message or an alert
|
|
357
|
+
calls it with `<var:name>`. Writing a new value rewrites that file and nothing else,
|
|
358
|
+
which the sign takes without blanking, so a message showing a temperature or a count can
|
|
359
|
+
change every minute and never restart. In a scrolling mode the new value appears on the
|
|
360
|
+
message's next pass. An alert carrying a live wind speed works the same way, and keeps
|
|
361
|
+
the sign while the number changes.
|
|
362
|
+
|
|
363
|
+
A few things are worth knowing, all of them measured on the sign:
|
|
364
|
+
|
|
365
|
+
- **Create the variable before a message calls it.** A message or an alert naming a
|
|
366
|
+
variable that does not exist is refused with a 400, and a variable that a message or
|
|
367
|
+
the alert still calls cannot be deleted: that is a 409 naming what calls it.
|
|
368
|
+
- **Formatting in a value carries on after it.** A value of `<red>DOWN` turns the rest
|
|
369
|
+
of the message red as well, and so does a character set or a speed. If the text after
|
|
370
|
+
the call matters, set it again in the message after the call.
|
|
371
|
+
- **A value takes the message markup, less two tokens.** `<week_day>` draws as a literal
|
|
372
|
+
`9` from inside a variable, and a variable cannot call another. `GET
|
|
373
|
+
/enumerations/value-tokens` lists what is allowed.
|
|
374
|
+
- **Keep a changing number from pushing the text around it.** With the sign's usual
|
|
375
|
+
proportional spacing, `11` and `88` are different widths and the text after them moves.
|
|
376
|
+
Put `<fixed_width>` in the message before the call and send values of the same length.
|
|
377
|
+
Fixed width also left-justifies the line.
|
|
378
|
+
- **A value that stops arriving can go stale.** Give a `ttl_seconds` and a `stale_value`
|
|
379
|
+
such as `--`, and once the time passes without a new value the sign shows the stale
|
|
380
|
+
value instead. The variable itself stays, since messages call it.
|
|
381
|
+
- **A value that does not fit is refused.** Each holds `variable_capacity` bytes after
|
|
382
|
+
rendering, 32 by default. The sign does not cut an overlong value short, it empties it,
|
|
383
|
+
so the service refuses one rather than send it.
|
|
384
|
+
|
|
385
|
+
A Home Assistant `rest_command` that keeps a sensor on the sign:
|
|
386
|
+
|
|
387
|
+
```yaml
|
|
388
|
+
rest_command:
|
|
389
|
+
sign_temperature:
|
|
390
|
+
url: http://readerboard.local:5001/variables/temp
|
|
391
|
+
method: put
|
|
392
|
+
headers:
|
|
393
|
+
X-API-Key: !secret readerboard_key
|
|
394
|
+
content_type: application/json
|
|
395
|
+
payload: '{"value": "{{ value }}", "ttl_seconds": 1800, "stale_value": "--"}'
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Call it from an automation triggered by the sensor, with the reading as `value`:
|
|
399
|
+
|
|
400
|
+
```yaml
|
|
401
|
+
actions:
|
|
402
|
+
- action: rest_command.sign_temperature
|
|
403
|
+
data:
|
|
404
|
+
value: "{{ states('sensor.outside_temperature') | round(0) | int }}"
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Icons
|
|
408
|
+
|
|
409
|
+
A message can draw one of 148 built-in bitmaps where a tag sits. `<icon:sun> FINE`
|
|
410
|
+
puts a sun in front of the word, and `<icon:lock:red>` retints an icon that is drawn
|
|
411
|
+
in a single ink; the colour words are the colour tokens' own, down to `dimred` and
|
|
412
|
+
`dimgreen`, so a message that can say `<red>` needs no second spelling. An icon
|
|
413
|
+
drawn in its own colours, such as the sun, takes no tint and asking for one is
|
|
414
|
+
refused rather than ignored. `GET /enumerations/icons` lists every icon with its
|
|
415
|
+
group, its width and whether it takes a tint.
|
|
416
|
+
|
|
417
|
+
Icons are off until `picture_count` is set, because each one on the sign needs a
|
|
418
|
+
picture file of its own and allocating those reallocates the sign's memory, which
|
|
419
|
+
erases every message on it. Set it once, alongside the other pool settings, and
|
|
420
|
+
16 is a comfortable number.
|
|
421
|
+
|
|
422
|
+
Four things are worth knowing, and the first is the one that decides how to use them:
|
|
423
|
+
|
|
424
|
+
- **An icon is not a live value.** Every write to a picture file blanks the display
|
|
425
|
+
and restarts a scrolling message. A weather slot stepping from sun to cloud to rain
|
|
426
|
+
pays that each time it lands on an icon the sign is not already holding. Something
|
|
427
|
+
that changes every minute belongs in a variable, which costs no blank at all.
|
|
428
|
+
- **The pool is smaller than the library, and that is the design.** A picture file is
|
|
429
|
+
claimed by whichever icon a message calls, and kept after its last caller goes, so a
|
|
430
|
+
source alternating between two icons costs nothing after the first write. When every
|
|
431
|
+
file is holding an icon something still calls and a new one is asked for, the write
|
|
432
|
+
is refused with a 409. `GET /health` reports pictures used against pictures total,
|
|
433
|
+
and a full pool is the resting state rather than a warning.
|
|
434
|
+
- **A tint makes a second picture.** `<icon:check:green>` and `<icon:check:red>` are
|
|
435
|
+
two bitmaps and take two files.
|
|
436
|
+
- **An icon can be parted from its word.** A line too wide for the display breaks onto
|
|
437
|
+
a second page in HOLD, and the last word can arrive there without the icon labelling
|
|
438
|
+
it. The service cannot warn about this: it would have to know the width of the sign's
|
|
439
|
+
proportional font.
|
|
440
|
+
|
|
270
441
|
### Recovering a sign that has stopped responding
|
|
271
442
|
|
|
272
443
|
A sign mounted out of reach can wedge: a stray bit corrupts what its decoder is
|
|
@@ -295,10 +466,10 @@ from the service's own record, so the display still comes back to what it was.
|
|
|
295
466
|
curl -X POST http://localhost:5001/sign/reboot -H 'X-API-Key: YOUR-KEY'
|
|
296
467
|
```
|
|
297
468
|
|
|
298
|
-
Reach for it only when a soft reset was not enough. The sign is blank for
|
|
299
|
-
|
|
300
|
-
that without resetting anything. The client
|
|
301
|
-
confirmation for the same reason.
|
|
469
|
+
Reach for it only when a soft reset was not enough. The sign is blank for twelve seconds
|
|
470
|
+
or more while it resets, longer with a lot of messages to put back. Neither is a way to
|
|
471
|
+
clear messages: `DELETE /slots` does that without resetting anything. The client
|
|
472
|
+
fronts the reboot with a warning-coloured confirmation for the same reason.
|
|
302
473
|
|
|
303
474
|
## Configuration
|
|
304
475
|
|
|
@@ -317,9 +488,32 @@ telnet serial protocol, `/dev/ttyUSB0` or `COM3` for a cable plugged straight in
|
|
|
317
488
|
and the port, and pyserial's answer to one that has a slash names neither the setting
|
|
318
489
|
nor the value.
|
|
319
490
|
|
|
320
|
-
|
|
321
|
-
on it**: `slot_count
|
|
322
|
-
the log, but they are not
|
|
491
|
+
Five settings reallocate the sign's memory when changed, and **that erases every message
|
|
492
|
+
on it**: `slot_count`, `slot_capacity`, `variable_count`, `variable_capacity` and
|
|
493
|
+
`picture_count`. The service will do it, and say so loudly in the log, but they are not
|
|
494
|
+
settings to fiddle with. With `variable_count` at 0, `variable_capacity` allocates
|
|
495
|
+
nothing, so changing it alone reallocates nothing either. `picture_count` starts at 0,
|
|
496
|
+
which switches icons off, so turning them on is one deliberate erase.
|
|
497
|
+
|
|
498
|
+
All five come out of one memory pool, which a BetaBrite Classic reported as 5482 bytes,
|
|
499
|
+
and each file costs thirteen bytes beyond its own size. The defaults take 2518 of that,
|
|
500
|
+
and each icon takes 69 on top, so the defaults with sixteen icons take 3622.
|
|
501
|
+
|
|
502
|
+
That 5482 is a ceiling, and it is checked in two places that do different jobs. A
|
|
503
|
+
configuration bigger than it is refused when the settings are read, on any machine,
|
|
504
|
+
whether or not a sign is attached; that is the ceiling, and no sign can raise it. Then,
|
|
505
|
+
on a start that is about to reallocate the sign's memory and only then, the service asks
|
|
506
|
+
the sign for its own figure, and a sign reporting less than 5482 is believed. So the
|
|
507
|
+
second check can lower the limit and never raise it. A sign with a bigger pool than this
|
|
508
|
+
hardware's would need `ASSUMED_SIGN_MEMORY_POOL` in `readerboard/sign/pool.py` raised
|
|
509
|
+
before it could use the extra.
|
|
510
|
+
|
|
511
|
+
A sign that answers and does not have the room stops the service starting, with a message
|
|
512
|
+
naming what was configured, what it needs and what there is; that is the one failure that
|
|
513
|
+
does stop it, because the alternative is erasing every message on the sign to write a pool
|
|
514
|
+
that could never work. A sign that says nothing does not stop anything. `POST /sign/reboot`
|
|
515
|
+
asks the same question before it clears the sign, and answers 409 rather than erasing it,
|
|
516
|
+
except when the sign is too wedged to answer, which is the case that endpoint exists for.
|
|
323
517
|
|
|
324
518
|
## Security
|
|
325
519
|
|
|
@@ -400,8 +594,9 @@ each is pinned separately so that nothing the service installs ever pulls Qt in.
|
|
|
400
594
|
with `--with-client` the client as well, so the whole loop comes up from one command.
|
|
401
595
|
Both editors carry it as a launch configuration under the same name, "readerboard and
|
|
402
596
|
the sign simulator", in `.vscode/launch.json` and in `.idea/runConfigurations/`, beside
|
|
403
|
-
configurations for running the pieces separately
|
|
404
|
-
"readerboard
|
|
597
|
+
configurations for running the pieces separately, the client among them as
|
|
598
|
+
"readerboard client". Both carry the three way one as "readerboard, the sign simulator
|
|
599
|
+
and the client" as well.
|
|
405
600
|
|
|
406
601
|
`scripts/run_against_a_sign.py` is the other one, for when the sign is real: the
|
|
407
602
|
service and the client, no simulator, and the sign's address passed as an argument so
|