So I made an app for a camper-van... It has seen daily use for close to a year and honestly I'm quite impressed. It's based on MQTT protocol, which provides good ability to handle power and network outages and includes state persistence and restoration. The stack is quite lean for the amount of features and has been able to grow well while maintaining reasonable complexity.
After some experimentation with existing solutions like OpenHAB, Home Assistant, ESPHome and even Apple Home via Homebridge I came to the irresponsible decision to try and do it myself. While these open source projects are undeniably easier to get started with, I was finding that the added layer of abstraction and use of DSLs made debugging rather tricky, beyond certain scale.





The core of the system is a single board computer running an open source broker Mosquitto. The SBC provides a dedicated WiFi access point with static IP allocation, to allow over the air updates. To provide some separation of concerns, the main functions are split between few dedicated clients. The Internet connectivity if enabled by Mikrotik's LtAP mini. With the recent addition of MQTT support in RouterOS, I was also able to integrate GPS, and LTE by writing few simple scripts


Source of Truth

Since a lot is happening asynchronously, it's important to have a single source of truth. What it means in practice is that clients stateless, and does not retain readings for later publishing. They simply receive commands, update external devices, and report the state to the broker. Clients also fetch the last retained state from broker on startup.
This, combined with last will feature ensures that broker has up-to-date view of the system.

Data Flow

Here's a simple toy example to illustrate data flow.
Rounded boxes represent clients while squared ones contain topics:

Naive Approach

Naive Approach - Here the dimmer has two-way binding to the room/light topic and the Light client is simply consuming this state. While this setup mostly works, there are many cases where the state of the light could get out of sync. It could be dropped message or multiple instances of dimmer controlling the same light.


Unidirectional Data Flow - MQTT Topics work on publish/subscribe principle and this makes them a two way street. To achieve unidirectional data flow my approach was separating the command topics (not retained) from those containing state (retained). Any client might only read or write to a certain topic. Now the state of the light originates at it's physical location - the light client. This also prevents potential feedback loops.



Most clients were implemented using ESP development boards. The one above is responsible for managing the battery. It's built on a standard 100x100mm prototype PCB using common brake-out boards. The client has a serial connection to the solar charge controller using Victron's VE.Direct for reporting solar readings. It also measures battery current using a shunt. The state of charge is retained in the broker, for preservation between reboots.
Since I'm using LiFePO4 batteries that would get damaged, if charged below freezing - the client also operates a heating pad using PWM based on temperature readings.


While I could go into way more detail, hopefully this article provides a decent high level overview of one of many ways to approach building a system like this. Overall the process has been a remarkable learning experience and my go-to justification for trying new things. This project involved broad range or technologies and tools such as 3d printing, PBC design/assembly, Rust, C++, Time Series Databases, SwiftUI, etc.
While it certainly has been a significant time investment, if feel that system like this is generic enough with sufficiently wide range of applications to justify time spent. (Or at least that's what I tell myself)

Comic XKCD Comic