From a77a093a792c57a12ed9244861493383f6bbd8e1 Mon Sep 17 00:00:00 2001 From: avaksru Date: Mon, 30 Jan 2023 11:33:41 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B8=20=D0=A2?= =?UTF-8?q?=D0=B5=D1=80=D0=BC=D0=BE=D1=81=D1=82=D0=B0=D1=82=20=D0=B8=20Nex?= =?UTF-8?q?tionUpload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- myProfile.json | 8 + .../display/NextionUpload/NextionUpload.cpp | 117 +++++ .../display/NextionUpload/modinfo.json | 50 ++ src/modules/exec/Thermostat/Thermostat.cpp | 471 ++++++++++++++++++ src/modules/exec/Thermostat/modinfo.json | 139 ++++++ 5 files changed, 785 insertions(+) create mode 100644 src/modules/display/NextionUpload/NextionUpload.cpp create mode 100644 src/modules/display/NextionUpload/modinfo.json create mode 100644 src/modules/exec/Thermostat/Thermostat.cpp create mode 100644 src/modules/exec/Thermostat/modinfo.json diff --git a/myProfile.json b/myProfile.json index a7c149a4..4850ecb5 100644 --- a/myProfile.json +++ b/myProfile.json @@ -225,6 +225,10 @@ { "path": "src/modules/exec/TelegramLT", "active": true + }, + { + "path": "src/modules/exec/Thermostat", + "active": false } ], "Экраны": [ @@ -232,6 +236,10 @@ "path": "src/modules/display/Lcd2004", "active": true }, + { + "path": "src/modules/display/NextionUpload", + "active": false + }, { "path": "src/modules/display/Ws2812b", "active": false diff --git a/src/modules/display/NextionUpload/NextionUpload.cpp b/src/modules/display/NextionUpload/NextionUpload.cpp new file mode 100644 index 00000000..9239d3ee --- /dev/null +++ b/src/modules/display/NextionUpload/NextionUpload.cpp @@ -0,0 +1,117 @@ + +#define DEBUG_SERIAL_ENABLE +#include "Global.h" +#include "classes/IoTItem.h" +#include "ESPNexUpload.h" +bool updated = false; +// const char *host = "live-control.com"; +// const char *url = "/iotm/Live-Control.tft"; +extern IoTGpio IoTgpio; + +class NextionUpload : public IoTItem +{ +private: + String _url; + String _host; + int _NEXT_RX; + int _NEXT_TX; + +public: + NextionUpload(String parameters) : IoTItem(parameters) + { + _url = jsonReadStr(parameters, "url"); + _host = jsonReadStr(parameters, "host"); + + _NEXT_RX = jsonReadInt(parameters, "NEXT_RX"); + _NEXT_TX = jsonReadInt(parameters, "NEXT_TX"); +#define NEXT_RX _NEXT_RX // Nextion RX pin | Default 16 +#define NEXT_TX _NEXT_TX // Nextion TX pin | Default 17 + } + + IoTValue execute(String command, std::vector ¶m) + { + + if (command == "Update") + { + SerialPrint("I", F("NextionUpdate"), "Update .... "); + + if (!updated) + { + SerialPrint("I", F("NextionUpdate"), "connecting to " + (String)_host); + HTTPClient http; + +#if defined ESP8266 + if (!http.begin(_host, 80, _url)) + { +#elif defined ESP32 + if (!http.begin(String("http://") + _host + _url)) + { +#endif + // Serial.println("connection failed"); + SerialPrint("I", F("NextionUpdate"), "connection failed "); + } + + SerialPrint("I", F("NextionUpdate"), "Requesting file: " + (String)_url); + int code = http.GET(); + int contentLength = http.getSize(); + + // Update the nextion display + if (code == 200) + { + SerialPrint("I", F("NextionUpdate"), "File received. Update Nextion... "); + bool result; + ESPNexUpload nextion(115200); + nextion.setUpdateProgressCallback([]() + { SerialPrint("I", F("NextionUpdate"), "... "); }); + + result = nextion.prepareUpload(contentLength); + + if (!result) + { + SerialPrint("I", F("NextionUpdate"), "Error: " + (String)nextion.statusMessage); + } + else + { + SerialPrint("I", F("NextionUpdate"), "Start upload. File size is: " + (String)contentLength); + result = nextion.upload(*http.getStreamPtr()); + + if (result) + { + updated = true; + SerialPrint("I", F("NextionUpdate"), "Succesfully updated Nextion! "); + } + else + { + SerialPrint("I", F("NextionUpdate"), "Error updating Nextion: " + (String)nextion.statusMessage); + } + + nextion.end(); + } + } + else + { + SerialPrint("I", F("NextionUpdate"), "HTTP error: " + (String)http.errorToString(code).c_str()); + } + + http.end(); + SerialPrint("I", F("NextionUpdate"), "Closing connection "); + } + } + + return {}; + } + + ~NextionUpload(){}; +}; + +void *getAPI_NextionUpload(String subtype, String param) +{ + if (subtype == F("NextionUpload")) + { + return new NextionUpload(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/display/NextionUpload/modinfo.json b/src/modules/display/NextionUpload/modinfo.json new file mode 100644 index 00000000..a97f2343 --- /dev/null +++ b/src/modules/display/NextionUpload/modinfo.json @@ -0,0 +1,50 @@ +{ + "menuSection": "Экраны", + + "configItem": [ + { + "global": 0, + "name": "Nextion Uploud", + "type": "Reading", + "subtype": "NextionUpload", + "id": "Nextion", + "widget": "", + "page": "", + "descr": "", + "host": "192.168.1.10", + "url": "castom_nextion.tif", + "NEXT_TX": 16, + "NEXT_RX": 17 + } + ], + + "about": { + "authorName": "AVAKS", + "authorContact": "https://t.me/@avaks_dev", + "authorGit": "https://github.com/avaksru", + "specialThanks": "", + "moduleName": "NextionUpload", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Nextion Upload", + "moduleDesc": "загрузка прошивки в дисплей Nextion. Команда для запуска обновления дисплея: Nextion.Update(); ", + "propInfo": { + "host": "Сервер обновления", + "url": "файл прошивки" + } + }, + + "defActive": false, + + "usedLibs": { + "esp32_4mb": ["https://github.com/avaksru/ESPNexUpload.git"], + "esp8266_4mb": ["https://github.com/avaksru/ESPNexUpload.git"], + "esp8266_1mb": ["https://github.com/avaksru/ESPNexUpload.git"], + "esp8266_1mb_ota": ["https://github.com/avaksru/ESPNexUpload.git"], + "esp8285_1mb": ["https://github.com/avaksru/ESPNexUpload.git"], + "esp8285_1mb_ota": ["https://github.com/avaksru/ESPNexUpload.git"] + } +} diff --git a/src/modules/exec/Thermostat/Thermostat.cpp b/src/modules/exec/Thermostat/Thermostat.cpp new file mode 100644 index 00000000..bcac8274 --- /dev/null +++ b/src/modules/exec/Thermostat/Thermostat.cpp @@ -0,0 +1,471 @@ +#include "Global.h" +#include "classes/IoTItem.h" + +extern IoTGpio IoTgpio; + +class ThermostatGIST : public IoTItem +{ +private: + String _set_id; // заданная температура + String _term_id; // термометр + String _term_rezerv_id; // резервный термометр + String _rele; // реле + float pv_last = 0; // предыдущая температура + float _gist = 1; // гистерис + float sp, pv, pv2; + String interim; + int enable = 1; + +public: + ThermostatGIST(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, "set_id", _set_id); + jsonRead(parameters, "term_id", _term_id); + jsonRead(parameters, "term_rezerv_id", _term_rezerv_id); + jsonRead(parameters, "gist", _gist); + jsonRead(parameters, "rele", _rele); + } + + void doByInterval() + { + // заданная температура + IoTItem *tmp = findIoTItem(_set_id); + if (tmp) + { + interim = tmp->getValue(); + sp = ::atof(interim.c_str()); + } + // термометр + tmp = findIoTItem(_term_id); + if (tmp) + { + interim = tmp->getValue(); + pv = ::atof(interim.c_str()); + } + if (sp && _rele != "") + { + if (pv > -40 && pv < 120 && pv) + { + if (enable) + { + setValue("штатный режим"); + } + // работаем по основному датчику + if (pv >= sp + _gist && enable) + { + tmp = findIoTItem(_rele); + if (tmp) + tmp->setValue("0", true); + } + if (pv <= sp - _gist && enable) + { + tmp = findIoTItem(_rele); + if (tmp) + tmp->setValue("1", true); + } + } + else + { + // резервный термометр + if (_term_rezerv_id != "") + { + tmp = findIoTItem(_term_rezerv_id); + if (tmp) + { + interim = tmp->getValue(); + pv2 = ::atof(interim.c_str()); + } + // работаем по резервному датчику + if (pv2 > -40 && pv2 < 120 && pv2) + { + if (enable) + { + setValue("резервный датчик"); + } + if (pv2 >= sp + _gist && enable) + { + tmp = findIoTItem(_rele); + if (tmp) + tmp->setValue("0", true); + } + if (pv2 <= sp - _gist && enable) + { + tmp = findIoTItem(_rele); + if (tmp) + tmp->setValue("1", true); + } + } + else + { + if (enable) + { + setValue("ошибка резервного датчика"); + } + } + } + else + { + if (enable) + { + setValue("ошибка датчика температуры"); + } + } + } + } + else + { + // если не заполнены настройки термостата + setValue("ошибка настройки термостата"); + } + + pv_last = pv; + } + + IoTValue execute(String command, std::vector ¶m) + { + if (param.size() == 1) + { + if (command == "enable") + { + if (param.size()) + { + enable = param[0].valD; + if (enable) + { + setValue("включен"); + } + else + { + setValue("выключен"); + } + } + } + } + return {}; + } + + ~ThermostatGIST(){}; +}; + +class ThermostatPID : public IoTItem +{ +private: + String _set_id; // заданная температура + String _term_id; // термометр + float _int, _KP, _KI, _KD, sp, pv, + pv_last = 0, // предыдущая температура + ierr = 0, // интегральная погрешность + dt = 0; // время между измерениями + String _rele; // реле + String interim; + int enable = 1; + long interval; + IoTItem *tmp; + int releState = 0; + +public: + ThermostatPID(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, "set_id", _set_id); + jsonRead(parameters, "term_id", _term_id); + jsonRead(parameters, "int", _int); + jsonRead(parameters, "KP", _KP); + jsonRead(parameters, "KI", _KI); + jsonRead(parameters, "KD", _KD); + jsonRead(parameters, F("int"), interval); + interval = interval * 1000; // интервал проверки в сек + jsonRead(parameters, "rele", _rele); + } + +protected: + //=============================================================== + // Вычисляем температуру контура отпления, коэффициенты ПИД регулятора + //=============================================================== + float pid(float sp, float pv, float pv_last, float &ierr, float dt) + { + float Kc = _KP; // K / %Heater 5 + float tauI = _KI; // sec 50 + float tauD = _KD; // sec 1 + // ПИД коэффициенты + float KP = Kc; // 5 + if (tauI == 0) + { + tauI = 50; + } + float KI = Kc / tauI; // 0.1 + float KD = Kc * tauD; // 5 + // верхняя и нижняя границы уровня нагрева + float ophi = 100; + float oplo = 0; + // вычислить ошибку + float error = sp - pv; // 0 + // calculate the integral error + ierr = ierr + KI * error * dt; // 0 + // вычислить производную измерения + float dpv = (pv - pv_last) / dt; // 0 + // рассчитать выход ПИД регулятора + float P = KP * error; // пропорциональная составляющая + float I = ierr; // интегральная составляющая + float D = -KD * dpv; // дифференциальная составляющая + float op = P + I + D; + // защита от сброса + if ((op < oplo) || (op > ophi)) + { + I = I - KI * error * dt; + // выход регулятора, он же уставка для ID-1 (температура теплоносителя контура СО котла) + op = constrain(op, oplo, ophi); + } + + ierr = I; + return op; + } + + void + doByInterval() + { + // заданная температура + IoTItem *tmp = findIoTItem(_set_id); + if (tmp) + { + interim = tmp->getValue(); + sp = ::atof(interim.c_str()); + } + // термометр + tmp = findIoTItem(_term_id); + if (tmp) + { + interim = tmp->getValue(); + pv = ::atof(interim.c_str()); + } + if (sp && pv) + { + value.valD = pid(sp, pv, pv_last, ierr, _int); + value.valS = (String)(int)pid(sp, pv, pv_last, ierr, _int); + regEvent(value.valS, "ThermostatPID", false, true); + } + pv_last = pv; + } + + void loop() + { + if (enableDoByInt) + { + currentMillis = millis(); + difference = currentMillis - prevMillis; + + if (_rele != "" && enable && value.valD * interval / 100000 > difference / 1000 && releState == 0) + { + releState = 1; + tmp = findIoTItem(_rele); + if (tmp) + tmp->setValue("1", true); + } + if (_rele != "" && enable && value.valD * interval / 100000 < difference / 1000 && releState == 1) + { + releState = 0; + tmp = findIoTItem(_rele); + if (tmp) + tmp->setValue("0", true); + } + + if (difference >= interval) + { + prevMillis = millis(); + this->doByInterval(); + } + } + } + IoTValue execute(String command, std::vector ¶m) + { + if (param.size() == 1) + { + if (command == "enable") + { + if (param.size()) + { + enable = param[0].valD; + } + } + if (command == "KP") + { + if (param.size()) + { + _KP = param[0].valD; + } + } + if (command == "KI") + { + if (param.size()) + { + _KI = param[0].valD; + } + } + if (command == "KD") + { + if (param.size()) + { + _KD = param[0].valD; + } + } + } + return {}; + } + ~ThermostatPID(){}; +}; + +class ThermostatETK : public IoTItem +{ +private: + float pv, sp, outside_temp; + float _iv_k; // эквитермические кривые + String _set_id; // заданная температура + String _term_id; // термометр + String _outside_id; // уличный термометр + String interim; + int enable = 1; + +public: + ThermostatETK(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, "set_id", _set_id); + jsonRead(parameters, "term_id", _term_id); + jsonRead(parameters, "iv_k", _iv_k); + jsonRead(parameters, "outside_id", _outside_id); + } + +protected: + //=================================================================================================================== + // Вычисляем температуру контура отпления, эквитермические кривые + //=================================================================================================================== + float curve(float iv_k, float outside_temp) + { + float a = (-0.21 * iv_k) - 0.06; // a = -0,21k — 0,06 + float b = (6.04 * iv_k) + 1.98; // b = 6,04k + 1,98 + float c = (-5.06 * iv_k) + 18.06; // с = -5,06k + 18,06 + float x = (-0.2 * outside_temp) + 5; // x = -0.2*t1 + 5 + float temp_n = (a * x * x) + (b * x) + c; // Tn = ax2 + bx + c + // Расчетная температура конура отопления + float op = temp_n; // T = Tn + // Ограничиваем температуру для ID-1 + op = constrain(op, 0, 100); + return op; + } + + void doByInterval() + { + // уличный термометр + IoTItem *tmp = findIoTItem(_outside_id); + if (tmp) + { + interim = tmp->getValue(); + outside_temp = ::atof(interim.c_str()); + } + if (_iv_k && outside_temp) + { + + value.valD = curve(_iv_k, outside_temp); + regEvent(value.valD, "ThermostatETK"); + } + } + + ~ThermostatETK(){}; +}; + +class ThermostatETK2 : public IoTItem +{ +private: + float pv, sp, outside_temp; + float _iv_k; // эквитермические кривые + String _set_id; // заданная температура + String _term_id; // термометр + String _outside_id; // уличный термометр + String interim; + int enable = 1; + +public: + ThermostatETK2(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, "set_id", _set_id); + jsonRead(parameters, "term_id", _term_id); + jsonRead(parameters, "iv_k", _iv_k); + jsonRead(parameters, "outside_id", _outside_id); + } + +protected: + //=================================================================================================================== + // Вычисляем температуру контура отпления, эквитермические кривые с учётом влияния температуры в помещении + //=================================================================================================================== + float curve2(float sp, float pv, float iv_k, float outside_temp) + { + // Расчет поправки (ошибки) термостата + float error = sp - pv; // Tt = (Tu — T2) × 5 + float temp_t = error * 3.0; + // Поправка на желаемую комнатную температуру + // Температура контура отопления в зависимости от наружной температуры + float a = (-0.21 * iv_k) - 0.06; // a = -0,21k — 0,06 + float b = (6.04 * iv_k) + 1.98; // b = 6,04k + 1,98 + float c = (-5.06 * iv_k) + 18.06; // с = -5,06k + 18,06 + float x = (-0.2 * outside_temp) + 5; // x = -0.2*t1 + 5 + float temp_n = (a * x * x) + (b * x) + c; // Tn = ax2 + bx + c + // Расчетная температура конура отопления + float op = temp_n + temp_t; // T = Tn + Tk + Tt + // Ограничиваем температуру для ID-1 + op = constrain(op, 0, 100); + return op; + } + + void doByInterval() + { + // заданная температура + IoTItem *tmp = findIoTItem(_set_id); + if (tmp) + { + interim = tmp->getValue(); + sp = ::atof(interim.c_str()); + } + // термометр + tmp = findIoTItem(_term_id); + if (tmp) + { + interim = tmp->getValue(); + pv = ::atof(interim.c_str()); + } + // уличный термометр + tmp = findIoTItem(_outside_id); + if (tmp) + { + interim = tmp->getValue(); + outside_temp = ::atof(interim.c_str()); + } + if (sp && pv && _iv_k && outside_temp) + { + value.valD = curve2(sp, pv, _iv_k, outside_temp); + regEvent(value.valD, "ThermostatETK2"); + } + } + + ~ThermostatETK2(){}; +}; + +void *getAPI_Thermostat(String subtype, String param) +{ + if (subtype == F("ThermostatGIST")) + { + return new ThermostatGIST(param); + } + else if (subtype == F("ThermostatPID")) + { + return new ThermostatPID(param); + } + else if (subtype == F("ThermostatETK")) + { + return new ThermostatETK(param); + } + else if (subtype == F("ThermostatETK2")) + { + return new ThermostatETK2(param); + } + //} + + return nullptr; +} diff --git a/src/modules/exec/Thermostat/modinfo.json b/src/modules/exec/Thermostat/modinfo.json new file mode 100644 index 00000000..61594db4 --- /dev/null +++ b/src/modules/exec/Thermostat/modinfo.json @@ -0,0 +1,139 @@ +{ + "menuSection": "Исполнительные устройства", + + "configItem": [ + { + "global": 0, + "needSave": 0, + "name": "Термостат Гистере́зис ", + "type": "Writing", + "subtype": "ThermostatGIST", + "id": "Thermo", + "widget": "anydataDef", + "page": "Климат", + "descr": "термостат", + "int": 60, + "round": 1, + "set_id": "", + "term_id": "", + "term_rezerv_id": "", + "gist": 0.3, + "rele": "" + }, + { + "global": 0, + "needSave": 0, + "name": "Термостат PID", + "type": "Writing", + "subtype": "ThermostatPID", + "id": "Thermo", + "widget": "anydataHum", + "page": "Климат", + "descr": "термостат", + "int": 60, + "round": 1, + "map": "1,100,1,100", + "set_id": "", + "term_id": "", + "rele": "", + "KP": 5.0, + "KI": 50, + "KD": 1.0 + }, + { + "global": 0, + "needSave": 0, + "name": "Термостат ЭТК", + "type": "Writing", + "subtype": "ThermostatETK", + "id": "Thermo", + "widget": "anydataTmp", + "page": "Климат", + "descr": "термостат", + "int": 60, + "round": 1, + "iv_k": 1, + "outside_id": "" + }, + { + "global": 0, + "needSave": 0, + "name": "Термостат ЭТК2 ", + "type": "Writing", + "subtype": "ThermostatETK2", + "id": "Thermo", + "widget": "anydataTmp", + "page": "Климат", + "descr": "термостат", + "int": 60, + "round": 1, + "set_id": "", + "term_id": "", + "iv_k": 1, + "outside_id": "" + } + ], + + "about": { + "authorName": "AVAKS", + "authorContact": "https://t.me/@avaks_dev", + "authorGit": "https://github.com/avaksru", + "specialThanks": "@Serghei63 за работу PID с обычным реле, Serg помощь в тестировании и устранении ошибок", + "moduleName": "Thermostat", + "moduleVersion": "1", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Термостат", + "moduleDesc": "Реализованы четыре варианта термостатирования: PID , Гистере́зис , эквитермические кривые, эквитермические кривые с учётом температуры в помещении", + "propInfo": { + "set_id": "Заданная температура в помещении", + "gist": "Гистере́зис - отклонение от заданной температуры при котором будет срабатывать термостат", + "term_id": "ID виджета термометра в помещении", + "term_rezerv_id": "ID резервного термометра в помещении", + "rele": "ID реле термостата", + "int": "интервал дискретизации термостата", + "outside_id": "ID уличного термометра", + "iv_k": "Эквитермическая кривая", + "KP": "Пропорциональный коэффициент PID ", + "KI": "Интегральный коэффициент PID ", + "KD": "Дифференциальный коэффициент PID ", + "round": "округление", + "map": "преобразование интервала значений" + }, + "funcInfo": [ + { + "name": "enable", + "descr": "включить / выключить термостатирование (режим AUTO) применим к PID и Гистере́зис ", + "params": ["thermostat.enable(1) - вкл, thermostat.enable(0) - выкл, "] + }, + { + "name": "KP", + "descr": "Пропорциональный коэффициент PID .", + "params": ["thermostat.KP(1) - задает значение коэффициента"] + }, + { + "name": "KI", + "descr": "Интегральный коэффициент PID .", + "params": ["thermostat.KI(1) - задает значение коэффициента"] + }, + { + "name": "KD", + "descr": "Дифференциальный коэффициент PID .", + "params": ["thermostat.KD(1) - задает значение коэффициента"] + } + ] + }, + + "defActive": false, + + "usedLibs": { + "esp32_4mb": [], + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [], + "esp8285_1mb": [], + "esp8285_1mb_ota": [] + } +}