From 34154d716d57da87010c60cd57d5489b18feed1f Mon Sep 17 00:00:00 2001 From: Simon Milvert Date: Mon, 25 May 2026 21:49:33 +0200 Subject: [PATCH] Adding mock for Home Assisant --- reciver/platformio.ini | 3 +- reciver/src/lorahandler.cpp | 158 +++++++++++++++++++++++++++++------- reciver/src/lorahandler.hpp | 9 +- reciver/src/main.cpp | 57 +++++++++---- reciver/src/mqtt.cpp | 129 +++++++++++++++++++++++++++-- reciver/src/mqtt.hpp | 7 ++ 6 files changed, 305 insertions(+), 58 deletions(-) diff --git a/reciver/platformio.ini b/reciver/platformio.ini index 388648d..33d8688 100644 --- a/reciver/platformio.ini +++ b/reciver/platformio.ini @@ -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 diff --git a/reciver/src/lorahandler.cpp b/reciver/src/lorahandler.cpp index 476b5b9..7ad0e37 100644 --- a/reciver/src/lorahandler.cpp +++ b/reciver/src/lorahandler.cpp @@ -1,54 +1,152 @@ #include "config.hpp" #include "lorahandler.hpp" #include "logger.h" +#include 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 } \ No newline at end of file diff --git a/reciver/src/lorahandler.hpp b/reciver/src/lorahandler.hpp index 8b5a1a3..9d19ea3 100644 --- a/reciver/src/lorahandler.hpp +++ b/reciver/src/lorahandler.hpp @@ -2,8 +2,7 @@ #define LORAHANDLER_H #include "config.hpp" -#include -#include +#include #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 */ \ No newline at end of file diff --git a/reciver/src/main.cpp b/reciver/src/main.cpp index 60ec006..fb68d1c 100644 --- a/reciver/src/main.cpp +++ b/reciver/src/main.cpp @@ -6,6 +6,7 @@ #include "lorahandler.hpp" #include "display.hpp" #include +#include 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 diff --git a/reciver/src/mqtt.cpp b/reciver/src/mqtt.cpp index 4842ff9..c7b64e5 100644 --- a/reciver/src/mqtt.cpp +++ b/reciver/src/mqtt.cpp @@ -2,13 +2,15 @@ #include "config.hpp" #include #include "logger.h" +#include 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(); + 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(); + 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(); + 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); } diff --git a/reciver/src/mqtt.hpp b/reciver/src/mqtt.hpp index c7974db..7d64f26 100644 --- a/reciver/src/mqtt.hpp +++ b/reciver/src/mqtt.hpp @@ -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 */ \ No newline at end of file