/* Theengs OpenMQTTGateway - We Unite Sensors in One Open-Source Interface Act as a gateway between your 433mhz, infrared IR, BLE, LoRa signal and one interface like an MQTT broker Send and receiving command by MQTT Output GPIO defined to High or Low Copyright: (c)Florian ROBERT Contributors: - 1technophile This file is part of OpenMQTTGateway. OpenMQTTGateway is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenMQTTGateway is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "User_config.h" #ifdef ZactuatorONOFF # include "LEDManager.h" # include "TheengsCommon.h" # include "config_ONOFF.h" extern LEDManager ledManager; extern void ActuatorTrigger(); # ifdef ESP32 // Global struct to store live ONOFF configuration data ONOFFConfig_s ONOFFConfig; void ONOFFConfig_init() { ONOFFConfig.ONOFFState = !ACTUATOR_ON; ONOFFConfig.useLastStateOnStart = USE_LAST_STATE_ON_RESTART; } void ONOFFConfig_fromJson(JsonObject& ONOFFdata) { Config_update(ONOFFdata, "uselaststate", ONOFFConfig.useLastStateOnStart); Config_update(ONOFFdata, "cmd", ONOFFConfig.ONOFFState); if (ONOFFdata.containsKey("erase") && ONOFFdata["erase"].as()) { // Erase config from NVS (non-volatile storage) preferences.begin(Gateway_Short_Name, false); if (preferences.isKey("ONOFFConfig")) { int result = preferences.remove("ONOFFConfig"); THEENGS_LOG_NOTICE(F("ONOFF config erase result: %d" CR), result); preferences.end(); return; // Erase prevails on save, so skipping save } else { THEENGS_LOG_NOTICE(F("ONOFF config not found" CR)); preferences.end(); } } if (ONOFFdata.containsKey("save") && ONOFFdata["save"].as()) { StaticJsonDocument jsonBuffer; JsonObject jo = jsonBuffer.to(); jo["uselaststate"] = ONOFFConfig.useLastStateOnStart; jo["cmd"] = ONOFFConfig.ONOFFState; // Save config into NVS (non-volatile storage) String conf = ""; serializeJson(jsonBuffer, conf); preferences.begin(Gateway_Short_Name, false); int result = preferences.putString("ONOFFConfig", conf); preferences.end(); THEENGS_LOG_NOTICE(F("ONOFF Config_save: %s, result: %d" CR), conf.c_str(), result); } } void ONOFFConfig_load() { StaticJsonDocument jsonBuffer; preferences.begin(Gateway_Short_Name, true); if (preferences.isKey("ONOFFConfig")) { auto error = deserializeJson(jsonBuffer, preferences.getString("ONOFFConfig", "{}")); preferences.end(); if (error) { THEENGS_LOG_ERROR(F("ONOFF config deserialization failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity()); return; } if (jsonBuffer.isNull()) { THEENGS_LOG_WARNING(F("ONOFF config is null" CR)); return; } JsonObject jo = jsonBuffer.as(); ONOFFConfig_fromJson(jo); THEENGS_LOG_NOTICE(F("ONOFF config loaded" CR)); } else { preferences.end(); THEENGS_LOG_NOTICE(F("ONOFF config not found" CR)); } } # else void ONOFFConfig_init() {}; void ONOFFConfig_fromJson(JsonObject& ONOFFdata) {}; void ONOFFConfig_load() {}; # endif void updatePowerIndicator() { # ifdef LED_ACTUATOR_ONOFF if (digitalRead(ACTUATOR_ONOFF_GPIO) == ACTUATOR_ON) { ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_ACTUATOR_ONOFF_COLOR); } else { ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_COLOR_BLACK); } # endif } //Check regularly temperature of the ESP32 board and switch OFF the relay if temperature is more than MAX_TEMP_ACTUATOR # ifdef MAX_TEMP_ACTUATOR extern float intTemperatureRead(); void overLimitTemp(void* pvParameters) { # if defined(ESP32) && !defined(NO_INT_TEMP_READING) for (;;) { static float previousInternalTempc = 0; float internalTempc = intTemperatureRead(); THEENGS_LOG_TRACE(F("Internal temperature of the ESP32 %F" CR), internalTempc); // We switch OFF the actuator if the temperature of the ESP32 is more than MAX_TEMP_ACTUATOR two consecutive times, so as to avoid false single readings to trigger the relay OFF. if (internalTempc > MAX_TEMP_ACTUATOR && previousInternalTempc > MAX_TEMP_ACTUATOR) { if (digitalRead(ACTUATOR_ONOFF_GPIO) == ACTUATOR_ON) { // This could be with the previous condition, but it is better to trigger the digitalRead only if the previous condition is met to avoid the digitalRead THEENGS_LOG_ERROR(F("[ActuatorONOFF] OverTemperature detected ( %F > %F ) switching OFF Actuator" CR), internalTempc, MAX_TEMP_ACTUATOR); ActuatorTrigger(); # ifdef LED_ACTUATOR_ONOFF ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_ERROR_COLOR, -1); # endif } } previousInternalTempc = internalTempc; vTaskDelay(TimeBetweenReadingIntTemp); } # endif } # endif void setupONOFF() { # ifdef MAX_TEMP_ACTUATOR xTaskCreate(overLimitTemp, "overLimitTemp", 4000, NULL, 10, NULL); # endif # ifdef ESP32 ONOFFConfig_init(); ONOFFConfig_load(); THEENGS_LOG_NOTICE(F("Target state on restart: %T" CR), ONOFFConfig.ONOFFState); THEENGS_LOG_NOTICE(F("Use last state on restart: %T" CR), ONOFFConfig.useLastStateOnStart); # endif pinMode(ACTUATOR_ONOFF_GPIO, OUTPUT); # ifdef ACTUATOR_ONOFF_DEFAULT digitalWrite(ACTUATOR_ONOFF_GPIO, ACTUATOR_ONOFF_DEFAULT); # elif defined(ESP32) if (ONOFFConfig.useLastStateOnStart) { digitalWrite(ACTUATOR_ONOFF_GPIO, ONOFFConfig.ONOFFState); } else { digitalWrite(ACTUATOR_ONOFF_GPIO, !ACTUATOR_ON); } # endif updatePowerIndicator(); THEENGS_LOG_TRACE(F("actuatorONOFF setup done" CR)); } # if jsonReceiving void XtoONOFF(const char* topicOri, JsonObject& ONOFFdata) { if (cmpToMainTopic(topicOri, subjectMQTTtoONOFF)) { THEENGS_LOG_TRACE(F("MQTTtoONOFF json data analysis" CR)); int boolSWITCHTYPE = ONOFFdata["cmd"] | 99; int gpio = ONOFFdata["gpio"] | ACTUATOR_ONOFF_GPIO; if (boolSWITCHTYPE != 99) { THEENGS_LOG_NOTICE(F("MQTTtoONOFF boolSWITCHTYPE ok: %d" CR), boolSWITCHTYPE); THEENGS_LOG_NOTICE(F("GPIO number: %d" CR), gpio); pinMode(gpio, OUTPUT); digitalWrite(gpio, boolSWITCHTYPE); # ifdef LED_ACTUATOR_ONOFF if (boolSWITCHTYPE == ACTUATOR_ON) { ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_ACTUATOR_ONOFF_COLOR); } else { ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_COLOR_BLACK); } # endif # ifdef ESP32 if (ONOFFConfig.useLastStateOnStart) { ONOFFdata["save"] = true; ONOFFConfig_fromJson(ONOFFdata); } # endif // we acknowledge the sending by publishing the value to an acknowledgement topic stateONOFFMeasures(); } else { if (ONOFFdata["cmd"] == "high_pulse") { THEENGS_LOG_NOTICE(F("MQTTtoONOFF high_pulse ok" CR)); THEENGS_LOG_NOTICE(F("GPIO number: %d" CR), gpio); int pulselength = ONOFFdata["pulse_length"] | 500; THEENGS_LOG_NOTICE(F("Pulse length: %d ms" CR), pulselength); pinMode(gpio, OUTPUT); digitalWrite(gpio, HIGH); delay(pulselength); digitalWrite(gpio, LOW); } else if (ONOFFdata["cmd"] == "low_pulse") { THEENGS_LOG_NOTICE(F("MQTTtoONOFF low_pulse ok" CR)); THEENGS_LOG_NOTICE(F("GPIO number: %d" CR), gpio); int pulselength = ONOFFdata["pulse_length"] | 500; THEENGS_LOG_NOTICE(F("Pulse length: %d ms" CR), pulselength); pinMode(gpio, OUTPUT); digitalWrite(gpio, LOW); delay(pulselength); digitalWrite(gpio, HIGH); } else { THEENGS_LOG_ERROR(F("MQTTtoONOFF failed json read" CR)); } } } if (cmpToMainTopic(topicOri, subjectMQTTtoONOFFset)) { THEENGS_LOG_TRACE(F("MQTTtoONOFF json set" CR)); /* * Configuration modifications priorities: * First `init=true` and `load=true` commands are executed (if both are present, INIT prevails on LOAD) * Then parameters included in json are taken in account * Finally `erase=true` and `save=true` commands are executed (if both are present, ERASE prevails on SAVE) */ if (ONOFFdata.containsKey("init") && ONOFFdata["init"].as()) { // Restore the default (initial) configuration ONOFFConfig_init(); } else if (ONOFFdata.containsKey("load") && ONOFFdata["load"].as()) { // Load the saved configuration, if not initialised ONOFFConfig_load(); } // Load config from json if available ONOFFConfig_fromJson(ONOFFdata); stateONOFFMeasures(); } } # endif # if simpleReceiving void XtoONOFF(const char* topicOri, const char* datacallback) { if ((cmpToMainTopic(topicOri, subjectMQTTtoONOFF))) { THEENGS_LOG_TRACE(F("MQTTtoONOFF" CR)); char* endptr = NULL; long gpio = strtol(datacallback, &endptr, 10); if (datacallback == endptr) gpio = ACTUATOR_ONOFF_GPIO; THEENGS_LOG_NOTICE(F("GPIO number: %d" CR), gpio); pinMode(gpio, OUTPUT); bool ON = false; if (strstr(topicOri, ONKey) != NULL) ON = true; if (strstr(topicOri, OFFKey) != NULL) ON = false; digitalWrite(gpio, ON); # ifdef LED_ACTUATOR_ONOFF if (ON == ACTUATOR_ON) { ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_ACTUATOR_ONOFF_COLOR); } else { ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_COLOR_BLACK); } # endif // we acknowledge the sending by publishing the value to an acknowledgement topic char b = ON; pub(subjectGTWONOFFtoMQTT, &b); } } # endif // Check regularly current the relay and switch it OFF if the current is more than MAX_CURRENT_ACTUATOR # ifdef MAX_CURRENT_ACTUATOR void overLimitCurrent(float RN8209current) { static float RN8209previousCurrent = 0; THEENGS_LOG_TRACE(F("RN8209 Current %F" CR), RN8209current); // We switch OFF the actuator if the current of the RN8209 is more than MAX_CURRENT_ACTUATOR. if (RN8209current > MAX_CURRENT_ACTUATOR && RN8209previousCurrent > MAX_CURRENT_ACTUATOR) { if (digitalRead(ACTUATOR_ONOFF_GPIO) == ACTUATOR_ON) { // This could be with the previous condition, but it is better to trigger the digitalRead only if the previous condition is met to avoid the digitalRead THEENGS_LOG_ERROR(F("[ActuatorONOFF] OverCurrent detected ( %F > %F ) switching OFF Actuator" CR), RN8209current, MAX_CURRENT_ACTUATOR); ActuatorTrigger(); # ifdef LED_ACTUATOR_ONOFF ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_ERROR_COLOR, -1); # endif } } RN8209previousCurrent = RN8209current; } # else void overLimitCurrent(float RN8209current) {} # endif /* Handling of actuator control following the cases below: -Button press, if the button goes to ACTUATOR_BUTTON_TRIGGER_LEVEL we change the Actuator level -Status less switch state change (a switch without ON OFF labels), an action of this type of switch will trigger a change of the actuator state independently from the switch position */ void ActuatorTrigger() { uint8_t level = !digitalRead(ACTUATOR_ONOFF_GPIO); THEENGS_LOG_TRACE(F("Actuator triggered %d" CR), level); digitalWrite(ACTUATOR_ONOFF_GPIO, level); # ifdef LED_ACTUATOR_ONOFF if (level == ACTUATOR_ON) { ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_ACTUATOR_ONOFF_COLOR); } else { ledManager.setMode(LED_ACTUATOR_ONOFF, 0, LEDManager::Mode::STATIC, LED_COLOR_BLACK); } # endif # ifdef ESP32 if (ONOFFConfig.useLastStateOnStart) { StaticJsonDocument<64> jsonBuffer; JsonObject ONOFFdata = jsonBuffer.to(); ONOFFdata["cmd"] = (int)level; ONOFFdata["save"] = true; ONOFFConfig_fromJson(ONOFFdata); } # endif stateONOFFMeasures(); } void stateONOFFMeasures() { //Publish actuator state StaticJsonDocument<128> jsonBuffer; JsonObject ONOFFdata = jsonBuffer.to(); ONOFFdata["cmd"] = (int)digitalRead(ACTUATOR_ONOFF_GPIO); # ifdef ESP32 ONOFFdata["uselaststate"] = ONOFFConfig.useLastStateOnStart; # endif ONOFFdata["origin"] = subjectGTWONOFFtoMQTT; enqueueJsonObject(ONOFFdata, QueueSemaphoreTimeOutTask); } #endif