#ifndef MQTT_H #define MQTT_H #include #include #include const char* mqtt_server = "broker.emqx.io"; const int mqtt_port = 1883; // Topics const char* mqtt_topic_state = "ups/dev15/state"; const char* mqtt_topic_cmd = "ups/dev15/cmd"; WiFiClient espClient; PubSubClient mqttClient(espClient); unsigned long lastMqttReconnectAttempt = 0; unsigned long lastMqttPublish = 0; const unsigned long mqttPublishInterval = 2000; // Publish state every 2 seconds // Forward declaration void mqttPublishState(); void mqttCallback(char* topic, byte* payload, unsigned int length) { Serial.print("Message arrived on topic: "); Serial.print(topic); Serial.print(". Message: "); String messageTemp; for (int i = 0; i < length; i++) { Serial.print((char)payload[i]); messageTemp += (char)payload[i]; } Serial.println(); // Parse command if (String(topic) == mqtt_topic_cmd) { DynamicJsonDocument doc(256); DeserializationError error = deserializeJson(doc, payload, length); if (!error) { String cmd = doc["cmd"].as(); String v = doc["val"].as(); bool updated = false; if (cmd == "on") { bool tgtState = v != "0"; if (tgtState != powerOutputActive) { // This function is defined in upsSvr.h / main logic // Note: mqtt.h is included after or before? Need to ensure it can call setOutputPowerState // Actually, setOutputPowerState is declared in upsCfg.h maybe? // If compilation fails, we can just set the flag. Wait, let's use the function since it's global. setOutputPowerState(tgtState); updated = true; } } else if (cmd == "auto") { uint8_t tmpAutoOnMode = v.toInt(); if ((tmpAutoOnMode != cfgAutoOn) && (tmpAutoOnMode <= 2)) { cfgAutoOn = tmpAutoOnMode; configChanged = true; updated = true; } } else if (cmd == "shutdown") { uint16_t tSec = v.toInt(); if (tSec > 999) tSec = 999; uint32_t nanoT = ((uint32_t)tSec) * 1000000UL; if (nanoT < 999999) nanoT = 999999; bool ok = setPersistentShutdown(nanoT); if (cfgShutdownInSeconds != tSec && ok) { cfgShutdownInSeconds = tSec; configChanged = true; updated = true; } } else if (cmd == "shutlv") { int16_t lv = v.toInt(); if (lv >= 0 && lv <= 5) { if (lv != cfgShutdownSuggestion) { cfgShutdownSuggestion = lv; configChanged = true; updated = true; } } } else if (cmd == "cm") { int16_t i = v.toInt(); if (i >= 0 && i <= 9) { cfgChargeMode = i; configChanged = true; updated = true; } } else if (cmd == "coil") { int16_t i = v.toInt(); if (i >= 0 && i <= 5) { cfgInputLostTriggerChargeMode = i; configChanged = true; updated = true; } } else if (cmd == "covp") { int16_t i = v.toInt(); if (i > 1) i = 1; if (i != cfgChargeOvervoltageProtection) { cfgChargeOvervoltageProtection = i; configChanged = true; updated = true; } } else if (cmd == "rofc") { int16_t i = v.toInt(); if (i > 0 && needFullChargeOnce) { resetOnGoingFullChargeAfterLineLost(); updated = true; } } else if (cmd == "car") { int16_t i = v.toInt(); if (i > 1) i = 1; if (i != cfgChargeAutomaticReducer) { cfgChargeAutomaticReducer = i; configChanged = true; updated = true; } } if (updated) { mqttPublishState(); } } } } boolean mqttReconnect() { String clientId = "UPS_DEV15_" + String(random(0xffff), HEX); Serial.print("Attempting MQTT connection as "); Serial.println(clientId); if (mqttClient.connect(clientId.c_str())) { Serial.println("MQTT Connected"); // Once connected, publish an announcement... mqttClient.publish("ups/dev15/status", "online"); // ... and resubscribe mqttClient.subscribe(mqtt_topic_cmd); } else { Serial.print("failed, rc="); Serial.print(mqttClient.state()); Serial.println(" try again in 5 seconds"); } return mqttClient.connected(); } void mqttInit() { mqttClient.setServer(mqtt_server, mqtt_port); mqttClient.setBufferSize(1024); // Increase buffer size for large JSON payloads mqttClient.setCallback(mqttCallback); } void mqttPublishState() { DynamicJsonDocument jsonDoc(1024); String jsonResponse; uint8_t lineActiveState = isUpsPoweredFromBattery() ? 2 : isInputLinePowered() ? 1 : isUpsOn() ? 2 : 0; jsonDoc["success"] = 2; jsonDoc["on"] = powerOutputActive ? 1 : 0; jsonDoc["state"] = isUpsOn() ? 1 : 0; jsonDoc["v"] = String(batteryVoltage, 2); jsonDoc["batt"] = batteryLevel; jsonDoc["battP"] = batteryPercentage; jsonDoc["line"] = lineActiveState; jsonDoc["uptime"] = millis() / 1000; jsonDoc["auto"] = cfgAutoOn; jsonDoc["battBlink"] = isUpsBatteryIndicatorChargingOrBlink() ? 1 : 0; jsonDoc["battCritical"] = isUpsBatteryCritical() ? 1 : 0; jsonDoc["chargeLv"] = getRawBatteryLevelFromPercentage(batteryPercentage); jsonDoc["battMax"] = 4; jsonDoc["timedShutdown"] = (persistentShutdown && powerOutputActive) ? 1 : 0; jsonDoc["shutdownRemain"] = getPersistentShutdownTick() / 1000000; jsonDoc["shutdownSuggestMode"] = cfgShutdownSuggestion; jsonDoc["shutdownSuggest"] = getShutdownSuggestionFlag() ? 1 : 0; jsonDoc["shutLastTimer"] = cfgShutdownInSeconds; jsonDoc["chgError"] = chargeError; jsonDoc["charging"] = getChargingState(); jsonDoc["chgMode"] = cfgChargeMode; jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; jsonDoc["chgReducer"] = cfgChargeAutomaticReducer; jsonDoc["chgFullTrig"] = cfgInputLostTriggerChargeMode; jsonDoc["chgOFC"] = needFullChargeOnce ? 1 : 0; serializeJson(jsonDoc, jsonResponse); mqttClient.publish(mqtt_topic_state, jsonResponse.c_str()); } void mqttRoutine() { if (!mqttClient.connected()) { long now = millis(); if (now - lastMqttReconnectAttempt > 5000) { lastMqttReconnectAttempt = now; // Attempt to reconnect if (mqttReconnect()) { lastMqttReconnectAttempt = 0; } } } else { mqttClient.loop(); // Publish state periodically long now = millis(); if (now - lastMqttPublish > mqttPublishInterval) { lastMqttPublish = now; mqttPublishState(); } } } #endif