frugal-iot-server 0.3.6 → 0.3.8

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.
package/INSTALLATION.md CHANGED
@@ -40,6 +40,44 @@ Any of these work. What differs is only how long the install takes, and how much
40
40
  A Zero W runs the server perfectly well once installed. It is *installing* that is slow, because a
41
41
  single 1 GHz core has to unpack several hundred packages and compile one of them.
42
42
 
43
+ ## The short way: one script
44
+
45
+ Steps 3 to 8 below can be done for you. Once you can ssh into the Pi (steps 1 and 2, which need a
46
+ person with an SD card):
47
+
48
+ ```
49
+ curl -fsSLO https://raw.githubusercontent.com/mitra42/frugal-iot-server/main/scripts/install-pi.sh
50
+ bash install-pi.sh --org myfarm --name "My Farm" --email you@example.com --phone +61123456789
51
+ ```
52
+
53
+ It asks for anything it needs that you did not pass, says what it is doing as it goes, and stops at
54
+ the first thing that fails - leaving a log, and telling you to run `frugal-iot-diagnostic`. Running
55
+ it again after a failure carries on rather than starting over, which matters on a Pi Zero W where one
56
+ step takes about 40 minutes. `--dry-run` checks the arguments and shows the plan without touching the
57
+ machine.
58
+
59
+ **Passwords** are asked for, not invented, unless you say otherwise: it prompts for each (twice, not
60
+ echoed) and generates one if you just press Enter. Pass them as `--superuser-password`,
61
+ `--login-password` and `--broker-password` to skip the prompts, `--random-passwords` to have all
62
+ three generated, or `--yes` to ask nothing at all — which is what to use over ssh with no terminal.
63
+ A password you chose is not written to the log; a generated one has to be, since otherwise you would
64
+ have no way of knowing it.
65
+
66
+ **It tells you whether a reboot is needed**, rather than leaving you to wonder — it notices a kernel
67
+ or boot-firmware package in the upgrade, the images in `/boot` changing underneath it, and
68
+ `/var/run/reboot-required`. If none of those happened it says so, and there is nothing more to do.
69
+
70
+ Fetch it with `curl -O` and then run it, rather than piping curl into bash - piped, it has no
71
+ terminal to ask questions at.
72
+
73
+ It finishes with a live server and a tested broker. It cannot do step 9, pointing your nodes at it,
74
+ because a node learns its broker by being flashed with it - so it prints the settings to give them.
75
+ Nor step 10, HTTPS.
76
+
77
+ The steps below are what it does, in the same order, if you would rather do it by hand or need to
78
+ understand what went wrong.
79
+
80
+ ---
43
81
  ### 0. What you need before you start
44
82
 
45
83
  **Hardware**
@@ -667,6 +705,241 @@ That section will cover: a DNS name for the Pi, port forwarding on the router, n
667
705
  proxy in front of port 8080, and certificates from Let's Encrypt via certbot. Both require the Pi
668
706
  to have internet access, which is the opposite of the offline case this guide is aimed at.
669
707
 
708
+ ### 11. Optional: bridge this Pi to a production server
709
+
710
+ Everything above gives a self-contained Pi. This step also relays its readings to a production
711
+ Frugal IoT server, so the same nodes appear on a dashboard elsewhere and can be controlled from
712
+ it, while the Pi carries on working on its own whenever the link is down. Skip it if you do not
713
+ want that — nothing else depends on it.
714
+
715
+ The relaying is done by the broker, not by the server: Mosquitto has a "bridge" feature for
716
+ connecting to another broker, and the server, the logger and the nodes need no configuration
717
+ change at all. This installation ships an example bridge configuration in
718
+ `extras/mosquitto-bridge.conf.example`, which explains each setting in place; this section is the
719
+ surrounding work.
720
+
721
+ It comes in three parts, and only the first is a one-off:
722
+
723
+ * **11a — preparing a production server to accept bridges.** A TLS listener, a certificate, and an
724
+ access-control file. Done once for a given production server, and not again until there is
725
+ another one.
726
+ * **11b — authorizing this Pi on that server.** One command, run there, once per Pi.
727
+ * **11c — pointing this Pi at it.** One command, run here, once per Pi.
728
+
729
+ If someone has already done 11a for the server you are bridging to — the usual case once the first
730
+ Pi is working — **start at 11b**.
731
+
732
+ **What you get, and what you do not.** While the link is up, readings appear on production within
733
+ a second or so. While it is down, the Pi records everything as usual and production simply has a
734
+ gap — the readings taken during an outage never reach it. That is a deliberate choice: the
735
+ alternative is for the broker to hold them and deliver the backlog on reconnect, but production
736
+ timestamps each reading as it arrives, so a day's backlog would arrive claiming to have happened
737
+ in the instant the link returned. A gap is honest; that would not be. Controls are treated the
738
+ other way round, and *are* queued, so turning something on from production while the Pi is offline
739
+ takes effect when it reconnects.
740
+
741
+ #### 11a. Preparing a production server to accept bridges
742
+
743
+ Once per production server, not per Pi — and someone has probably already done it for
744
+ `frugaliot.naturalinnovation.org`. If so, skip to 11b.
745
+
746
+ **Add a listener for bridges.** A Mosquitto bridge speaks MQTT or MQTT-over-TLS and cannot use
747
+ WebSockets, so the existing `wss://` path that browsers and servers use cannot carry this. It needs
748
+ a TLS listener of its own on 8883, and the sensible certificate to give it is the one the web
749
+ server already uses for the same hostname — one certificate and one renewal, rather than two.
750
+
751
+ Find where that certificate actually is, rather than assuming: certbot's own layout under
752
+ `/etc/letsencrypt/live/` is only one of several, and a wildcard certificate covering more than one
753
+ domain is often installed by hand somewhere else entirely.
754
+
755
+ ```
756
+ sudo grep -rn 'SSLCertificateFile\|SSLCertificateKeyFile' /etc/apache2/ # Apache
757
+ sudo nginx -T 2>/dev/null | grep ssl_certificate # nginx
758
+ ```
759
+
760
+ Take the paths for the vhost serving this server's hostname — `sudo apache2ctl -S` says which
761
+ vhost that is — and add to production's Mosquitto configuration:
762
+
763
+ ```
764
+ listener 8883
765
+ protocol mqtt
766
+ certfile <the SSLCertificateFile path>
767
+ keyfile <the SSLCertificateKeyFile path>
768
+ ```
769
+
770
+ then open 8883 on the firewall.
771
+
772
+ > **`certfile` must contain the intermediate certificate as well as the server's own**, or bridges
773
+ > will refuse to connect even though browsers are happy — a browser can often fill in a missing
774
+ > intermediate from cache and a bridge never can. `sudo grep -c 'BEGIN CERTIFICATE' <certfile>`
775
+ > should report 2 or more. If it reports 1, concatenate the issuer's chain onto a copy and point
776
+ > `certfile` at that.
777
+
778
+ > **The key's permissions are what usually goes wrong.** Unlike a web server, which reads its key
779
+ > as root at startup, Mosquitto drops to the `mosquitto` user *first* and reads the key afterwards,
780
+ > so a key left `root:root 0600` stops the broker starting:
781
+ >
782
+ > ```
783
+ > Error: Unable to load server key file "...". Check keyfile.
784
+ > OpenSSL Error[0]: error:8000000D:system library::Permission denied
785
+ > ```
786
+ >
787
+ > On Debian the tidy fix is the `ssl-cert` group, which exists for exactly this and which
788
+ > `/etc/ssl/private` is already set up to admit:
789
+ >
790
+ > ```
791
+ > sudo adduser mosquitto ssl-cert
792
+ > sudo chgrp ssl-cert <keyfile>
793
+ > sudo chmod 640 <keyfile>
794
+ > ```
795
+ >
796
+ > The web server is unaffected — root still reads the key as before. If you would rather not widen
797
+ > access to a directory holding keys for other domains, copy the certificate and key into
798
+ > `/etc/mosquitto/certs` owned `mosquitto:mosquitto` instead, and refresh that copy on renewal.
799
+
800
+ **Add one line to however this certificate gets renewed:**
801
+
802
+ ```
803
+ systemctl reload mosquitto
804
+ ```
805
+
806
+ Mosquitto keeps the certificate it loaded at startup, so without this the broker goes on serving
807
+ the old one until it happens to be restarted, and bridges start failing once it expires. A reload
808
+ is enough — it picks up a replaced certificate without dropping a single connection. Where that
809
+ line goes depends on how the certificate is renewed: a certbot deploy hook in
810
+ `/etc/letsencrypt/renewal-hooks/deploy/`, `acme.sh`'s `--reloadcmd`, or, if the certificate is
811
+ installed by hand, the written procedure for doing that.
812
+
813
+ > Note the asymmetry with the Pi end below: a *certificate* change needs only `reload`, but adding
814
+ > or changing a *bridge* needs a full `restart`, because Mosquitto does not reload bridges.
815
+
816
+ **Turn on access control.** Without it, any account that can log in to the broker can publish and
817
+ subscribe anywhere on it, so a per-Pi account would be no more confined than the organization's
818
+ own. This is the part to plan carefully, because Mosquitto's ACL file is **deny-by-default**: the
819
+ moment the broker names an `acl_file`, every existing account with no entry in it stops working.
820
+ So the file that restricts new bridge accounts has to grant the existing organizations what they
821
+ already have, in the same change.
822
+
823
+ ```
824
+ sudo tee /etc/mosquitto/aclfile >/dev/null <<'EOF'
825
+ # Deny-by-default: an account with no rule here can connect but reach no topic at all.
826
+
827
+ # Each organization reaches only its own topic tree. "%u" is the connecting account name, and the
828
+ # first element of every Frugal IoT topic is the organization id - so this is what the nodes,
829
+ # dashboards and the server's own logger already do. It just stops being optional.
830
+ pattern readwrite %u/#
831
+ EOF
832
+ sudo chmod 644 /etc/mosquitto/aclfile
833
+ ```
834
+
835
+ `chmod 644` matters: like the password file and the TLS key, the broker reads this *after* dropping
836
+ to the `mosquitto` user, so a root-only file stops it starting. Then name it in the configuration
837
+ and restart:
838
+
839
+ ```
840
+ echo 'acl_file /etc/mosquitto/aclfile' | sudo tee /etc/mosquitto/conf.d/zy-frugal-iot-acl.conf
841
+ sudo systemctl restart mosquitto
842
+ ```
843
+
844
+ Check every existing organization can still log in **before** connecting any Pi — a wrong ACL file
845
+ locks out every node and dashboard at once, and the reason appears only in Mosquitto's own log. On a
846
+ Frugal IoT server the quickest check is its journal, which logs one line per organization:
847
+
848
+ ```
849
+ sudo journalctl -u frugaliot -n 40 --no-pager | grep -iE "mqtt |not authoris"
850
+ ```
851
+
852
+ Every organization should show `connect` and none should show `not authorized`. To back the change
853
+ out, delete `/etc/mosquitto/conf.d/zy-frugal-iot-acl.conf` and restart.
854
+
855
+ Nothing needs adding here per Pi: `frugal-iot-addbridge-prod` in 11b appends each bridge's own rule.
856
+
857
+ > One thing this costs: `pattern readwrite %u/#` also denies `$SYS`, so the production broker can no
858
+ > longer tell anyone which bridges are currently connected. The Pi still reports its own bridge
859
+ > state to `frugal-iot-diagnostic`, which is where you would look anyway, so this is a fair trade —
860
+ > but it is why production's dashboard cannot show a site as up or down.
861
+
862
+ #### 11b. Authorizing this Pi (run on the production server)
863
+
864
+ Once per Pi. On the production server, from its own directory:
865
+
866
+ ```
867
+ npx --no frugal-iot-addbridge-prod <org-id> <site-name>
868
+ ```
869
+
870
+ The site name only distinguishes one Pi from another within an organization — `northfield`,
871
+ `shed`, `village2` — and becomes part of the account name. The script creates the broker account,
872
+ adds its access-control rule, reloads the broker, and prints both the password and the exact
873
+ command to run in 11c.
874
+
875
+ Three things it checks, because each of them is a way this quietly fails:
876
+
877
+ * **The organization must already exist on the production server**, or the readings will arrive at
878
+ its broker and nothing will record them — that server's logger subscribes per organization,
879
+ driven by the files in its `config.d/organizations/`. If it is missing, add it there first with
880
+ `frugal-iot-addorganization`, using the *same* organization id as this Pi.
881
+ * **Each Pi gets its own account**, rather than sharing the organization's. The organization's
882
+ broker password is handed to every browser that logs in, so it is not a secret; a per-Pi account
883
+ can be confined to one site and revoked on its own.
884
+ * **An existing account is left alone.** Re-running it for a site that already has one is refused
885
+ rather than quietly issuing a new password, which would stop that Pi relaying without anyone
886
+ touching it.
887
+
888
+ The password is shown once and is not stored anywhere you can read back — copy it before you lose
889
+ the output.
890
+
891
+ #### 11c. Bridging this Pi (run on the Pi)
892
+
893
+ Once per Pi. Run the command 11b printed, from this installation's directory:
894
+
895
+ ```
896
+ cd ~/frugal-iot
897
+ npx --no frugal-iot-addbridge-pi --org <org-id> --host <prod-host> --account bridge-<site-name>
898
+ ```
899
+
900
+ It asks for the password rather than taking it on the command line, so it stays out of your shell
901
+ history. Then it checks the far end is reachable and that its certificate is valid for that name,
902
+ writes `/etc/mosquitto/conf.d/frugal-iot-bridge.conf`, restarts the broker, and reports whether the
903
+ bridge actually connected.
904
+
905
+ The restart is unavoidable: Mosquitto does not pick up bridges on a reload signal, so `reload`
906
+ appears to succeed and changes nothing. It briefly disconnects every node, which they recover from
907
+ on their own.
908
+
909
+ > The configuration goes into `/etc/mosquitto/conf.d/` and not into this installation's
910
+ > `config.d/`, because it holds a password for the production server. Everything under `config.d/`
911
+ > is served to any logged-in browser by `/config.json`, so a credential put there would not stay
912
+ > private. The file is written mode 600 for the same reason.
913
+
914
+ To undo it: `sudo rm /etc/mosquitto/conf.d/frugal-iot-bridge.conf && sudo systemctl restart
915
+ mosquitto`. The Pi goes back to being self-contained and keeps everything it has recorded.
916
+
917
+ #### Check it
918
+
919
+ ```
920
+ npx --no frugal-iot-diagnostic
921
+ ```
922
+
923
+ The **Bridge to a production server** section names the bridge, lists what it relays, and reports
924
+ whether it is connected. `CONNECTED` means the link is up. `DOWN`, or a report that it has never
925
+ connected, means the address, the credentials or the certificate is wrong — and Mosquitto's own log,
926
+ quoted earlier in the same output, says which.
927
+
928
+ Then confirm at the far end: log in to production's dashboard and look for this Pi's nodes. They
929
+ should appear within a couple of minutes, which is however long it takes each node to next report.
930
+
931
+ **One oddity worth expecting.** Each time the bridge reconnects, the most recent value of every
932
+ reading is re-sent to production and recorded there with the reconnect time rather than the time
933
+ it was measured. That is one row per topic per outage, and it happens because nodes publish
934
+ readings retained. The `duplicates:` rules in `config.d/schema/topics.yaml` absorb it when the
935
+ value has not moved and the outage was short; after a long outage the row is written.
936
+
937
+ **The clock matters more once a Pi is bridged**, because its readings now sit alongside
938
+ production's. A bridged Pi has internet access whenever the link is up, so it sets its time from
939
+ the network and the problem below mostly goes away — but it still comes up after a power cut
940
+ believing whatever it last saved, and anything recorded before it reaches a time server carries
941
+ that wrong time.
942
+
670
943
  ### Known limitation: the clock on an offline Pi
671
944
 
672
945
  A Raspberry Pi has no battery-backed clock. While it has internet access it sets its time from the
@@ -755,6 +1028,8 @@ broken. It covers most of the checks scattered through this guide, in one pass:
755
1028
  * whether Mosquitto and the `frugaliot` service are running, with the last lines of the journal
756
1029
  **and of Mosquitto's own log**, which is where its startup errors actually appear
757
1030
  * whether the broker refuses a wrong password, and accepts each organization's real one
1031
+ * whether a bridge to a production server is configured, what it relays, and whether it is
1032
+ connected right now (step 11) — the bridge password is not printed
758
1033
  * whether ports 1883, 9012 and 8080 are listening, and whether the web server answers
759
1034
  * whether the broker host in `config.d/mqtt.yaml` resolves from this machine
760
1035
  * how much data the logger has written
@@ -770,6 +1045,30 @@ If you have not reached step 4 yet, the command does not exist. Copy
770
1045
  [scripts/diagnostic.zsh](https://github.com/mitra42/frugal-iot-server/blob/main/scripts/diagnostic.zsh)
771
1046
  to the Pi and run `zsh diagnostic.zsh` instead.
772
1047
 
1048
+ ### A topic that will not go away
1049
+
1050
+ Nodes publish almost everything **retained**, meaning the broker keeps the last value and gives it
1051
+ to every new subscriber — which is how a dashboard shows a reading immediately instead of waiting
1052
+ for the next one. The catch is that a topic published by mistake outlives the mistake. Misspell a
1053
+ field, rename a module, flash a node with the wrong id, and that topic sits on the broker for ever,
1054
+ appearing on every dashboard. Fixing the node does not remove it, and neither does restarting the
1055
+ broker: the only way is to publish an empty message to that exact topic.
1056
+
1057
+ ```
1058
+ cd ~/frugal-iot
1059
+ npx --no frugal-iot-clearretained 'myfarm/lotus/+/sht/temperture/#'
1060
+ ```
1061
+
1062
+ Quote the pattern or the shell will expand it. That lists what is retained and changes nothing;
1063
+ add `--delete` to the same command to remove it.
1064
+
1065
+ > Look before deleting. A node's `min`, `max`, `color` and `wired` settings are retained messages
1066
+ > too, and they are how the dashboard knows how to draw it — delete those and the node has to be
1067
+ > restarted before it looks right again. A pattern of just `#` is refused outright.
1068
+
1069
+ On a Pi that is bridged to a production server (step 11), deleting here deletes there too: the
1070
+ bridge forwards the empty message like any other.
1071
+
773
1072
  ---
774
1073
 
775
1074
  ## Upgrading
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  ## Installation
4
4
 
5
5
  For a self-contained server on a Raspberry Pi - including the MQTT broker, and starting from a
6
- blank SD card - see [INSTALLATION.md](INSTALLATION.md).
6
+ blank SD card - see [INSTALLATION.md](INSTALLATION.md), which also has a script that does the whole
7
+ thing in one go once you can ssh in.
7
8
 
8
9
  On a unix box that is already running ...
9
10
 
@@ -20,15 +21,20 @@ npm install frugal-iot-server
20
21
  npx --no frugal-iot-init
21
22
  ```
22
23
  `frugal-iot-init` copies in the configuration files, creates the `data`, `ota` and
23
- `config.d/organizations` directories, and creates the database. It never overwrites anything
24
- already there, so run it again after an upgrade to pick up newly added configuration.
24
+ `config.d/organizations` directories, points `config.d/server.yaml` at the web client npm just
25
+ installed, and creates the database. It never overwrites anything already there, so run it again
26
+ after an upgrade to pick up newly added configuration.
25
27
 
26
28
  Edit `config.yaml` and `config.d/mqtt.yaml` (which broker to talk to) if the defaults do not suit.
27
29
 
28
- The database is created with a `superuser` account that has no password, so give it one:
30
+ A newly created database has two accounts in it, `everyone` (which holds the permissions every
31
+ logged-in user gets) and `superuser`, which has no password - so give it one:
29
32
  ```
30
33
  npx --no frugal-iot-setpassword superuser "<a-good-password>"
31
34
  ```
35
+ On a database that existed before this account was introduced, that command reports
36
+ `no user 'superuser'`, because id 1 was already taken by whoever registered first - and that
37
+ account already has the superuser's privileges. Log in as that one instead.
32
38
 
33
39
  Then add an organization - this writes its yaml file into `config.d/organizations`, creates a
34
40
  login account of the same name with its permissions, and sets its password on the MQTT broker:
@@ -52,24 +58,49 @@ readYamlConfigFile ./config.yaml
52
58
  ... and then reading each of the other files
53
59
 
54
60
  Broker wss://frugaliot.naturalinnovation.org/wss - organizations: dev
61
+ Not logging HTTP requests (morgan: false in config.d/server.yaml)
55
62
  Doing OTA updates at /ota_update from ...some path.../ota
56
- Serving /node_modules from ./node_modules
63
+ Serving /node_modules from node_modules/frugal-iot-client/node_modules then ./node_modules
57
64
  User Database exists
58
65
  Opened user database
59
66
  Exec-ed starting SQL
67
+ Created logger client for API integration
68
+ Mounted API routes at /api
60
69
  Serving /data from ./data
61
70
  Server starting on port 8080
71
+ Logger not reporting individual messages (verbose: false in config.d/logger.yaml)
72
+ Collecting readings in memory, writing them out every 300 seconds
62
73
  mqtt dev connecting
63
74
  mqtt dev connect
64
75
  Subscribing topic dev/# 0
65
- Received dev/lotus/esp8266-85ea2b/humidity 71.8
66
76
  ```
67
- Where the broker and organizations are reported back,
68
- then it successfully connects to the mqtt server
69
- and receives data from nodes attached to it.
77
+ Where the broker and organizations are reported back,
78
+ then it successfully connects to the mqtt server.
79
+
80
+ **It then goes quiet, and that is what should happen.** A new installation is set up to write as
81
+ little as it can, because the usual home for this is a Raspberry Pi running from an SD card, and
82
+ cards wear out from being written to. So it does not log a line per message received, nor a line
83
+ per web request, and readings are collected in memory and written out every five minutes rather
84
+ than one at a time. Seeing nothing after the lines above does not mean nothing is arriving.
85
+
86
+ To watch the messages while setting up, or to work out why a node's readings are not appearing,
87
+ turn it back on in `config.d/logger.yaml` and restart:
88
+ ```
89
+ verbose: true
90
+ ```
91
+ and each message reappears as `Received dev/lotus/esp8266-85ea2b/humidity 71.8`. `morgan: true`
92
+ in `config.d/server.yaml` does the same for web requests. Turn both off again afterwards.
93
+ There is more on what gets written, and how to see how much, under
94
+ [Wear and tear on the SD card](INSTALLATION.md#wear-and-tear-on-the-sd-card).
70
95
 
71
96
  Open a browser pointing at for example `localhost:8080` and you should see the UI.
72
97
 
98
+ If something is not right, this reports on the whole installation - versions, configuration,
99
+ broker logins, what has been logged - and changes nothing:
100
+ ```
101
+ npx --no frugal-iot-diagnostic
102
+ ```
103
+
73
104
  To upgrade later: `npm update frugal-iot-server` then `npx --no frugal-iot-init`.
74
105
 
75
106
  #### Developing the server, client or logger
@@ -85,22 +116,66 @@ The commands above have in-repo equivalents - `scripts/addorganization.zsh` and
85
116
  `scripts/setpassword.zsh` - and the server is `node frugal-iot-server.js`. All of them work on
86
117
  the directory you run them in, which for a clone is the top of the repo.
87
118
 
88
- To work on the client or logger from sibling checkouts, switch two places to their commented-out
89
- development lines:
90
- - `config.d/server.yaml` - `htmldir` and `nodemodulesdir` point at `../frugal-iot-client`
91
- - `frugal-iot-server.js` - the `MqttLogger` import points at `../frugal-iot-logger/index.js`
119
+ To work on the client or the logger from sibling checkouts as well, link them in. This needs no
120
+ edit to any file, so there is nothing to remember to change back:
121
+ ```
122
+ cd ../frugal-iot-client && npm link
123
+ cd ../frugal-iot-logger && npm link
124
+ cd ../frugal-iot-server && npm link frugal-iot-client frugal-iot-logger
125
+ ```
126
+ The server then loads both from your checkouts. `ls -l node_modules/frugal-iot-*` shows whether the
127
+ links are in place - a later `npm install` or `npm update` can replace them with the published
128
+ packages, which looks like your changes having no effect.
129
+
130
+ There are commented-out alternatives in `config.d/server.yaml` (for `htmldir`) and in
131
+ `frugal-iot-server.js` (for the `MqttLogger` import) that do the same job by editing instead. They
132
+ work, but they are easy to publish by accident, so prefer the links.
92
133
 
93
- Take care not to commit those local switches, and note that `npm publish` packages your working
94
- tree - so check with `npm pack --dry-run` before publishing.
134
+ Before publishing, run:
135
+ ```
136
+ npm run prerelease
137
+ ```
138
+ It checks the sensor schema and copies it into the examples that ship with `frugal-iot-logger`, sets
139
+ the service worker's cache version from the client version this release installs, and refuses if:
140
+ - the package is wired to a local checkout - a `file:` dependency, an `npm link`, a switched import
141
+ or a switched `htmldir`;
142
+ - it requires a version of the client or logger that has not been published (a warning if published
143
+ but not the newest, since a fresh install resolves to the newest anyway while an existing one may
144
+ not move);
145
+ - anything git ignores would be published. `files` in `package.json` is an allow-list, which fails
146
+ closed, but a file sitting in one of the listed directories goes out whether or not you meant it
147
+ to - which is how a working copy of the user database was published in 0.3.2.
148
+
149
+ It also lists anything in the repository that would *not* be published and is not already accounted
150
+ for in `scripts/not-published.txt`, so a newly added file that installers need does not go missing
151
+ unnoticed. `npm run check-files` runs just that part.
152
+
153
+ `npm run check-schema` on its own reports sensor topics that do not say whether they are logged, or
154
+ that are logged with no rule about how often - both of which are easy to add by accident and
155
+ expensive on an SD card.
95
156
 
96
157
  #### Running a production server
97
158
  To set it up as a service that runs at startup (and instructions vary between flavors of Linux)
98
159
 
99
- copy and edit `extras/frugaliot.service` to `/etc/systemd/system/frugaliot.service`
100
- you'll need to change the user, the directory you installed into, and `ExecStart`
160
+ copy and edit `extras/frugaliot.service` to `/etc/systemd/system/frugaliot.service`. As shipped it
161
+ matches the Raspberry Pi install in [INSTALLATION.md](INSTALLATION.md) - user `pi`, installed in
162
+ `/home/pi/frugal-iot` - so on any other machine change `User`, `WorkingDirectory` and `ExecStart`
163
+ to the account and directory you installed into.
164
+ ```
165
+ sudo cp extras/frugaliot.service /etc/systemd/system/frugaliot.service
166
+ sudo systemctl daemon-reload
167
+ sudo systemctl enable --now frugaliot
168
+ ```
169
+ `daemon-reload` is the step that is easy to miss - without it systemd keeps using the version it
170
+ read before, and your edits appear to do nothing. After that `service frugaliot start|stop|restart`
171
+ works as usual, and `systemctl status frugaliot` reports whether it is running.
172
+
173
+ If you already have the service installed and only want to change one setting, `sudo systemctl edit
174
+ frugaliot` writes an override rather than touching the file, which is easier to undo
175
+ (`sudo systemctl revert frugaliot`).
101
176
 
102
- You can run`service frugaliot start` to start it
103
- and `systemctl enable frugaliot` to make sure it starts at boot.
177
+ Stop it with `service frugaliot stop` rather than killing it, so that readings still held in memory
178
+ are written out first.
104
179
 
105
180
  Note that this will give you a HTTP server, but OTA on ESP32 requires HTTPS.
106
181
 
@@ -0,0 +1,63 @@
1
+ # Which readings appear on a device's card, and in what order.
2
+ #
3
+ # Keys match the OTA key, which the firmware builds as SYSTEM_OTA_PREFIX "_" SYSTEM_OTA_SUFFIX -
4
+ # the application, then the board (see src/system/ota.cpp). So sht30 built for ten boards gives ten
5
+ # keys, sht30_c3_pico, sht30_d1_mini, ... all wanting the same card. Resolution order is:
6
+ # 1. an exact match on the device id e.g. esp8266-fb94bb
7
+ # 2. an exact match on the full OTA key e.g. sht30_c3_pico
8
+ # 3. the longest key the OTA key starts with e.g. sht30
9
+ # Longest wins at step 3, so file order never matters and sht30 beats sht.
10
+ #
11
+ # front: the ordered rows on the front of the card. An entry containing "/" is a twig
12
+ # (module/leaf); one without is a control module, shown as its single control row.
13
+ # summary: the one-line summary. Optional, and usually best omitted - it defaults to the first few
14
+ # entries of front, which now includes a control, shown compactly as "Relay ✓".
15
+ #
16
+ # An application with no entry gets the default: graphable readings, then actuators, then controls.
17
+ # That is the right answer whenever the order does not matter, so most applications belong here only
18
+ # if they want something specific. Module ids are the keys of modules.yaml - note the BH1750 sensor
19
+ # publishes under "lux", not "bh1750".
20
+ #
21
+ # Of the applications in the node library, three are deliberately absent: "all" is every sensor at
22
+ # once and no order helps, "gsheets" carries only a logging control, and "lcd_sht" displays another
23
+ # node's readings rather than its own.
24
+
25
+ agri:
26
+ front: [soil/soil, ds18b20/ds18b20, sht/temperature, sht/humidity, controlhysteresis]
27
+ summary: [soil/soil, ds18b20/ds18b20]
28
+ bh1750:
29
+ front: [lux/lux, controlhysteresis]
30
+ blinken:
31
+ front: [controlblinken]
32
+ commonground:
33
+ front: [bme280/temperature, bme280/humidity, do/do, ultrasonic/ultrasonic, ds18b20/ds18b20, controlhysteresis]
34
+ datalogger:
35
+ front: [sht/temperature, sht/humidity]
36
+ ensaht:
37
+ front: [aht21/temperature, aht21/humidity, ens160/aqi, controlhysteresis]
38
+ gps:
39
+ front: [gps/satellites, gps/latitude, gps/longitude, gps/altitude]
40
+ summary: [gps/satellites]
41
+ lilygohigrow:
42
+ front: [soil/soil, dht/temperature, dht/humidity, lux/lux]
43
+ summary: [soil/soil]
44
+ loadcell:
45
+ front: [loadcell/loadcell]
46
+ loramesher:
47
+ front: [sht/temperature, sht/humidity]
48
+ ms5803:
49
+ front: [ms5803/pressure, ms5803/temperature]
50
+ power:
51
+ front: [sht/temperature, sht/humidity, controlblinken]
52
+ remotedisplay:
53
+ front: [sht/temperature, sht/humidity, controlhysteresis]
54
+ sht30:
55
+ front: [sht/temperature, sht/humidity, controlhysteresis]
56
+ soil:
57
+ front: [soil/soil, sht/temperature, sht/humidity, controlhysteresis]
58
+ summary: [soil/soil]
59
+ sonoff:
60
+ # The relay is what someone wants to see; the control that drives it comes second.
61
+ front: [relay/on, controlhysteresis]
62
+ temp:
63
+ front: [aht20/temperature, bmp280/pressure, controlhysteresis]
@@ -7,6 +7,9 @@
7
7
  # topics: list of topics, each with leaf, optionally leaf_from if it should be copied, and optionally any fields of topics to be overridden
8
8
  # slot: if it is supposed to fit in a specific slot of frugaliot
9
9
  # insidefrugaliot: true if it should be hidden inside the Frugal_IoT drop-down on the name of a sensor, false or not set if it should be shown as a separate module
10
+ # summary: false to keep this module off a device's one-line summary. Omitted means it contributes -
11
+ # default-visible, because a new sensor that is silently invisible until someone remembers a flag
12
+ # is the worse failure. insidefrugaliot modules never contribute; they feed the status strip.
10
13
  aht20:
11
14
  name: "AHT20"
12
15
  topics:
@@ -28,33 +31,30 @@ bme280:
28
31
  - leaf: temperature
29
32
  - leaf: humidity
30
33
  - leaf: pressure
31
- max: 1100
32
- min: 300
33
34
  bme680:
34
35
  name: "BME680"
35
36
  topics:
36
37
  - leaf: temperature
37
38
  - leaf: humidity
38
39
  - leaf: pressure
39
- max: 1100
40
- min: 300
41
40
  - leaf: gas
42
41
  bmp280:
43
42
  name: "BMP280"
44
43
  topics:
45
44
  - leaf: temperature
46
45
  - leaf: pressure
47
- max: 1100
48
- min: 300
49
46
  button:
47
+ summary: false # transient - a press is not a state worth a summary line
50
48
  name: Button
51
49
  topics:
52
50
  - leaf: button
53
51
  captive:
52
+ summary: false # a language code is not a reading
54
53
  name: Captive
55
54
  topics:
56
55
  - leaf: language_code
57
56
  carousel:
57
+ summary: false # a display selector is not a reading
58
58
  name: Display carousel
59
59
  topics:
60
60
  - leaf: select
@@ -64,37 +64,6 @@ controlblinken:
64
64
  - leaf: timeon
65
65
  - leaf: timeoff
66
66
  - leaf: out
67
- climate:
68
- name: Climate
69
- topics:
70
- - leaf: temperature_now
71
- leaf_from: controlfloat
72
- name: Temperature Now
73
- - leaf: temperature_setpoint
74
- leaf_from: controlfloat
75
- wireable: false
76
- name: Temperature Setpoint
77
- - leaf: temperature_hysteresis
78
- leaf_from: controlfloat
79
- wireable: false
80
- name: Temperature Hysteresis
81
- - leaf: humidity_now
82
- leaf_from: controlfloat
83
- name: Humidity Now
84
- - leaf: humidity_setpoint
85
- leaf_from: controlfloat
86
- name: Humidity Setpoint
87
- wireable: false
88
- - leaf: humidity_hysteresis
89
- leaf_from: controlfloat
90
- name: Humidity Hysteresis
91
- wireable: false
92
- - leaf: temperature_out
93
- leaf_from: controlouttoggle
94
- name: Temperature Out
95
- - leaf: humidity_out
96
- leaf_from: controlouttoggle
97
- name: Humidity Out
98
67
  controlhysteresis:
99
68
  name: Control
100
69
  topics:
@@ -153,6 +122,20 @@ do:
153
122
  ens160:
154
123
  name: "ENS160"
155
124
  topics:
125
+ # Compensation inputs, not readings. The ENS160 has no thermometer of its own: it takes
126
+ # ambient temperature and humidity from whatever is wired to it - an AHT21 sitting on the
127
+ # same breakout, or any other sensor on the node. So these come from controlfloat (wireable,
128
+ # rw: w, not logged) rather than from the temperature/humidity topics, which are read-only
129
+ # readings - whichever sensor actually measures them is the one that logs them.
130
+ - leaf: temperature
131
+ leaf_from: controlfloat
132
+ name: Temperature
133
+ max: 50
134
+ color: red
135
+ - leaf: humidity
136
+ leaf_from: controlfloat
137
+ name: Humidity
138
+ color: blue
156
139
  - leaf: aqi
157
140
  - leaf: tvoc
158
141
  - leaf: eco2
@@ -169,6 +152,7 @@ ensaht:
169
152
  - leaf: eco2
170
153
  - leaf: aqi500
171
154
  frugal_iot:
155
+ summary: false # this is the card identity, not a module on it
172
156
  name: XXX
173
157
  topics:
174
158
  - leaf: id
@@ -207,6 +191,7 @@ ina219:
207
191
  - leaf: power
208
192
  - leaf: load
209
193
  lcd:
194
+ summary: false # a message is not a reading
210
195
  name: LCD
211
196
  topics:
212
197
  - leaf: message