#include "Global.h" #include "classes/IoTItem.h" #include #include "BoilerHeader.h" namespace _Boiler { DynamicJsonDocument OpenThemData(JSON_BUFFER_SIZE / 2); IoTItem *_idPID = nullptr; IoTItem *_idTboiler = nullptr; IoTItem *_idTret = nullptr; IoTItem *_idToutside = nullptr; IoTItem *_idStateCH = nullptr; IoTItem *_idStateFlame = nullptr; IoTItem *_idModLevel = nullptr; IoTItem *_idCmdCH = nullptr; IoTItem *_idSetCH = nullptr; IoTItem *_idCtrlType = nullptr; int pid; IoTItem *rele[3]; IoTItem *relePump; IoTItem *releDhw; IoTItem *tmp; // проверяем если пришедшее значение отличается от предыдущего регистрируем событие void publishNew(String widget, String value) { if (OpenThemData[widget] != value) { OpenThemData[widget] = value; tmp = findIoTItem(widget); if (tmp) { tmp->setValue(value, true); } else { if (_debug > 0) SerialPrint("new", "SmartBoiler", widget + " = " + value); } } } void sendTelegramm(String msg) { if (_telegram == 1) { if (tlgrmItem) tlgrmItem->sendTelegramMsg(false, msg); } } /* * ========================================================================================= * КЛАСС УПРАВЛЕНИЯ ГВС * ========================================================================================= */ IoTItem *_idTdhw = nullptr; IoTItem *_idStateDHW = nullptr; IoTItem *_idCmdDHW = nullptr; IoTItem *_idTDhw = nullptr; IoTItem *_idSetDHW = nullptr; class DHWControl : public IoTItem { private: unsigned long ts = 0; public: DHWControl(String parameters) : IoTItem(parameters) { SerialPrint("i", F("DHWControl"), " START... "); String tmpID; jsonRead(parameters, "idReleDhw", tmpID); releDhw = findIoTItem(tmpID); jsonRead(parameters, "idSetDHW", tmpID); _idSetDHW = findIoTItem(tmpID); jsonRead(parameters, "idStateDHW", tmpID); _idStateDHW = findIoTItem(tmpID); jsonRead(parameters, "idCmdDHW", tmpID); _idCmdDHW = findIoTItem(tmpID); jsonRead(parameters, "idTDhw", tmpID); _idTDhw = findIoTItem(tmpID); jsonRead(parameters, "minDhw", conf.minDhw); jsonRead(parameters, "maxDhw", conf.maxDhw); jsonRead(parameters, "gistDhw", conf.gistDhw); // releDhw = findIoTItem("releDhw"); if (releDhw) { SerialPrint("i", "DHWControl", "Инициализировано РЕЛЕ ГВС"); } // dhw_ctrl = this; } // ============================== ЛОГИКА РАБОТЫ КОТЛА И ВКЛЮЧЕНИЯ ТЭНОВ ====================================== // Работы котла и включения тэнов static void logicPowerDhw() { if (_idCmdDHW) set.cmd_dhwEnable = ::atoi(_idCmdDHW->getValue().c_str()); if (_idTDhw) state.Tdhw = ::atof(_idTDhw->getValue().c_str()); if (_idSetDHW) set.TSetDhw = ::atof(_idSetDHW->getValue().c_str()); if (set.cmd_dhwEnable) { if (releDhw) { if (_idTDhw) state.Tdhw = ::atof(_idTDhw->getValue().c_str()); if (set.TSetDhw - state.Tdhw >= conf.gistDhw && state.Tdhw > -5 && state.Tdhw < 120) { // включаем ГВС releDhw->setValue("1", true); state.stateDHW = 1; state.stateCH = 0; for (uint8_t i = 0; i < conf.countRele; i++) { state.r[i] = true; } state.RelModLevel = 100; } else { releDhw->setValue("0", true); state.stateDHW = 0; state.RelModLevel = 0; } } } else { if (releDhw) releDhw->setValue("0", true); } // publishNew("StateDHW", String(state.stateDHW)); IoTValue val; if (_idStateDHW) { val.valD = state.stateDHW; _idStateDHW->setValue(val, true); } } void doByInterval() { } // Комманды из сценария IoTValue execute(String command, std::vector ¶m) { IoTValue val; if (command == "SetDHW") { set.TSetDhw = param[0].valD; set.TSetDhw = constrain(set.TSetDhw, conf.minDhw, conf.maxDhw); // publishNew("SetDHW", String(set.TSetDhw)); val.valD = set.TSetDhw; if (_idSetDHW) _idSetDHW->setValue(val, true); SerialPrint("i", "DHWControl", "Scenario DHWSet "); } else if (command == "DHWEnable") { set.cmd_dhwEnable = param[0].valD; val.valD = set.cmd_dhwEnable; if (_idCmdDHW) _idCmdDHW->setValue(val, true); SerialPrint("i", "DHWControl", "Scenario DHWEnable "); } return {}; } ~DHWControl() { } }; /* * ========================================================================================= * КЛАСС УПРАВЛЕНИЯ КОТЛОМ * ========================================================================================= */ class BoilerControl : public IoTItem { private: unsigned long ts = 0; public: BoilerControl(String parameters) : IoTItem(parameters) { SerialPrint("i", F("BoilerControl"), " START... "); jsonRead(parameters, "LogLevel", _debug); jsonRead(parameters, "telegram", _telegram); String tmpID; jsonRead(parameters, "idPID", tmpID); _idPID = findIoTItem(tmpID); jsonRead(parameters, "idTboiler", tmpID); _idTboiler = findIoTItem(tmpID); jsonRead(parameters, "idTret", tmpID); _idTret = findIoTItem(tmpID); jsonRead(parameters, "idToutside", tmpID); _idToutside = findIoTItem(tmpID); jsonRead(parameters, "idStateCH", tmpID); _idStateCH = findIoTItem(tmpID); jsonRead(parameters, "idStateFlame", tmpID); _idStateFlame = findIoTItem(tmpID); jsonRead(parameters, "idModLevel", tmpID); _idModLevel = findIoTItem(tmpID); jsonRead(parameters, "idCmdCH", tmpID); _idCmdCH = findIoTItem(tmpID); jsonRead(parameters, "idSetCH", tmpID); _idSetCH = findIoTItem(tmpID); jsonRead(parameters, "idCtrlType", tmpID); _idCtrlType = findIoTItem(tmpID); jsonRead(parameters, "rele1_Pwr", conf.relePwr[0]); jsonRead(parameters, "rele2_Pwr", conf.relePwr[1]); jsonRead(parameters, "rele3_Pwr", conf.relePwr[2]); jsonRead(parameters, "Pump", conf.pump); jsonRead(parameters, "changeRele", conf.changeRele); jsonRead(parameters, "minCH", conf.minCH); jsonRead(parameters, "maxCH", conf.maxCH); jsonRead(parameters, "gistCH", conf.gistCH); jsonRead(parameters, "antiFreez", conf.antiFreez); configuration(); } // ============================== ЛОГИКА РАБОТЫ КОТЛА И ВКЛЮЧЕНИЯ ТЭНОВ ====================================== // Работы котла и включения тэнов static void logicPowerOn() { // TODO ВКЛЮЧЕНИЕ РЕЛЕ ПО ИХ МОЩНОСТИ (СДЕЛАТЬ ШАГИ НАГРЕВА И КОМБИНАЦИИ ВКЛБЧЕНИЯ ТЕНОВ С РАЗНОЙ МОЩНОСТЬЮ) for (uint8_t i = 0; i < conf.countRele; i++) { state.r[i] = false; } // сейчас включаются по порядку state.RelModLevel = 0; pid = 0; state.fl_flame = false; if (_idPID) pid = ::atoi(_idPID->getValue().c_str()); // обнуляем ГВС state.stateDHW = 0; if (state.Tboiler < conf.maxCH) { // if (dhw_ctrl) //{ // если есть модуль ГВС, то вызываем его логику включения тэнов DHWControl::logicPowerDhw(); //} if (!state.stateDHW) // Если уже включено ГВС, то нечего смотреть на отопление { if (set.cmd_chEnable) { publishNew("status", "Штатный режим"); // включаем отопление state.stateCH = 1; if (state.Tboiler < (set.TSetCH + conf.gistCH) && /*set.TSetCH - state.Tboiler >= conf.gistCH &&*/ state.Tboiler > -5 && state.Tboiler < 120) { if (conf.ctrlType == 0) { // режим модуляции, это когда есть модуль ПИД и более одного реле if (pid > 0) { state.r[state.currentRele] = true; state.RelModLevel = conf.prcOnekWt * conf.relePwr[state.currentRele]; } if (pid > 30 && pid <= 60) { uint8_t next = state.currentRele + 1; if (next >= conf.countRele) next = 0; state.r[next] = true; state.RelModLevel = (conf.prcOnekWt * conf.relePwr[state.currentRele]) + (conf.prcOnekWt * conf.relePwr[next]); } if (pid > 60) { for (uint8_t i = 0; i < conf.countRele; i++) { state.r[i] = true; } state.RelModLevel = 100; } } else { // у нас релейный режим без ПИДа или с одним реле(тэном) // TODO СЕЙЧАС ВКЛЮЧАЕМ ВСЕ РЕЛЕ, НАДО ЧТО ТО ПРИДУМАТЬ УМНОЕ for (uint8_t i = 0; i < conf.countRele; i++) { state.r[i] = true; } state.RelModLevel = 100; } } else { state.RelModLevel = 0; } } else { publishNew("status", "Выкл отопление"); state.stateCH = 0; } } } if (conf.antiFreez > 0 && state.Tboiler < (conf.antiFreez + 4)) { state.r[state.currentRele] = true; state.RelModLevel = conf.prcOnekWt * conf.relePwr[state.currentRele]; // setValue("Анти-Заморозка"); publishNew("status", "Режим анти-заморозка"); SerialPrint("i", "BoilerControl", "Режим анти-заморозка"); sendTelegramm("Режим анти-заморозка"); } static bool prev_flame = false; if (state.RelModLevel > 0) state.fl_flame = true; // если хоть одно реле включено, то выставляем флаг горелки if (state.fl_flame && prev_flame != state.fl_flame) { if (conf.changeRele) { state.currentRele++; if (state.currentRele >= conf.countRele) state.currentRele = 0; } } prev_flame = state.fl_flame; bool fl_pump = false; // переключаем реле в соответсии с их статусами for (uint8_t i = 0; i < conf.countRele; i++) { if (rele[i]) { rele[i]->setValue(state.r[i] ? "1" : "0", true); } if (!fl_pump) // что бы не обнулить если выстиавили true fl_pump = state.r[i] ? true : false; // если хоть одно реле включено, то включаем насос } if (fl_pump) { // если хоть одно реле включено, то включаем насос if (relePump) relePump->setValue("1", true); } else { if (relePump) { // если все реле выключены if (state.Tboiler > conf.minCH && set.cmd_chEnable) // НО температура ещё горячая и при этом отопление включено, то включаем насос relePump->setValue("1", true); else // Если температура в котле уже остыла, или отопление нам не нужно (летом нагрели воду-пусть котел сам остывает без насоса), то выключаем насос relePump->setValue("0", true); } } IoTValue val; if (_idModLevel) { val.valD = state.RelModLevel; _idModLevel->setValue(val, true); } if (_idStateCH) { val.valD = state.stateCH; _idStateCH->setValue(val, true); } if (_idStateFlame) { val.valD = state.fl_flame; _idStateFlame->setValue(val, true); } // publishNew("ModLevel", String(state.RelModLevel)); // publishNew("stateCH", String(state.stateCH)); // publishNew("controlType", String(conf.ctrlType)); // publishNew("StateFlame", String(state.fl_flame)); } //============================== ОБЕСПЕЧЕНИЕ РАБОТЫ IoTMANAGER ===================================== // конфигурирование котла в зависимости от настроек void configuration() { state.fl_flame = state.stateCH = 0; conf.countRele = conf.prcOnekWt = 0; if (conf.relePwr[0]) { rele[0] = findIoTItem("rele1"); if (rele[0]) { conf.countRele++; SerialPrint("i", "BoilerControl", "Инициализировано РЕЛЕ 1-го тэна"); } } if (conf.relePwr[1]) { rele[1] = findIoTItem("rele2"); if (rele[1]) { conf.countRele++; SerialPrint("i", "BoilerControl", "Инициализировано РЕЛЕ 2-го тэна"); } } if (conf.relePwr[2]) { rele[2] = findIoTItem("rele3"); if (rele[2]) { conf.countRele++; SerialPrint("i", "BoilerControl", "Инициализировано РЕЛЕ 3-го тэна"); } } for (int i = 0; i < conf.countRele; i++) { conf.prcOnekWt += conf.relePwr[i]; } if (conf.countRele && conf.prcOnekWt) { // conf.prcOnekWt /= conf.countRele; conf.prcOnekWt = 100 / conf.prcOnekWt; // SerialPrint("i", "BoilerControl", "Процент одного кВт = " + String (conf.prcOnekWt) + "%"); } if (conf.pump) { relePump = findIoTItem("relePump"); if (relePump) { SerialPrint("i", "BoilerControl", "Инициализировано РЕЛЕ Насоса"); } } conf.ctrlType = false; if (conf.countRele == 1 || _idPID == nullptr) conf.ctrlType = true; // IoTValue val; // val.valD = conf.ctrlType; if (_idCtrlType) { if (conf.ctrlType) _idCtrlType->setValue("Вкл/Выкл", true); else _idCtrlType->setValue("Модуляция", true); } updateStateboiler(); } void doByInterval() { // updateStateboiler(); // Принудительно чистим данные, что бы обновился интерфейс OpenThemData.clear(); if (_debug > 0) { SerialPrint("i", "BoilerControl", "Обновляем данные в web интерфейсе"); } if (_debug > 0) { SerialPrint("i", "BoilerControl", "memoryUsage: " + String(OpenThemData.memoryUsage())); } } // Основной цикл программы void loop() { unsigned long new_ts = millis(); int delay = 1000; if (new_ts - ts > delay) { ts = new_ts; updateStateboiler(); logicPowerOn(); } // для новых версий IoTManager IoTItem::loop(); } // Комманды из сценария IoTValue execute(String command, std::vector ¶m) { IoTValue val; if (command == "CHSet") { set.TSetCH = param[0].valD; set.TSetCH = constrain(set.TSetCH, conf.minCH, conf.maxCH); val.valD = set.TSetCH; if (_idSetCH) _idSetCH->setValue(val, true); // publishNew("SetCH", String(set.TSetCH)); SerialPrint("i", "BoilerControl", "Scenario CHSet "); } else if (command == "CHEnable") { set.cmd_chEnable = param[0].valD; val.valD = set.cmd_chEnable; if (_idCmdCH) _idCmdCH->setValue(val, true); SerialPrint("i", "BoilerControl", "Scenario CHEnable "); } return {}; } // обновление данных от датчиков void updateStateboiler() { if (_idTboiler) state.Tboiler = ::atof(_idTboiler->getValue().c_str()); if (_idTret) state.Tret = ::atof(_idTret->getValue().c_str()); if (_idToutside) state.Toutside = ::atof(_idToutside->getValue().c_str()); // if (_idStateCH) // state.stateCH = ::atoi(_idStateCH->getValue().c_str()); // if (_idStateFlame) // state.fl_flame = ::atoi(_idStateFlame->getValue().c_str()); // if (_idModLevel) // state.RelModLevel = ::atof(_idModLevel->getValue().c_str()); if (_idCmdCH) set.cmd_chEnable = ::atoi(_idCmdCH->getValue().c_str()); if (_idSetCH) set.TSetCH = ::atof(_idSetCH->getValue().c_str()); } ~BoilerControl() { } }; } void *getAPI_SmartBoiler(String subtype, String param) { if (subtype == F("BoilerControl")) { return new _Boiler::BoilerControl(param); } else if (subtype == F("DHWControl")) { return new _Boiler::DHWControl(param); } else { return nullptr; } }