Adding mock for Home Assisant

This commit is contained in:
Simon Milvert 2026-05-25 21:49:33 +02:00
parent c9dad05631
commit 34154d716d
6 changed files with 305 additions and 58 deletions

View File

@ -15,10 +15,11 @@ framework = arduino
monitor_speed = 115200
build_flags =
-D DEBUG
-D MOCK_LORA
lib_deps =
https://github.com/tzapu/WiFiManager.git
bblanchon/ArduinoJson@^7.0.4
peterus/esp-logger@^1.0.0
knolleary/PubSubClient@^2.8
sandeepmistry/LoRa@^0.8.0
jgromes/RadioLib@^6.0.0
thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.5.0

View File

@ -1,54 +1,152 @@
#include "config.hpp"
#include "lorahandler.hpp"
#include "logger.h"
#include <ArduinoJson.h>
extern logging::Logger logger;
extern Config config;
LoraHandler::LoraHandler() {
// Initialize with SX1278 radio
}
void LoraHandler::setup()
{
#ifdef MOCK_LORA
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "Running in MOCK MODE - simulating LoRa reception!");
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "Mock data will be generated every 10 calls");
return;
#endif
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "Set SPI pins!");
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
long freq = config.loraConfig.frequency;
if (!LoRa.begin(freq))
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "Initializing SX1278 with RadioLib!");
// Create new SPI instance for LoRa
spi = new SPIClass(VSPI);
spi->begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
// Create SX1278 instance
radio = new SX1278(new SPIDriver(spi, LORA_CS));
// Configure RXEN/TXEN pins
pinMode(LORA_RXEN, OUTPUT);
pinMode(LORA_TXEN, OUTPUT);
digitalWrite(LORA_RXEN, LOW);
digitalWrite(LORA_TXEN, LOW);
// Initialize LoRa module
int state = radio->begin();
if (state != RADIOLIB_ERR_NONE)
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa", "Starting LoRa failed!");
// show_display("ERROR", "Starting LoRa failed!");
while (true)
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa", "LoRa init failed: %i", state);
while (true) {
delay(1000);
}
}
LoRa.setSpreadingFactor(config.loraConfig.spreadingFactor);
LoRa.setSignalBandwidth(config.loraConfig.signalBandwidth);
LoRa.setCodingRate4(config.loraConfig.codingRate4);
LoRa.enableCrc();
LoRa.setTxPower(config.loraConfig.power);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "LoRa init done!");
String currentLoRainfo = "LoRa Freq: " + String(config.loraConfig.frequency) + " / SF:" + String(config.loraConfig.spreadingFactor) + " / CR: " + String(config.loraConfig.codingRate4);
// Configure LoRa parameters
long freq = config.loraConfig.frequency;
state = radio->setFrequency(freq);
if (state != RADIOLIB_ERR_NONE)
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa", "setFrequency failed: %i", state);
}
state = radio->setSpreadingFactor(config.loraConfig.spreadingFactor);
if (state != RADIOLIB_ERR_NONE)
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa", "setSpreadingFactor failed: %i", state);
}
state = radio->setBandwidth(config.loraConfig.signalBandwidth);
if (state != RADIOLIB_ERR_NONE)
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa", "setBandwidth failed: %i", state);
}
state = radio->setCodingRate(config.loraConfig.codingRate4);
if (state != RADIOLIB_ERR_NONE)
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa", "setCodingRate failed: %i", state);
}
state = radio->setOutputPower(config.loraConfig.power);
if (state != RADIOLIB_ERR_NONE)
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa", "setOutputPower failed: %i", state);
}
// Enable CRC
radio->setCRC(true);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "SX1278 initialized successfully!");
String currentLoRainfo = "LoRa Freq: " + String(freq) + " / SF:" + String(config.loraConfig.spreadingFactor) + " / CR: " + String(config.loraConfig.codingRate4);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", currentLoRainfo.c_str());
}
ReceivedLoRaPacket LoraHandler::receivePacket()
{
ReceivedLoRaPacket receivedLoraPacket;
String packet = "";
int packetSize = LoRa.parsePacket();
if (packetSize)
#ifdef MOCK_LORA
// Mock mode - simulate LoRa reception every 10 calls
mockCounter++;
if (mockCounter >= 10)
{
while (LoRa.available())
{
int inChar = LoRa.read();
packet += (char)inChar;
}
receivedLoraPacket.text = packet;
receivedLoraPacket.rssi = LoRa.packetRssi();
receivedLoraPacket.snr = LoRa.packetSnr();
receivedLoraPacket.freqError = LoRa.packetFrequencyError();
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa Rx", "---> %s", packet.c_str());
mockCounter = 0;
// Create mock sensor data from sender
float temp = 22.5 + (random(0, 50) / 100.0); // 22.5 - 23.0°C
int battery = 75 + random(0, 20); // 75-95%
int boot = 5;
int update = 1;
// Build JSON payload like sender would send
JsonDocument doc;
doc["temp"] = temp;
doc["battery"] = battery;
doc["boot"] = boot;
doc["update"] = update;
serializeJson(doc, receivedLoraPacket.text);
// Simulate signal parameters
receivedLoraPacket.rssi = -85 + random(0, 30); // -85 to -55 dBm
receivedLoraPacket.snr = 5.0 + (random(0, 100) / 100.0); // 5.0 to 6.0 dB
receivedLoraPacket.freqError = random(-1000, 1000);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa Rx [MOCK]", "---> %s (RSSI: %d, SNR: %.1f)",
receivedLoraPacket.text.c_str(), receivedLoraPacket.rssi, receivedLoraPacket.snr);
}
return receivedLoraPacket;
#else
// Real hardware mode
// Enable RX mode
digitalWrite(LORA_RXEN, HIGH);
digitalWrite(LORA_TXEN, LOW);
int state = radio->receive(0); // 0 = non-blocking
if (state == RADIOLIB_ERR_RX_ONGOING)
{
// No packet received yet
return receivedLoraPacket;
}
else if (state == RADIOLIB_ERR_NONE)
{
// Packet received!
receivedLoraPacket.text = radio->readData();
receivedLoraPacket.rssi = radio->getRSSI();
receivedLoraPacket.snr = radio->getSNR();
receivedLoraPacket.freqError = radio->getFrequencyError();
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa Rx", "---> %s (RSSI: %d, SNR: %.1f)",
receivedLoraPacket.text.c_str(), receivedLoraPacket.rssi, receivedLoraPacket.snr);
}
else
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa Rx", "Receive error: %i", state);
}
return receivedLoraPacket;
#endif
}

View File

@ -2,8 +2,7 @@
#define LORAHANDLER_H
#include "config.hpp"
#include <SPI.h>
#include <LoRa.h>
#include <RadioLib.h>
#define LORA_SCK 5
#define LORA_MISO 19
@ -11,6 +10,8 @@
#define LORA_CS 18 // CS --> NSS
#define LORA_RST 14
#define LORA_IRQ 26 // IRQ --> DIO0
#define LORA_RXEN 2 // RX Enable
#define LORA_TXEN 13 // TX Enable
struct ReceivedLoRaPacket {
String text;
@ -21,9 +22,13 @@ struct ReceivedLoRaPacket {
class LoraHandler {
public:
LoraHandler();
void setup();
ReceivedLoRaPacket receivePacket();
private:
SX1278* radio;
SPIClass* spi;
int mockCounter = 0;
};
#endif /* LORAHANDLER_H */

View File

@ -6,6 +6,7 @@
#include "lorahandler.hpp"
#include "display.hpp"
#include <WiFi.h>
#include <ArduinoJson.h>
Config config;
WiFiServer server(80);
@ -51,10 +52,36 @@ void setup()
void loop()
{
ReceivedLoRaPacket packet = lora.receivePacket();
if (packet.text.isEmpty())
// Process received LoRa packet
if (!packet.text.isEmpty())
{
packet.rssi = 69;
packet.snr = 3.3;
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MAIN", "Processing LoRa packet: %s", packet.text.c_str());
// Try to parse JSON payload from sender
JsonDocument doc;
DeserializationError error = deserializeJson(doc, packet.text);
if (!error) {
float temp = doc["temp"] | 0.0;
int battery = doc["battery"] | 0;
int boot = doc["boot"] | 0;
int update = doc["update"] | 0;
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MAIN", "Parsed - Temp: %.2f, Battery: %d, Boot: %d, Update: %d", temp, battery, boot, update);
// Publish to MQTT with Home Assistant format
if (WiFi.status() == WL_CONNECTED) {
mqtt.publishSensorData(temp, packet.rssi, battery);
}
} else {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, "MAIN", "Failed to parse JSON: %s", error.c_str());
}
}
else
{
packet.rssi = 0;
packet.snr = 0.0;
}
if (WiFi.status() == WL_CONNECTED) { // Check WiFi connection
@ -65,26 +92,22 @@ void loop()
// Optional: Display message indicating WiFi not connected
display.show_message("WiFi not connected", 0);
}
int mqtt_pkt = 123455;
String line1 = "Mqtt pkt: " + String(mqtt_pkt);
String line1 = "LoRa RSSI: " + String(packet.rssi) + " dBm";
display.show_message(line1.c_str(), 1);
String line2 = "Last lora, rssi: " + String(packet.rssi ) ;
String line2 = "SNR: " + String(packet.snr, 1) + " dB";
display.show_message(line2.c_str(), 2);
display.show_message("Fourth", 3);
display.show_message("Fifth", 4);
sleep(5);
/*
display.show_message("Data received OK", 3);
// Keep MQTT connection alive
mqtt.mqttRun();
if (portalRunning)
{
wm.process();
}
mqtt.mqttPublish("test/sensor", "123");
checkButton();
sleep(10);*/
sleep(5);
}
void checkButton()
{
// check for button press

View File

@ -2,13 +2,15 @@
#include "config.hpp"
#include <PubSubClient.h>
#include "logger.h"
#include <ArduinoJson.h>
extern logging::Logger logger;
extern Config config;
extern Config config;
Mqtt::Mqtt(WiFiClient& wifiClient) : mqttClient(wifiClient) {}
Mqtt::Mqtt(WiFiClient& wifiClient) : mqttClient(wifiClient) {
deviceId = "water_temp_receiver";
baseTopic = "homeassistant/sensor/water_temp";
}
void Mqtt::mqttSetup()
{
@ -16,7 +18,6 @@ void Mqtt::mqttSetup()
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", message.c_str());
if (config.mqttConfig.server.length() > 0)
{
// strcpy(mqttHost, config.mqtt.server.c_str());
String message = "MqttServer: " + config.mqttConfig.server + ", MqttPort: " + config.mqttConfig.port;
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", message.c_str());
IPAddress mqttServerIP;
@ -68,12 +69,15 @@ void Mqtt::mqttReconnect()
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT CONFIG", "Server: %s", config.mqttConfig.server.c_str());
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT CONFIG", "Port: %d", config.mqttConfig.port);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT CONFIG", "Username: %s", config.mqttConfig.username.c_str());
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT CONFIG", "Password: %s", config.mqttConfig.password.c_str());
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT CONFIG", "ID: %s", config.mqttConfig.id.c_str());
if (mqttClient.connect(mqttId.c_str(), "simon", "bajsa123"))
if (mqttClient.connect(mqttId.c_str(), config.mqttConfig.username.c_str(), config.mqttConfig.password.c_str()))
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "connected");
// Setup Home Assistant Discovery on successful connection
setupHomeAssistantDiscovery();
String topic = config.mqttConfig.topic;
if (topic.isEmpty())
{
@ -97,6 +101,117 @@ void Mqtt::mqttReconnect()
}
}
void Mqtt::setupHomeAssistantDiscovery()
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Setting up Home Assistant MQTT Discovery");
// Temperature sensor discovery
{
JsonDocument doc;
doc["name"] = "Water Temperature";
doc["unique_id"] = "water_temp_temperature";
doc["unit_of_measurement"] = "°C";
doc["device_class"] = "temperature";
doc["state_topic"] = baseTopic + "/temperature";
doc["availability_topic"] = baseTopic + "/availability";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = deviceId;
device["name"] = "Water Temperature Sensor";
device["model"] = "Water Temp v1";
device["manufacturer"] = "DIY";
String payload;
serializeJson(doc, payload);
String topic = baseTopic + "/temperature/config";
mqttPublish(topic.c_str(), payload.c_str());
}
// RSSI sensor discovery
{
JsonDocument doc;
doc["name"] = "LoRa RSSI";
doc["unique_id"] = "water_temp_rssi";
doc["unit_of_measurement"] = "dBm";
doc["device_class"] = "signal_strength";
doc["state_topic"] = baseTopic + "/rssi";
doc["availability_topic"] = baseTopic + "/availability";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = deviceId;
String payload;
serializeJson(doc, payload);
String topic = baseTopic + "/rssi/config";
mqttPublish(topic.c_str(), payload.c_str());
}
// Battery level discovery
{
JsonDocument doc;
doc["name"] = "Sender Battery";
doc["unique_id"] = "water_temp_battery";
doc["unit_of_measurement"] = "%";
doc["device_class"] = "battery";
doc["state_topic"] = baseTopic + "/battery";
doc["availability_topic"] = baseTopic + "/availability";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = deviceId;
String payload;
serializeJson(doc, payload);
String topic = baseTopic + "/battery/config";
mqttPublish(topic.c_str(), payload.c_str());
}
// Set availability to online
String availTopic = baseTopic + "/availability";
mqttPublish(availTopic.c_str(), "online");
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Home Assistant Discovery setup complete");
}
void Mqtt::publishTemperature(float temp)
{
String topic = baseTopic + "/temperature";
String payload = String(temp, 2);
mqttPublish(topic.c_str(), payload.c_str());
}
void Mqtt::publishRSSI(int rssi)
{
String topic = baseTopic + "/rssi";
String payload = String(rssi);
mqttPublish(topic.c_str(), payload.c_str());
}
void Mqtt::publishSensorData(float temp, int rssi, int battery)
{
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Publishing sensor data - Temp: %.2f, RSSI: %d, Battery: %d", temp, rssi, battery);
publishTemperature(temp);
publishRSSI(rssi);
String topic = baseTopic + "/battery";
String payload = String(battery);
mqttPublish(topic.c_str(), payload.c_str());
// Also publish combined JSON
{
JsonDocument doc;
doc["temperature"] = serialized(String(temp, 2));
doc["rssi"] = rssi;
doc["battery"] = battery;
doc["timestamp"] = millis();
String jsonPayload;
serializeJson(doc, jsonPayload);
String jsonTopic = baseTopic + "/state";
mqttPublish(jsonTopic.c_str(), jsonPayload.c_str());
}
}
void Mqtt::callback(char *topic, uint8_t *payload, unsigned int length)
// Not implemented yet. Just for debug
{
@ -107,8 +222,6 @@ void Mqtt::callback(char *topic, uint8_t *payload, unsigned int length)
// Nullify last character to eliminate garbage at end
message[length] = '\0';
// Create correct object
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Received: %s", message);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "From: %s", topic);
}

View File

@ -13,10 +13,17 @@ class Mqtt {
void mqttSetup();
void mqttRun();
void mqttPublish(const char* topic, const char* payload);
void publishTemperature(float temp);
void publishRSSI(int rssi);
void publishSensorData(float temp, int rssi, int battery);
void publishDiscoveryConfig();
private:
void mqttReconnect();
void setupHomeAssistantDiscovery();
static void callback(char *topic, uint8_t *payload, unsigned int length);
PubSubClient mqttClient;
String deviceId;
String baseTopic;
};
#endif /* MQTT_H */