-
Notifications
You must be signed in to change notification settings - Fork 7
Concepts
This document is intended to give a high level overview of some of the concepts in Frugal IoT for developers, so its a good place to start and there should be links to more detailed versions.
Currently there is no particular order to these, I am just trying to bring documentation together on concepts that in many cases bridge nodes, logger/server and client.
To be covered below:
- System Architecture
- Messages - the core of Frugal IoT
- Naming - how messages are addressed
- LoRa - how we'll handle longer range communications outside of WiFi range
- Control - how parts are hooked together into control systems
- Back Data / Logging - how we keep track of, and serve, old data
- Interrupts
- Power
- Processors - including adding new ones
- Local Ops - own server on RPi etc
- UX (both its ugliness and composability)
- Loops, periodic and infrequent
- Data Structures on Client
- Graphing
- State retention - Node File System; MQTT broker, logger, RTC
- multilingual
- Messages are the core of Frugal IoT.
- All communications between modules are via messages. There should not be anywhere that one module reaches into another one for information.
- To-do: check for any exceptions and document here.
- On the node, the messages are all run through the messages module.
- Outgoing messages are queued by the messaging model, which then decides whether it has a connection to Wi-Fi. In which case it's sent via Wi-Fi to the MQTT broker, or, if not, if it has a LoRa connection, it sends by LoRaMesher. In the future, this is where we'll add GSM service.
- Incoming messages are received either by the Wi-Fi MQTT interface or the LoRa interface and added to the queue in the messaging module.
- The messaging module then pulls each module to see if it can handle the message. Each module checks for module-specific things, for example, names and/or description, and then polls each of its inputs and outputs to see if they can inform the message.
- Generally speaking, messages come in two variants: with or without a set.
- For example,
/dev/lotus/esp32-1234/set/temperature/max=100this is an incoming message from the UX setting on this technology. - After the module handles this, it will echo back
/dev/lotus/esp32-1234//temperature/max=100
- The LoRa pathway works slightly differently.
- The LoRa messages are just strings, so you cannot put an MQTT packet directly into it.
- Instead, a LoRa message is encoded with a leading byte that indicates the QoS and retain, then a string for the topic, a separator, and a string for the message.
- Upstream (node to server) LoRa messages then arrive at a gateway, which packs them into a message structure and puts them in the outgoing message queue to go up to the MQTT server.
- Incoming messages from WiFi/MQTT are passed to the LoRa module the same as to any other module.
- If the LoRa module has recorded a subscription to that topic, then it turns the message into a LoRa string as above and sends it over LoRa.
- At the receiving node, it's turned back from LoRa to a message again and then is queued to go through the same path as any other incoming message.
Most of the time the sensor is running in a loop but things can happen on three different levels of frequency:
In each main.cpp we call e.g. frugal_iot.configure_power(Power_Deep, 600000, 30000)
This sets our "period" as 600000ms (10 minutes)
In the definition of the
- loop() is run every time the loop goes round. This happens very frequently - around once every 10ms.
- periodic() is run once for each period we define in the power setup so here it would be every 10 minutes. If we are sleeping, this is always once every time after we wake up
- infrequent() is run every time the loop goes round but it should check a timer to see whether it should run. Generally avoid using the timers unless necessary. See System_Discovery::infrequently() for an example.
State is stored in several places in the system. Wherever it's stored, it is conceptually following the message naming scheme /orgid/projectid/nodeid/moduleid/leaf[/parameter] for its hierarchy.
parameter is optional, and in structures that cannot handle a variable number of fields the special parameter value is used for the value of the leaf.
- Each node has its own file system in the
LittleFSfile format. - (We previously used
SPIFFS, and you might still find references to this). - It's stored in a standard file hierarchy with the top level being the module, the next being the leaf, and the file name being the parameter or
value. - There is an exception where the directory
wifihas files, each of which is the SSID of a WiFi network, and the content is the password, e.g./wifi/myhome = secret - This file system can be flashed if you're using
platformio, and in particular I use this to store the Wi-Fi networks that I expect the node to encounter. - Each module uses
readConfigFromFS()to read its directory during thesetup()process. - Incoming
setmessages, are written to the file system withwriteConfigToFSso the state is retained
- Most state change messages are sent using
retain=true, - which means the broker will keep a mapping between topic and value.
- The node doesn't see these echoed back because it only subscribes to
setmessages. - UX gets the full copy of the state from these messages, since it has subscribed to the
.../node/#
Currently, set messages are also sent with retain=true, but this is likely to change. This currently means that on startup a node subscribes to .../node/set/# and gets these messages sent again, but since they should have previously been received and stored on disk, there is no impact on node state.
- The logger stores messages that are flagged in the
schema/modules.yamlaslog=trueinto CSV files. Seeback data storageabove. - The logger typically also stores the current state of each node, although there is currently no way to access this.
- During deep sleep, the RAM memory is cleared.
- A few specific values that change frequently are stored in RTC memory, which survives deep sleep.
- Currently, this includes just the timers and the total amount of time that sleep has happened for.