From dab36592a6ea53e3e048e9726ed1d1c640e0501e Mon Sep 17 00:00:00 2001 From: Simon Milvert Date: Sat, 30 May 2026 14:23:58 +0200 Subject: [PATCH] Works with HA --- .gitignore | 1 + reciver/platformio.ini | 1 + reciver/src/lorahandler.cpp | 12 +--- reciver/src/lorahandler.hpp | 2 - reciver/src/main.cpp | 96 ++++++++++++------------- reciver/src/mqtt.cpp | 137 ++++++++++++++++++++---------------- reciver/src/mqtt.hpp | 2 + 7 files changed, 129 insertions(+), 122 deletions(-) diff --git a/.gitignore b/.gitignore index ef06222..87c6585 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ *.exe *.out *.app +schematic/water_temp/water_temp-backups/* diff --git a/reciver/platformio.ini b/reciver/platformio.ini index 33d8688..3bbb2cb 100644 --- a/reciver/platformio.ini +++ b/reciver/platformio.ini @@ -16,6 +16,7 @@ monitor_speed = 115200 build_flags = -D DEBUG -D MOCK_LORA + -D MQTT_MAX_PACKET_SIZE=1024 lib_deps = https://github.com/tzapu/WiFiManager.git bblanchon/ArduinoJson@^7.0.4 diff --git a/reciver/src/lorahandler.cpp b/reciver/src/lorahandler.cpp index 7ad0e37..dcd5ccf 100644 --- a/reciver/src/lorahandler.cpp +++ b/reciver/src/lorahandler.cpp @@ -24,14 +24,9 @@ void LoraHandler::setup() 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); + // --- FIX APPLIED HERE --- + Module* mod = new Module(LORA_CS, LORA_IRQ, LORA_RST, RADIOLIB_NC, *spi); + radio = new SX1278(mod); // Initialize LoRa module int state = radio->begin(); @@ -82,7 +77,6 @@ void LoraHandler::setup() 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; diff --git a/reciver/src/lorahandler.hpp b/reciver/src/lorahandler.hpp index 9d19ea3..ea13db8 100644 --- a/reciver/src/lorahandler.hpp +++ b/reciver/src/lorahandler.hpp @@ -10,8 +10,6 @@ #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; diff --git a/reciver/src/main.cpp b/reciver/src/main.cpp index fb68d1c..62feca7 100644 --- a/reciver/src/main.cpp +++ b/reciver/src/main.cpp @@ -51,63 +51,55 @@ void setup() void loop() { - ReceivedLoRaPacket packet = lora.receivePacket(); - - // Process received LoRa packet - if (!packet.text.isEmpty()) - { - 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; - } + ReceivedLoRaPacket packet = lora.receivePacket(); + + if (!packet.text.isEmpty()) + { + logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MAIN", "Processing LoRa packet: %s", packet.text.c_str()); + + 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); + + if (WiFi.status() == WL_CONNECTED) { + mqtt.publishSensorData(temp, packet.rssi, battery); + } - if (WiFi.status() == WL_CONNECTED) { // Check WiFi connection - IPAddress ip = WiFi.localIP(); - String line0 = "IP: " + ip.toString(); - display.show_message(line0.c_str(), 0); - } else { - // Optional: Display message indicating WiFi not connected - display.show_message("WiFi not connected", 0); + if (WiFi.status() == WL_CONNECTED) { + IPAddress ip = WiFi.localIP(); + String line0 = "IP: " + ip.toString(); + display.show_message(line0.c_str(), 0); + } else { + display.show_message("WiFi not connected", 0); + } + + String line1 = "LoRa RSSI: " + String(packet.rssi) + " dBm"; + display.show_message(line1.c_str(), 1); + + String line2 = "SNR: " + String(packet.snr, 1) + " dB"; + display.show_message(line2.c_str(), 2); + + display.show_message("Data received OK", 3); + } else { + logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, "MAIN", "Failed to parse JSON: %s", error.c_str()); + } } - - String line1 = "LoRa RSSI: " + String(packet.rssi) + " dBm"; - display.show_message(line1.c_str(), 1); - - String line2 = "SNR: " + String(packet.snr, 1) + " dB"; - display.show_message(line2.c_str(), 2); - - display.show_message("Data received OK", 3); - - // Keep MQTT connection alive + + // Kör MQTT-klientens loop mqtt.mqttRun(); - sleep(5); + // FIX: Ändra från 100ms till 1000ms (1 sekund). + // Detta förhindrar att ESP32:an kraschar/tappar WiFi av logg-spamming, + // men är tillräckligt snabbt för att inte missa LoRa-paket. + delay(1000); } - - void checkButton() { // check for button press diff --git a/reciver/src/mqtt.cpp b/reciver/src/mqtt.cpp index c7b64e5..544bed5 100644 --- a/reciver/src/mqtt.cpp +++ b/reciver/src/mqtt.cpp @@ -9,21 +9,19 @@ extern Config config; Mqtt::Mqtt(WiFiClient& wifiClient) : mqttClient(wifiClient) { deviceId = "water_temp_receiver"; - baseTopic = "homeassistant/sensor/water_temp"; + // FIX 1: Låt bas-topic ligga helt fritt på roten, UTAN "homeassistant/". + // Det är hit din data (temperatur, rssi, batteri) kommer att skickas. + baseTopic = "water_temp_receiver"; } void Mqtt::mqttSetup() { - String message = "MqttServer: " + config.mqttConfig.server + ", MqttPort: " + config.mqttConfig.port; - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", message.c_str()); if (config.mqttConfig.server.length() > 0) { String message = "MqttServer: " + config.mqttConfig.server + ", MqttPort: " + config.mqttConfig.port; logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", message.c_str()); IPAddress mqttServerIP; if(mqttServerIP.fromString(config.mqttConfig.server)){ - - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", mqttServerIP.toString().c_str()); mqttClient.setServer(mqttServerIP, config.mqttConfig.port); }else{ logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "MQTT", "MQTT Invalid IP address format %s", config.mqttConfig.server); @@ -48,34 +46,22 @@ void Mqtt::mqttRun() void Mqtt::mqttReconnect() { int retryCount = 0; - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Attempting MQTT connection..."); - // Loop until we're reconnected while (!mqttClient.connected() && retryCount < 10) { - // Attempt to connect String mqttId = config.mqttConfig.id; if (mqttId.isEmpty()) { mqttId = "ESP32-"; mqttId += String(random(0xffff), HEX); - - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Set ID: %s", mqttId ); config.setMqttId(mqttId); config.writeData(); } - - 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", "ID: %s", config.mqttConfig.id.c_str()); 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; @@ -84,90 +70,120 @@ void Mqtt::mqttReconnect() topic = config.mqttConfig.id; } mqttClient.subscribe(topic.c_str()); - String message = "Subscribe to: " + topic; - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", message.c_str()); } else { logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, "MQTT", "Not connected to mqtt, try again in 5 s."); - // Wait 5 seconds before retrying delay(5000); } + retryCount++; } - // Check if we're connected after the loop if (!mqttClient.connected()) { - // Connection failed after maximum retries logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "MQTT", "Failed to connect to MQTT broker after 10 retries"); } } void Mqtt::setupHomeAssistantDiscovery() { - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Setting up Home Assistant MQTT Discovery"); + logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Setting up Home Assistant MQTT Discovery using char buffers"); - // Temperature sensor discovery + // Skapa fasta teckenbuffertar för ämnen (topics) och JSON-data + char topicBuffer[128]; + char jsonBuffer[512]; // 512 bytes räcker gott och väl för denna JSON + + // Vi låter alla sensorer lyssna på den gemensamma JSON-strängen på /state + // Bygg upp strängarna med snabba snprintf istället för dynamiska String-objekt + char sharedStateTopic[64]; + char availTopic[64]; + snprintf(sharedStateTopic, sizeof(sharedStateTopic), "%s/state", baseTopic.c_str()); + snprintf(availTopic, sizeof(availTopic), "%s/availability", baseTopic.c_str()); + + // 1. Temperature sensor discovery { - JsonDocument doc; + JsonDocument doc; // I v7 använder man bara JsonDocument, inte DynamicJsonDocument doc["name"] = "Water Temperature"; - doc["unique_id"] = "water_temp_temperature"; + + // Bygg unikt ID i en temporär buffert + char uniqueId[64]; + snprintf(uniqueId, sizeof(uniqueId), "%s_temperature", deviceId.c_str()); + doc["unique_id"] = uniqueId; + doc["unit_of_measurement"] = "°C"; doc["device_class"] = "temperature"; - doc["state_topic"] = baseTopic + "/temperature"; - doc["availability_topic"] = baseTopic + "/availability"; + doc["state_class"] = "measurement"; + doc["state_topic"] = sharedStateTopic; + doc["value_template"] = "{{ value_json.temperature }}"; + doc["availability_topic"] = availTopic; + // I ArduinoJson v7 skapar man nästlade objekt direkt så här: 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()); + // Serialisera direkt till vår fasta char-buffert + serializeJson(doc, jsonBuffer, sizeof(jsonBuffer)); + + // Bygg config-topic och publicera + snprintf(topicBuffer, sizeof(topicBuffer), "homeassistant/sensor/%s/temperature/config", deviceId.c_str()); + mqttPublishRetained(topicBuffer, jsonBuffer); } - // RSSI sensor discovery + // 2. RSSI sensor discovery { JsonDocument doc; doc["name"] = "LoRa RSSI"; - doc["unique_id"] = "water_temp_rssi"; + + char uniqueId[64]; + snprintf(uniqueId, sizeof(uniqueId), "%s_rssi", deviceId.c_str()); + doc["unique_id"] = uniqueId; + doc["unit_of_measurement"] = "dBm"; doc["device_class"] = "signal_strength"; - doc["state_topic"] = baseTopic + "/rssi"; - doc["availability_topic"] = baseTopic + "/availability"; + doc["state_class"] = "measurement"; + doc["state_topic"] = sharedStateTopic; + doc["value_template"] = "{{ value_json.rssi }}"; + doc["availability_topic"] = availTopic; JsonObject device = doc["device"].to(); device["identifiers"][0] = deviceId; + device["name"] = "Water Temperature Sensor"; - String payload; - serializeJson(doc, payload); - String topic = baseTopic + "/rssi/config"; - mqttPublish(topic.c_str(), payload.c_str()); + serializeJson(doc, jsonBuffer, sizeof(jsonBuffer)); + + snprintf(topicBuffer, sizeof(topicBuffer), "homeassistant/sensor/%s/rssi/config", deviceId.c_str()); + mqttPublishRetained(topicBuffer, jsonBuffer); } - // Battery level discovery + // 3. Battery level discovery { JsonDocument doc; doc["name"] = "Sender Battery"; - doc["unique_id"] = "water_temp_battery"; + + char uniqueId[64]; + snprintf(uniqueId, sizeof(uniqueId), "%s_battery", deviceId.c_str()); + doc["unique_id"] = uniqueId; + doc["unit_of_measurement"] = "%"; doc["device_class"] = "battery"; - doc["state_topic"] = baseTopic + "/battery"; - doc["availability_topic"] = baseTopic + "/availability"; + doc["state_class"] = "measurement"; + doc["state_topic"] = sharedStateTopic; + doc["value_template"] = "{{ value_json.battery }}"; + doc["availability_topic"] = availTopic; JsonObject device = doc["device"].to(); device["identifiers"][0] = deviceId; + device["name"] = "Water Temperature Sensor"; - String payload; - serializeJson(doc, payload); - String topic = baseTopic + "/battery/config"; - mqttPublish(topic.c_str(), payload.c_str()); + serializeJson(doc, jsonBuffer, sizeof(jsonBuffer)); + + snprintf(topicBuffer, sizeof(topicBuffer), "homeassistant/sensor/%s/battery/config", deviceId.c_str()); + mqttPublishRetained(topicBuffer, jsonBuffer); } - // Set availability to online - String availTopic = baseTopic + "/availability"; - mqttPublish(availTopic.c_str(), "online"); + // Sätt enheten till "online" med retain + mqttPublishRetained(availTopic, "online"); logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Home Assistant Discovery setup complete"); } @@ -197,7 +213,7 @@ void Mqtt::publishSensorData(float temp, int rssi, int battery) String payload = String(battery); mqttPublish(topic.c_str(), payload.c_str()); - // Also publish combined JSON + // Kombinerad JSON-statustopic { JsonDocument doc; doc["temperature"] = serialized(String(temp, 2)); @@ -213,17 +229,11 @@ void Mqtt::publishSensorData(float temp, int rssi, int battery) } void Mqtt::callback(char *topic, uint8_t *payload, unsigned int length) -// Not implemented yet. Just for debug { - // Create spot in memory for message char message[length + 1]; - // Write payload to String strncpy(message, (char *)payload, length); - // Nullify last character to eliminate garbage at end message[length] = '\0'; - - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Received: %s", message); - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "From: %s", topic); + logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Received: %s From: %s", message, topic); } void Mqtt::mqttPublish(const char* topic, const char* payload){ @@ -234,3 +244,12 @@ void Mqtt::mqttPublish(const char* topic, const char* payload){ logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "MQTT", "Failed to publish message. MQTT client not connected."); } } + +void Mqtt::mqttPublishRetained(const char* topic, const char* payload) { + if (mqttClient.connected()) { + mqttClient.publish(topic, payload, true); + logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "MQTT", "Published RETAINED message to %s", topic); + } else { + logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "MQTT", "Failed to publish retained message. Not connected."); + } +} \ No newline at end of file diff --git a/reciver/src/mqtt.hpp b/reciver/src/mqtt.hpp index 7d64f26..491af5d 100644 --- a/reciver/src/mqtt.hpp +++ b/reciver/src/mqtt.hpp @@ -4,6 +4,7 @@ #include "wifi.hpp" #include "config.hpp" #include +#define MQTT_MAX_PACKET_SIZE 1024 #include @@ -13,6 +14,7 @@ class Mqtt { void mqttSetup(); void mqttRun(); void mqttPublish(const char* topic, const char* payload); + void mqttPublishRetained(const char* topic, const char* payload); void publishTemperature(float temp); void publishRSSI(int rssi); void publishSensorData(float temp, int rssi, int battery);