frugal-iot-server 0.3.7 → 0.3.9

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.
@@ -9,6 +9,11 @@
9
9
  # name Capitalized English (and add translation below in 'languages')
10
10
  # max For gauges, slider
11
11
  # min For gauges, slider
12
+ # width Total characters of the rendered number, including any sign and the decimal point and
13
+ # excluding units - the same meaning the node gives it. Decimals are derived, not declared:
14
+ # intWidth = max(len(int(min)), len(int(max))) including a '-', then decimals = width-intWidth-1.
15
+ # Deriving from min/max rather than from the value keeps the decimal count steady as the value
16
+ # moves. Only float and exponential topics need it; an int has no decimals to decide.
12
17
  # color can be a name or a #RRGGBB
13
18
  # display One of bar,gauge,text,slider,inputbox
14
19
  # slot if this topic fits into a specific slot in the enclosing module (currently just frugal_iot module)
@@ -21,7 +26,8 @@
21
26
  # no setting at all, any float, int or bool with "rw: r" is recorded, which is easy to be
22
27
  # surprised by. scripts/check-schema.js warns about a topic that does not say.
23
28
  # duplicates when to bother recording a reading: significantvalue is how much it has to have moved
24
- # since the last one recorded, significantdate how long (in milliseconds) since that one, and a
29
+ # since the last one recorded - either an absolute amount e.g. 0.5, or as a percentage of the last
30
+ # reading e.g. "2%" - significantdate how long (in milliseconds) since that one, and a
25
31
  # reading that meets neither is dropped. Every topic with "log: true" wants a rule here - without
26
32
  # one, every reading is written to the SD card as it arrives, which wears the card out.
27
33
  # Note battery gets special cased
@@ -30,10 +36,12 @@ altitude:
30
36
  leaf: altitude
31
37
  name: Altitude
32
38
  type: float
39
+ units: m
33
40
  display: text
34
41
  min: -500
35
42
  max: 9000
36
- color: black
43
+ width: 6
44
+ color: "#000000"
37
45
  log: true
38
46
  duplicates:
39
47
  significantdate: 900000
@@ -59,7 +67,7 @@ aqi:
59
67
  display: bar
60
68
  min: 0
61
69
  max: 5
62
- color: purple
70
+ color: "#800080"
63
71
  rw: r
64
72
  graphable: true
65
73
  log: true
@@ -73,7 +81,7 @@ aqi500:
73
81
  display: bar
74
82
  min: 0
75
83
  max: 500
76
- color: brown
84
+ color: "#a52a2a"
77
85
  rw: r
78
86
  graphable: true
79
87
  log: true
@@ -87,7 +95,7 @@ battery:
87
95
  display: text
88
96
  min: 3000
89
97
  max: 5000
90
- color: green
98
+ color: "#008000"
91
99
  rw: r
92
100
  graphable: true
93
101
  log: true
@@ -111,7 +119,8 @@ bus:
111
119
  display: text
112
120
  min: 0
113
121
  max: 32
114
- color: green
122
+ width: 5
123
+ color: "#008000"
115
124
  rw: r
116
125
  log: true
117
126
  units: V
@@ -125,7 +134,7 @@ button:
125
134
  type: bool
126
135
  log: true
127
136
  display: toggle
128
- color: black
137
+ color: "#000000"
129
138
  rw: r
130
139
  # A press changes the value, so it is still recorded - this only drops the repeats in between
131
140
  duplicates:
@@ -142,10 +151,11 @@ controlfloat:
142
151
  #[leaf, name] should be overridden
143
152
  min: 0
144
153
  max: 100
154
+ width: 5
145
155
  type: float
146
156
  log: false
147
157
  display: text
148
- color: black
158
+ color: "#000000"
149
159
  wireable: true
150
160
  rw: w
151
161
  retain: true
@@ -156,7 +166,7 @@ controlint:
156
166
  type: int
157
167
  log: false
158
168
  display: text
159
- color: black
169
+ color: "#000000"
160
170
  wireable: true
161
171
  rw: w
162
172
  retain: true
@@ -167,7 +177,7 @@ controltext:
167
177
  type: text
168
178
  log: false
169
179
  display: text
170
- color: black
180
+ color: "#000000"
171
181
  wireable: true
172
182
  rw: w
173
183
  retain: true
@@ -176,7 +186,7 @@ controlintoggle:
176
186
  type: bool
177
187
  log: false
178
188
  display: toggle
179
- color: black
189
+ color: "#000000"
180
190
  rw: w
181
191
  wireable: false
182
192
  controlouttoggle:
@@ -184,7 +194,7 @@ controlouttoggle:
184
194
  type: bool
185
195
  log: true
186
196
  display: toggle
187
- color: black
197
+ color: "#000000"
188
198
  rw: r
189
199
  wireable: true
190
200
  # Read-only bool, so logged by default - match the "out" and "on" topics it stands in for
@@ -198,7 +208,8 @@ course:
198
208
  display: text
199
209
  min: 0
200
210
  max: 360
201
- color: black
211
+ width: 5
212
+ color: "#000000"
202
213
  log: true
203
214
  duplicates:
204
215
  significantdate: 900000
@@ -213,7 +224,8 @@ current:
213
224
  display: text
214
225
  min: -3200
215
226
  max: 3200
216
- color: red
227
+ width: 7
228
+ color: "#ff0000"
217
229
  rw: r
218
230
  log: true
219
231
  units: mA
@@ -237,7 +249,8 @@ do:
237
249
  display: bar
238
250
  min: 0
239
251
  max: 20
240
- color: blue
252
+ width: 5
253
+ color: "#0000ff"
241
254
  rw: r
242
255
  log: true
243
256
  units: mg/L
@@ -252,7 +265,7 @@ eco2:
252
265
  display: bar
253
266
  min: 300
254
267
  max: 900
255
- color: brown
268
+ color: "#a52a2a"
256
269
  log: true
257
270
  units: ppm
258
271
  duplicates:
@@ -267,7 +280,8 @@ gas:
267
280
  display: text
268
281
  min: 0
269
282
  max: 500
270
- color: green
283
+ width: 5
284
+ color: "#008000"
271
285
  rw: r
272
286
  log: true
273
287
  units: kOhm
@@ -282,7 +296,8 @@ hdop:
282
296
  display: text
283
297
  min: 0
284
298
  max: 50
285
- color: black
299
+ width: 5
300
+ color: "#000000"
286
301
  log: false
287
302
  duplicates:
288
303
  significantdate: 900000
@@ -296,7 +311,8 @@ humidity:
296
311
  display: bar
297
312
  min: 0
298
313
  max: 100
299
- color: blue
314
+ width: 5
315
+ color: "#0000ff"
300
316
  log: true
301
317
  duplicates:
302
318
  significantdate: 900000
@@ -325,7 +341,7 @@ language_code:
325
341
  type: text
326
342
  log: false
327
343
  display: text
328
- color: black
344
+ color: "#000000"
329
345
  rw: r
330
346
  lastseen:
331
347
  leaf: lastseen
@@ -342,7 +358,8 @@ latitude:
342
358
  display: text
343
359
  min: -90
344
360
  max: +90
345
- color: black
361
+ width: 9
362
+ color: "#000000"
346
363
  log: true
347
364
  duplicates:
348
365
  significantdate: 900000
@@ -357,7 +374,8 @@ load:
357
374
  display: text
358
375
  min: 0
359
376
  max: 32
360
- color: green
377
+ width: 5
378
+ color: "#008000"
361
379
  rw: r
362
380
  log: true
363
381
  units: V
@@ -372,7 +390,8 @@ loadcell:
372
390
  display: text
373
391
  min: 0
374
392
  max: 65000
375
- color: yellow
393
+ width: 7
394
+ color: "#b8860b"
376
395
  log: true
377
396
  duplicates:
378
397
  significantdate: 900000
@@ -387,7 +406,8 @@ longitude:
387
406
  display: text
388
407
  min: -180
389
408
  max: +180
390
- color: black
409
+ width: 10
410
+ color: "#000000"
391
411
  log: true
392
412
  duplicates:
393
413
  significantdate: 900000
@@ -402,12 +422,13 @@ lux:
402
422
  display: bar
403
423
  min: 0
404
424
  max: 65000
405
- color: yellow
425
+ width: 5
426
+ color: "#b8860b"
406
427
  log: true
407
428
  rw: r
408
429
  duplicates:
409
430
  significantdate: 900000
410
- significantvalue: 100
431
+ significantvalue: 5%
411
432
  graphable: true
412
433
  units: lx
413
434
  manual:
@@ -415,7 +436,7 @@ manual:
415
436
  name: Manual
416
437
  type: bool
417
438
  display: toggle
418
- color: black
439
+ color: "#000000"
419
440
  rw: r
420
441
  log: true
421
442
  graphable: true
@@ -431,7 +452,7 @@ message:
431
452
  type: text
432
453
  log: false
433
454
  display: text
434
- color: black
455
+ color: "#000000"
435
456
  wireable: true
436
457
  rw: w
437
458
  retain: true
@@ -442,7 +463,8 @@ mm:
442
463
  display: text
443
464
  min: 0
444
465
  max: 500
445
- color: blue
466
+ width: 5
467
+ color: "#0000ff"
446
468
  rw: r
447
469
  log: true
448
470
  duplicates:
@@ -464,7 +486,7 @@ on:
464
486
  name: On
465
487
  type: bool
466
488
  display: toggle
467
- color: black
489
+ color: "#000000"
468
490
  rw: w
469
491
  log: true
470
492
  duplicates:
@@ -476,7 +498,7 @@ out:
476
498
  name: Out
477
499
  type: bool
478
500
  display: toggle
479
- color: black
501
+ color: "#000000"
480
502
  rw: r
481
503
  log: true
482
504
  wireable: true
@@ -484,13 +506,33 @@ out:
484
506
  duplicates:
485
507
  significantdate: 900000
486
508
  significantvalue: 1
509
+ percent:
510
+ # A 0-100% figure that is worth recording and graphing - state of charge, battery health. Unlike
511
+ # controlfloat it is read-only and logged, so it needs its own duplicate rule; [name] and the
512
+ # duplicate thresholds should be overridden per module.
513
+ leaf: percent
514
+ name: Percent
515
+ type: float
516
+ display: bar
517
+ min: 0
518
+ max: 100
519
+ width: 3
520
+ color: "#008000"
521
+ rw: r
522
+ wireable: false
523
+ log: true
524
+ duplicates:
525
+ significantdate: 900000
526
+ significantvalue: 1
527
+ graphable: true
528
+ units: "%"
487
529
  position:
488
530
  leaf: position
489
531
  name: Position
490
532
  type: text
491
533
  log: false
492
534
  display: text
493
- color: black
535
+ color: "#000000"
494
536
  rw: r
495
537
  power:
496
538
  leaf: power
@@ -499,7 +541,8 @@ power:
499
541
  display: text
500
542
  min: 0
501
543
  max: 100000
502
- color: orange
544
+ width: 8
545
+ color: "#ffa500"
503
546
  rw: r
504
547
  log: true
505
548
  units: mW
@@ -512,9 +555,11 @@ pressure:
512
555
  name: Pressure
513
556
  type: float
514
557
  display: bar
515
- min: 0
516
- max: 99
517
- color: blue
558
+ min: 300
559
+ max: 1100
560
+ width: 6
561
+ units: hPa
562
+ color: "#0000ff"
518
563
  rw: r
519
564
  log: true
520
565
  duplicates:
@@ -528,7 +573,8 @@ rain:
528
573
  display: text
529
574
  min: 0
530
575
  max: 500
531
- color: blue
576
+ width: 5
577
+ color: "#0000ff"
532
578
  rw: r
533
579
  log: true
534
580
  duplicates:
@@ -543,14 +589,15 @@ satellites:
543
589
  display: text
544
590
  min: 0
545
591
  max: 32
546
- color: black
592
+ width: 2
593
+ color: "#000000"
547
594
  log: false
548
595
  duplicates:
549
596
  significantdate: 900000
550
597
  significantvalue: 1
551
598
  rw: r
552
599
  graphable: false
553
- units: m/s
600
+ units: count
554
601
  select:
555
602
  leaf: select
556
603
  name: Select
@@ -558,7 +605,7 @@ select:
558
605
  log: false
559
606
  min: 0
560
607
  display: text
561
- color: black
608
+ color: "#000000"
562
609
  wireable: false
563
610
  rw: w
564
611
  retain: true
@@ -569,7 +616,8 @@ shunt:
569
616
  display: text
570
617
  min: -320
571
618
  max: 320
572
- color: blue
619
+ width: 6
620
+ color: "#0000ff"
573
621
  rw: r
574
622
  log: true
575
623
  units: mV
@@ -584,7 +632,8 @@ speed:
584
632
  display: text
585
633
  min: 0
586
634
  max: 999
587
- color: black
635
+ width: 5
636
+ color: "#000000"
588
637
  log: true
589
638
  duplicates:
590
639
  significantdate: 900000
@@ -599,7 +648,7 @@ soil:
599
648
  display: bar
600
649
  min: 0
601
650
  max: 100
602
- color: brown
651
+ color: "#a52a2a"
603
652
  rw: r
604
653
  log: true
605
654
  duplicates:
@@ -607,7 +656,23 @@ soil:
607
656
  significantvalue: 1
608
657
  graphable: true
609
658
  calibrate: true
610
- units: "%RH"
659
+ units: "%"
660
+ tank:
661
+ leaf: tank
662
+ name: Water Tank
663
+ type: float
664
+ display: bar
665
+ min: 0
666
+ max: 100
667
+ width: 5
668
+ color: "#0000ff"
669
+ rw: r
670
+ log: true
671
+ duplicates:
672
+ significantdate: 900000
673
+ significantvalue: 1
674
+ graphable: true
675
+ units: "%"
611
676
  temperature:
612
677
  leaf: temperature
613
678
  name: Temperature
@@ -616,7 +681,8 @@ temperature:
616
681
  rw: r
617
682
  min: 0
618
683
  max: 50
619
- color: red
684
+ width: 4
685
+ color: "#ff0000"
620
686
  wireable: false
621
687
  log: true
622
688
  units: Cel
@@ -629,10 +695,11 @@ timeoff:
629
695
  name: Time Off (s)
630
696
  min: 0
631
697
  max: 3600
698
+ width: 4
632
699
  type: float
633
700
  log: false
634
701
  display: text
635
- color: black
702
+ color: "#000000"
636
703
  rw: w
637
704
  wireable: true
638
705
  units: s
@@ -641,10 +708,11 @@ timeon:
641
708
  name: Time On (s)
642
709
  min: 0
643
710
  max: 3600
711
+ width: 4
644
712
  type: float
645
713
  log: false
646
714
  display: text
647
- color: black
715
+ color: "#000000"
648
716
  rw: w
649
717
  wireable: true
650
718
  units: s
@@ -655,7 +723,7 @@ tvoc:
655
723
  display: bar
656
724
  min: 0
657
725
  max: 99
658
- color: green
726
+ color: "#008000"
659
727
  rw: r
660
728
  log: true
661
729
  units: ppb
@@ -670,7 +738,8 @@ ultrasonic:
670
738
  display: bar
671
739
  min: 0
672
740
  max: 7500
673
- color: blue
741
+ width: 4
742
+ color: "#0000ff"
674
743
  rw: r
675
744
  log: true
676
745
  units: mm
@@ -685,8 +754,43 @@ utc_time:
685
754
  type: text
686
755
  log: false
687
756
  display: text
688
- color: black
757
+ color: "#000000"
758
+ rw: r
759
+ voltage:
760
+ # A DC voltage read through a resistor divider - see Sensor_Voltage in the library. Millivolts,
761
+ # like `battery`, but without that topic's single-lithium-cell range or its special-casing in
762
+ # the UX. [name, min, max] should be overridden per module.
763
+ leaf: voltage
764
+ name: Voltage
765
+ type: int
766
+ display: text
767
+ min: 0
768
+ max: 30000
769
+ color: "#800080"
689
770
  rw: r
771
+ graphable: true
772
+ log: true
773
+ units: mV
774
+ duplicates:
775
+ significantdate: 900000
776
+ significantvalue: 100
777
+ volts:
778
+ leaf: volts
779
+ name: Volts
780
+ type: float
781
+ display: slider
782
+ rw: w
783
+ min: 0
784
+ max: 3.3
785
+ width: 4
786
+ color: "#800080"
787
+ units: V
788
+ wireable: true
789
+ log: true
790
+ duplicates:
791
+ significantdate: 900000
792
+ significantvalue: 0.05
793
+ graphable: true
690
794
  wifibars:
691
795
  leaf: wifibars
692
796
  name: WiFi
@@ -695,7 +799,7 @@ wifibars:
695
799
  display: text
696
800
  min: 0
697
801
  max: 4
698
- color: blue
802
+ color: "#0000ff"
699
803
  rw: r
700
804
  # Logged, because an int that is read-only is logged unless told otherwise. Only 5 values, so
701
805
  # any change is worth recording and nothing else is
@@ -708,5 +812,5 @@ wifissid:
708
812
  type: text
709
813
  log: false
710
814
  display: text
711
- color: blue
815
+ color: "#0000ff"
712
816
  rw: r
@@ -0,0 +1,130 @@
1
+ # Bridging this Pi's broker to a production Frugal IoT server
2
+ #
3
+ # Optional. Without this file the Pi is self-contained: nodes publish to the broker here, the
4
+ # logger records the readings here, and the dashboard here shows them. Adding it also relays
5
+ # everything to a production server, so the same nodes appear on a dashboard elsewhere and can be
6
+ # controlled from it, while the Pi carries on working on its own whenever the link is down.
7
+ #
8
+ # This is a Mosquitto "bridge" - the broker's own feature for connecting to another broker. The
9
+ # server, the logger and the nodes know nothing about it and need no configuration change.
10
+ #
11
+ # To use it:
12
+ # sudo cp extras/mosquitto-bridge.conf.example /etc/mosquitto/conf.d/frugal-iot-bridge.conf
13
+ # sudo nano /etc/mosquitto/conf.d/frugal-iot-bridge.conf # replace every <...> below
14
+ # sudo systemctl restart mosquitto
15
+ #
16
+ # It has to be a restart, not a reload: Mosquitto does not pick up bridges on a reload signal.
17
+ # That briefly disconnects every node, which they recover from on their own.
18
+ #
19
+ # The file goes in /etc/mosquitto/conf.d/ rather than in this installation, because it holds a
20
+ # password for the production broker. Everything under config.d/ here is served to any logged-in
21
+ # browser by /config.json, so a credential put there would not stay private.
22
+
23
+ # ============ The connection ============
24
+
25
+ # Names the bridge. Anything, but it appears in the log, so make it recognisable.
26
+ connection to-prod
27
+
28
+ # The production broker's plain-MQTT-over-TLS address. Not the wss:// URL the browsers and the
29
+ # server use - a Mosquitto bridge speaks MQTT or MQTT-over-TLS and cannot use WebSockets, so
30
+ # production needs a listener on 8883 for this. Port 1883 would work on a trusted LAN, but sends
31
+ # the password below in clear, so over the internet use 8883 and keep the TLS lines further down.
32
+ address <prod-host>:8883
33
+
34
+ # The account this Pi uses on the production broker. Give each Pi its own, rather than sharing the
35
+ # organization's account: the organization password is handed to every browser that logs in, so it
36
+ # is not a secret, and a per-Pi account can be restricted to just this site's topics and revoked
37
+ # on its own.
38
+ remote_username <bridge-account>
39
+ remote_password <bridge-password>
40
+
41
+ # The client id this Pi presents to production, and the name production uses when it reports the
42
+ # state of this bridge (see "notifications" below). One per Pi, or two Pis will disconnect each
43
+ # other in a loop as each takes over the other's session.
44
+ remote_clientid <bridge-account>
45
+
46
+ # Verify production's certificate against the system CA store, which is what a Let's Encrypt
47
+ # certificate on production needs - no certificate has to be copied onto this Pi. Delete both
48
+ # lines, and use port 1883 above, only on a network you trust end to end.
49
+ bridge_cafile /etc/ssl/certs/ca-certificates.crt
50
+ bridge_insecure false
51
+
52
+ # How long to wait before retrying, in seconds, doubling from the first number up to the second.
53
+ # A Pi on a weak or intermittent link keeps retrying for as long as it takes; the cap stops a
54
+ # flapping connection from hammering production.
55
+ restart_timeout 5 60
56
+
57
+ # Keep production's copy of our subscriptions across a dropped connection. This is the default,
58
+ # and it matters: with "true" instead, Mosquitto re-subscribes on every reconnect and production
59
+ # re-sends every retained message each time, which on a flaky link means a burst of duplicated
60
+ # readings after every blip. Nodes publish nearly everything retained, so that burst is large.
61
+ cleansession false
62
+
63
+ # Publish a retained "1" or "0" to $SYS/broker/connection/<remote_clientid>/state on both brokers
64
+ # whenever the link comes up or goes down. This is how "npx --no frugal-iot-diagnostic" reports
65
+ # whether the bridge is connected, and it also lets production see which sites are reachable.
66
+ # The default is true; it is written out here because it is useful rather than incidental.
67
+ notifications true
68
+
69
+ # Uncomment if production refuses the bridge's connection or logs a denied publish to $SYS. Once
70
+ # production has an acl_file, writing to $SYS may not be permitted, and this stops trying: the
71
+ # state is then published only to the broker here, which is all the diagnostic script reads. What
72
+ # is lost is production's view of whether this site is reachable.
73
+ #notifications_local_only true
74
+
75
+ # Not set, and worth knowing why: local_username / local_password. The broker here requires
76
+ # authentication, but a bridge's local side is internal to the broker and does not go through it.
77
+ # Verified on mosquitto 2.0.21 - an anonymous publish is refused while the bridge still forwards.
78
+
79
+ # ============ What gets relayed ============
80
+ #
81
+ # Replace <org> in all four lines with the organization id - the first element of every topic, and
82
+ # the name of the file in config.d/organizations/. One set of four lines per organization on this
83
+ # Pi. To relay only some projects, replace "<org>/" with "<org>/<project>/" and add "+/" where a
84
+ # level is now missing, or repeat the block per project.
85
+ #
86
+ # Frugal IoT topics come in five shapes, and each line below picks out one or two of them. "+"
87
+ # matches exactly one level, so counting levels is what separates them:
88
+ #
89
+ # <org>/<project> the "quick discover" announcement
90
+ # <org>/<project>/<node>/<module>/<field> a reading
91
+ # <org>/<project>/<node>/<module>/<field>/<parameter> a reading's min, max, colour, wiring
92
+ # <org>/<project>/<node>/set/<module>/<field> a control
93
+ # <org>/<project>/<node>/set/<module>/<field>/<parameter>
94
+ #
95
+ # Readings, at five levels. These are the only topics the logger writes to disk, so this is the
96
+ # one line where a topic must not be matched twice - each extra copy would be another row in the
97
+ # CSV files at the far end.
98
+ #
99
+ # The "0" is the QoS, and it is doing real work. At 0, anything published while production is
100
+ # unreachable is simply dropped: production's history has a gap for the outage and this Pi's is
101
+ # complete. At 1 the broker would instead queue it all up and deliver it on reconnect, where
102
+ # production stamps each reading with the time it arrived - so an outage would come back as a
103
+ # whole day's readings all claiming to have happened at the moment the link returned, which is
104
+ # worse than a gap because it looks like real data. Leave this at 0.
105
+ topic <org>/+/+/+/+ out 0
106
+
107
+ # The quick discover announcement, so production learns which nodes live here. Sent periodically
108
+ # and not retained, so nothing accumulates while the link is down.
109
+ topic <org>/+ out 0
110
+
111
+ # Parameters - the min, max, colour and wiring a node announces once at startup. The dashboard
112
+ # needs them to draw a node properly, and the logger never records them.
113
+ topic <org>/+/+/+/+/+ out 0
114
+
115
+ # Controls, in both directions and at QoS 1. Both directions, because a control can be set from
116
+ # either dashboard and both brokers need to know. QoS 1 because here queueing is exactly what is
117
+ # wanted: a control set on production while this Pi is offline is held and delivered when it comes
118
+ # back, which is the same reason nodes publish these retained.
119
+ #
120
+ # At six levels this overlaps the parameter line above, so some controls reach production twice.
121
+ # That is deliberate - it is the harmless place to put the overlap, since a repeated control
122
+ # carries the same value and the logger discards anything with "set" in it. There is no way to
123
+ # write "six levels but not a control", as MQTT patterns cannot exclude.
124
+ topic <org>/+/+/set/# both 1
125
+
126
+ # Loop protection is Mosquitto's "try_private", on by default, which tells production that this is
127
+ # a bridge and not an ordinary client so that it never echoes our own messages back at us. Both
128
+ # ends must be Mosquitto for that to hold - it was measured working between 2.0.21 and itself.
129
+ # TODO revisit if production ever moves to a different broker, or if a third broker is ever
130
+ # bridged into the same topics, as neither case is covered by that protection.