MCPcopy Index your code
hub / github.com/arashsm79/OFMon

github.com/arashsm79/OFMon @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
38 symbols 69 edges 6 files 16 documented · 42%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Offline-first Smart Energy Montoring

Associated mobile app available here. * Introduction * Rust and ESP32 * Software Packages Needed for Rust Development on ESP32 * Filesystem * Measuring current and voltage using SCT sensors * Using LittleFS in Rust * Sharding * Dealing with power outages * Rust program routine * Webserver * Flash Memory Partitioning * Thingsboard Platform * Thingsboard Flutter Mobile App * Scan Nearby Access Points * Initial Setup of The Device * Receiving Data From The Device * ER Diagram and Database Architecture * Perform OTA via Mobile * Sending Readings to The Server * Display Data on The Server Side * Conclusion * References

Introduction

There have been numerous smart energy monitoring systems based on the Internet of things (IoT). In most of these systems, the end devices are directly connected to a network and are continuously sending out their data. This poses a real challenge in environments where a reliable wireless connection is not always available, e.g., inside containers, remote locations, or even in places where there is simply no proper infrastructure for wireless communication.

I have developed an offline-first energy monitoring system using ESP32 microcontrollers and their bleeding-edge Rust ecosystem to store data locally in their flash. The readings are collected from the devices by connecting to their access point and are saved on the local storage using a smartphone running a fork of the Thingsboard Flutter Mobile application. After collecting the telemetry data from the devices and when a network connection is available, the mobile app can flush the data of all the devices to the Thingsboards server, an IoT platform used for data collection and visualization. Rust is a type-safe and memory-safe system programming language, it provides its safety guarantees at compile time using rules that eliminate many memory-safety issues. There has been a significant adoption using Rust for embedded development after companies like STMicrocontrollers and Espressif Systems introduced their community projects for enabling the use of the Rust programming language on their SoCs and modules.

A Current transformer is connected to the ADC subsystem of the ESP32 microcontroller and is used to calculate the RMS current and voltage and kWh of the power line. These readings are periodically saved to flash storage. The device also creates a Wi-Fi access point using a unique SSID that is a combination of its MAC address and a predefined string. It then spins up a web server to which clients can request telemetry data along with other operations.

The filesystem used for flash storage is of utmost importance. We have chosen LittleFS since it is power-loss resilient and has wear-leveling mechanisms for flash storage. It also uses constant RAM to work with any kind of data. Some parts of the flash storage have been reserved for Over-the-Air (OTA) updates, but the majority of the flash storage is formatted with the LittleFS file system. The devices are identified using their server-side representation token which is given to them on their first-time initialization using the mobile app. The mobile app also maintains a list of the devices it has connected to and fetches their server-side information when a connection is available. This information is shown alongside the devices’ SSID when scanning for access points. After collecting the data from the devices and syncing it with the Thingsboard server, customers with a few devices or operators with tens of devices can view their data both on the mobile app and on the Thingsboard web app.

Rust and ESP32

Rust programming language has attracted the attention of many companies today. This language allows programmers to write safe programs with concepts such as Ownership and Borrowing and new programming structures. During compilation, this language can guarantee that your program will not encounter many types of errors that were common in older languages such as C. Espressif has designed a framework for programming its microcontrollers, which includes a set of libraries written in C and other tools written in Python. This framework is called esp-idf, and there is a tutorial on how to install and start working with it on the company's website. This framework provides programmers with all the tools needed to set up a project in C language and easily use different parts of the microcontroller including Wifi and Bluetooth.

Over the last few years, the company has started a movement to make it possible to use esp-idf, which is written in C, in the Rust language using FFI, or Foreign Function Interface. Using FFI, code written in one language can be called in another language and its return value can be retrieved. All the work done in this regard is in the esp-rs repository and the development of all these programs is done as open source on Github.

At the time of writing this, two categories of programs are being developed: * Programs that are completely written in Rust and are so-called bare-metal. That is, they are very close to hardware and do not need Rust's standard library. Like: esp-hal * Programs that use the esp-idf API through FFI instead of implementing everything from scratch in Rust. These programs must use the Rust standard library because they use functions needed to communicate with C and esp-idf, and therefore require std. For example: esp-idf-hal

Using the first category is almost impossible due to the immaturity and lack of preparation of many APIs required in industrial work. So currently, most people use the second category, which is based on the tested and advanced esp-idf framework. [1]

Software Packages Needed for Rust Development on ESP32

In the Rust language, a crate is the smallest unit recognized by the Rust compiler. In general, crates can be considered a software package or a project. The following packages are used in developing Rust applications with esp-idf: * embedded-hal: This package contains a set of programming interfaces or traits for using HAL in embedded environments and does not include any code related to a specific microcontroller. * esp-idf-hal: This package is the implementation of embedded-hal for ESP32 microcontrollers through esp-idf. * embedded-svc: This package contains a set of programming interfaces or traits for using different services such as wifi, Bluetooth, and httpd in embedded environments and does not include any code related to a specific microcontroller. * esp-idf-svc: this package is the implementation of embedded-svc for ESP32 microcontrollers through esp-idf. * esp-idf-sys: This package provides raw and insecure connections to the esp-idf library written in C.

Also, since most ESP32 microcontrollers use the Xtensa architecture and this architecture is not supported by default by the Rust compiler and its backend, which is LLVM, Espressif maintains a fork of the Rust compiler that supports this architecture. To get started, this fork of the Rust compiler must be downloaded and installed. The rest of the software needed to flash and monitor the microcontroller and other tips can be found in the Espressif documentation for Rust programming. There is a step-by-step tutorial on how to set up a Rust project in these documents, and it is recommended to read them. [1] To flash, it is enough to install the espflash program from the tutorial above and use the following command to flash the binary file placed in the project on the microcontroller:

$ espflash /dev/ttyUSB0 target/xtensa-esp32-none-elf/release/examples/blinky --monitor

And to create a binary file suitable for OTA use:

$ espflash save-image --partition-table partitions_singleapp.csv ESP32 target/xtensa-esp32-espidf/release/sem sem103

Filesystem

In order to be able to store and manage a lot of data in the flash memory of the microcontroller, we need a file system. According to our needs, this file system should do three things: * Be resistant to power outages. Microcontrollers that are used to monitor the power flow may be interrupted at any moment, and this sudden event should not cause the stored data structures to suffer and the system to be in an unknown state. * RAM memory in microcontrollers is very limited, and regardless of the size of the file in the memory and the number of files, the use of RAM should be constant and not change with the increase of input. * Flash memories have a limited number of write cycles, and if a physical part of the flash is written a large number of times (usually 10,000 to 100,000 times), that part will be damaged. Therefore, the file system must take care to use all memory blocks to spread the depreciation over all blocks. After my research, I have come to the conclusion that there are generally three file systems used for flash memory in microcontrollers. These three file systems and the NVS method, which stores values in memory as key and value pairs and is not considered a file system, have been compared together. [2]

SPIFFS and LittleFS seem to be good options. Examining the repository related to SPIFFS, we find that there is not much development for this file system and the project is almost abandoned. But LittleFS shows a lot of potential. LittleFS is constantly being developed and has a fairly large community behind it. The MicroPython project also uses this file system by default. This relatively new file system uses the Copy-on-Write method, in the simplest case, it works in such a way that when a branch is changed in the file system tree, the whole branch is copied somewhere else and then the change is applied. The previous branch is marked deleted by changing a flag that indicates it no longer has the correct value. With this method, there is no need to erase the memory unnecessarily and use flash memory writing cycles, and you can also make sure that if a problem occurs during writing or the power is cut off, because the values are copied before changing, the original values will not be tampered with. They remain valid until the write is complete. It should be noted that many optimizations are used in this file system to improve the efficiency of the COW method, the details of which are available in the project documentation. [3]

This file system is not suitable for situations where it is necessary to change the middle of a large file, because since the entire file is copied before the change, the entire memory space may be filled due to this copy and the continuation of the file may have problems. But this problem does not exist if we just add something to the end of the file. But it is still generally better to avoid increasing the size of each file, which we will do with the sharding method.

Measuring current and voltage using SCT sensors

These sensors are connected as a clip around the current carrying wire. The current inside the wire passes through a thick metal ring inside the clip and induces a current inside this ring. Another coil with a specific number of turns is connected to the metal ring, and when a current is induced in the big ring, another current proportional to it is induced in the coil; This ratio is determined by the number of coil turns. So if, for example, 100 amps are passed through the desired wire, depending on the number of small coil loops, the output current of the sensor can be something like 10 milliamps. [6] To read the output of the sensors, they are connected through a 3.5 mm jack converter to the pins of the microcontroller that have ADC capability. Since we are going to be working with city AC power, we can calculate the RMS current and voltage using readings from several sensor outputs. The point that should be noted is that the ADC output is a positive number between 0 and n depending on its resolution, and therefore it is necessary to find the middle of the sine wave corresponding to the electric current and subtract it from the whole wave to get our numbers. around 0. This number to be subtracted is known as the DC offset, which is shown below. [7]

<img src="https://user-images.githubusercont

Core symbols most depended-on inside this repo

now
called by 8
src/main.rs
add_f32_to_buf
called by 5
src/utils.rs
set_system_time
called by 2
src/main.rs
find_newest_readings_shard_num
called by 2
src/ct.rs
store_time
called by 2
src/ct.rs
add_u16_to_buf
called by 1
src/utils.rs
add_u64_to_buf
called by 1
src/utils.rs
init_littlefs_storage
called by 1
src/main.rs

Shape

Method 17
Function 16
Class 5

Languages

Rust100%

Modules by API surface

src/ct.rs22 symbols
src/main.rs10 symbols
src/utils.rs3 symbols
src/ota.rs2 symbols
build.rs1 symbols

For agents

$ claude mcp add OFMon \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact