Skip to content

Concepts

Mitra Ardron edited this page May 27, 2026 · 17 revisions

DRAFT - DO NOT ASSUME THIS IS CORRECT OR COMPLETE YET !

Frugal IoT 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:

Messages - the core of Frugal IoT

  • 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

  • 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.

Set versus normal messages

  • Generally speaking, messages come in two variants: with or without a set.
  • For example, /dev/lotus/esp32-1234/set/temperature/max=100 this 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

  • 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.

LoRa gateway

  • 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.

Loops, periodic and infrequent

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 retention - Node File System; MQTT broker, logger, RTC

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.

Node File System

  • Each node has its own file system in the LittleFS file 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 wifi has 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 the setup() process.
  • Incoming set messages, are written to the file system with writeConfigToFS so the state is retained

MQTT broker

  • 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 set messages.
  • 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.

logger - see back data

  • The logger stores messages that are flagged in the schema/modules.yaml aslog=true into CSV files. See back data storage above.
  • The logger typically also stores the current state of each node, although there is currently no way to access this.

RTC

  • 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.

Clone this wiki locally