├── .gitattributes ├── LICENSE ├── README.md ├── bin └── esp32dev │ ├── SmartTherm32_full_flash.zip │ ├── f_ot_debug.bin │ ├── firmware.bin │ ├── readme.txt │ ├── st │ └── firmware.bin │ ├── st2 │ └── firmware.bin │ └── st32 │ ├── firmware.bin │ └── firmware_id29fix.bin ├── ha ├── automations_0.yaml ├── automations_1.yaml └── test_automation.yaml ├── platformio.ini └── src ├── As_TCP.h ├── DeviceType.h ├── OT_slave.cpp ├── OpenTherm.cpp ├── OpenTherm.h ├── Pid.cpp ├── SD_MQTT.cpp ├── SD_OpenTherm.cpp ├── SD_OpenTherm.hpp ├── SD_pid_control.cpp ├── SmartDevice.cpp ├── SmartDevice.hpp ├── Smart_Config.h ├── Smart_commands.h ├── UDP_TCP.cpp ├── Web.cpp ├── main.cpp ├── myBuffer.cpp ├── mybuffer.hpp └── pid.hpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2024 Evgen2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmartTherm 2 | 3 | Version 0.8.4 4 | 5 | Open source for [SmartTherm](https://www.umkikit.ru/index.php?route=product/product&path=67&product_id=103) ESP8266/ESP32 OpenTherm controller 6 | 7 | Use: 8 | * code of [OpenTherm Library by ihormelnyk](https://github.com/ihormelnyk/opentherm_library) 9 | * [AutoConnect Library by Hieromon](https://github.com/Hieromon/AutoConnect) 10 | * [DS18B20 Library by robtillaart](https://github.com/RobTillaart/DS18B20_RT) 11 | 12 | Build with [PlatformIO](https://platformio.org/) 13 | 14 | Features: 15 | * [Captive portal](https://en.wikipedia.org/wiki/Captive_portal) before WiFi connection 16 | * Web interface after WiFi connection 17 | * [OpenTherm](https://en.wikipedia.org/wiki/OpenTherm) interface for Gas/Electric boiler contol (HVAC) 18 | * [Personal cloud control](https://github.com/Evgen2/SmartServer) used 19 | * [Android application for local/remote control](https://github.com/Evgen2/SmartThermClient) (betatest) 20 | * TCP/UDP API interface 21 | * up to 2 DS18B20 temperature sensors 22 | 23 | 0.8.4.1 24 | * bugfix 25 | 26 | 0.8.4 27 | * Add remote OT log 28 | * Close AP after conection to WiFi router after timeout 29 | 30 | 0.8.3 31 | * Add support for slave OpenTherm interface 32 | * Change MQTT server string up to 80 characters 33 | * MQTT settings read/write to separate config file 34 | * Add link to controller web page from HA MQTT device card 35 | * Add effective modulation for the previous hour MQTT sensor 36 | 37 | 0.8.2 38 | * Add build variant with onboard relay 39 | * Add support for OT:MaxRelModLevelSetting 40 | * Add support for OT:RemoteRequest (BLOR) 41 | 42 | 0.8.1 43 | * At PID startup and room setpoint change recalculate the integral part of PID 44 | to speed up reaching the target setpoint. I.e start and restart PID with non zero integral 45 | 46 | 0.8.0 47 | * TCP API changes for Andriod application & remote server support 48 | * PID changes 49 | * add use ID29 (Tstorage) as Indirect Water Heaters temperature for Buderus 50 | * Immergas fix 51 | 52 | 0.7.5 53 | * add WinterMode (ID0:HB5) and Use_OTC (ID0:HB3) support 54 | * speedup OT startup ~2 sec 55 | * add binary CH and HW sensors to MQTT 56 | * MQTT connect after detecting boiler Capabilities if OT work 57 | * MQTT connect to server without reset at MQTT config changes 58 | 59 | 60 | 0.7.4 changes 61 | * PID + weather-compensated automation (standalone + HA) 62 | 63 | 0.7.3 changes 64 | * fixed autoreconnect to WiFi 65 | 66 | 0.7.1 changes 67 | * Add MQTT and MQTT discovery for home assistant 68 | 69 | v 0.6 changes 70 | * TCP/UDP interface, Windows/Linux application [SmartServer](https://github.com/Evgen2/SmartServer) for TCP/UDP API 71 | * config saved and read after reboot 72 | * Hot water and CH2 enabled 73 | * Increased free RAM 74 | 75 | 76 | ## License 77 | Copyright (c) 2022-2024 Evgen2. Licensed under the [MIT license](/LICENSE?raw=true). -------------------------------------------------------------------------------- /bin/esp32dev/SmartTherm32_full_flash.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evgen2/SmartTherm/acd08179fa0b6fb23002a05f1c46f40b888a9bdb/bin/esp32dev/SmartTherm32_full_flash.zip -------------------------------------------------------------------------------- /bin/esp32dev/f_ot_debug.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evgen2/SmartTherm/acd08179fa0b6fb23002a05f1c46f40b888a9bdb/bin/esp32dev/f_ot_debug.bin -------------------------------------------------------------------------------- /bin/esp32dev/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evgen2/SmartTherm/acd08179fa0b6fb23002a05f1c46f40b888a9bdb/bin/esp32dev/firmware.bin -------------------------------------------------------------------------------- /bin/esp32dev/readme.txt: -------------------------------------------------------------------------------- 1 | /st32 controller SmartTherm32 2 | /st controller SmartTherm = SmartTherm32 + Relay 3 | /st2 controller SmartTherm 2 = SmartTherm32 + Relay + 2 OpenTherm (master+slave) 4 | 5 | 6 | -------------------------------------------------------------------------------- /bin/esp32dev/st/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evgen2/SmartTherm/acd08179fa0b6fb23002a05f1c46f40b888a9bdb/bin/esp32dev/st/firmware.bin -------------------------------------------------------------------------------- /bin/esp32dev/st2/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evgen2/SmartTherm/acd08179fa0b6fb23002a05f1c46f40b888a9bdb/bin/esp32dev/st2/firmware.bin -------------------------------------------------------------------------------- /bin/esp32dev/st32/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evgen2/SmartTherm/acd08179fa0b6fb23002a05f1c46f40b888a9bdb/bin/esp32dev/st32/firmware.bin -------------------------------------------------------------------------------- /bin/esp32dev/st32/firmware_id29fix.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evgen2/SmartTherm/acd08179fa0b6fb23002a05f1c46f40b888a9bdb/bin/esp32dev/st32/firmware_id29fix.bin -------------------------------------------------------------------------------- /ha/automations_0.yaml: -------------------------------------------------------------------------------- 1 | # Настройки->Автоматизации и сцены->Автоматизации->Создать автоматизацию->текстовый редактор 2 | # Автоматизация для Weather forecast from met.no, delivered by the Norwegian Meteorological Institute. 3 | # и SmartTherm v 0.7.5 - требуется ввести идентификатор объекта с именем 4 | # NAME(*) из настроек в вебинтерфейсе 5 | # Например: 6 | # * в SmartTherm имя устройства задали "Boiler" 7 | # * В HA в списке объектов появляется объект с именем "Boiler T outdoor" 8 | # и идентификатором "number.boiler_t_outdoor", 9 | # тогда в target_entity пишем так: 10 | # target_entity: number.boiler_t_outdoor 11 | # 12 | # Кроме того, проверьте идентификатор объекта для погоды. Он может быть 13 | # В Home Assistant Core "weather.forecast_home" 14 | # В Home Assistant OS "weather.forecast_home_assistant" 15 | 16 | alias: Report outdoor temp to SmartTerm from weather 17 | description: >- 18 | Script for reporting outdoor temperature to the SmartTerm controller from home 19 | assistant weather integration 20 | trigger: 21 | - platform: time_pattern 22 | seconds: /30 23 | condition: 24 | - condition: template 25 | value_template: >- 26 | {{ states(source_entity) != 'unavailable' and states(target_entity) != 27 | 'unavailable' }} 28 | action: 29 | - if: 30 | - condition: template 31 | value_template: >- 32 | {{ (state_attr(source_entity, 'temperature')|float(0) - 33 | states(target_entity)|float(0)) | abs | round(2) >= 0.1 }} 34 | then: 35 | - service: number.set_value 36 | data: 37 | value: "{{ state_attr(source_entity, 'temperature')|float(0)|round(2) }}" 38 | target: 39 | entity_id: "{{ target_entity }}" 40 | variables: 41 | source_entity: weather.forecast_home_assistant 42 | target_entity: number.NAME_t_outdoor 43 | # NAME - object id of device from SmartTherm web interface ->Setup -> MQTT device name 44 | # example: MQTT device name "Boiler", object name "Boiler T outdoor", object id "number.boiler_t_outdoor" 45 | # target_entity: number.boiler_t_outdoor 46 | mode: single 47 | -------------------------------------------------------------------------------- /ha/automations_1.yaml: -------------------------------------------------------------------------------- 1 | # Настройки->Автоматизации и сцены->Автоматизации->Создать автоматизацию->текстовый редактор 2 | # Автоматизация для передачи в SmartTherm комнатной температуры с датчика 3 | # для SmartTherm v 0.7.5 - требуется ввести идентификатор объекта с именем 4 | # NAME из настроек в вебинтерфейсе + в настройках PID 5 | # указать "Источник температуры в комнате:" 3 6 | # и подставить вместо YOUR_SENSOR ваш датчик температуры в HA 7 | 8 | # Например: 9 | # * в SmartTherm имя устройства задали "Boiler" 10 | # * В HA в списке объектов появляется объект с именем "Boiler T indoor" 11 | # и идентификатором "number.boiler_t_indoor", 12 | # тогда в target_entity пишем так: 13 | # target_entity: number.boiler_t_indoor 14 | # 15 | # Работает на 16 | # Core 2024.11.2 17 | # Supervisor 2024.11.2 18 | # Operating System 13.2 19 | # Пользовательский интерфейс 20241106.2 20 | 21 | 22 | 23 | alias: Report indoor temp to SmarTherm controller 24 | description: Script for reporting indoor temperature to the SmartTerm controller from home 25 | assistant temperature sensor 26 | triggers: 27 | - trigger: time_pattern 28 | seconds: /10 29 | conditions: 30 | - condition: template 31 | value_template: "{{ states('source_entity') != 'unavailable' }}" 32 | actions: 33 | - if: 34 | - condition: template 35 | value_template: >- 36 | {{ (states(source_entity)|float(0) - states(target_entity)|float(0)) | 37 | abs | round(2) >= 0.05 }} 38 | then: 39 | - action: number.set_value 40 | metadata: {} 41 | data: 42 | value: "{{ states(source_entity)|float(0)|round(2) }}" 43 | target: 44 | entity_id: "{{ target_entity }}" 45 | variables: 46 | source_entity: sensor.YOUR_SENSOR 47 | target_entity: number.NAME_t_indoor 48 | 49 | # target_entity: number.NAME_t_outdoor 50 | 51 | # NAME - object id of device from SmartTherm web interface ->Setup -> MQTT device name 52 | # example: MQTT device name "Boiler", object name "Boiler T indoor", object id "number.boiler_t_indoor" 53 | # target_entity: number.boiler_t_indoor 54 | 55 | 56 | mode: single 57 | -------------------------------------------------------------------------------- /ha/test_automation.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evgen2/SmartTherm/acd08179fa0b6fb23002a05f1c46f40b888a9bdb/ha/test_automation.yaml -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = esp32dev 13 | 14 | [env:esp32dev] 15 | platform = espressif32 16 | board = esp32dev 17 | framework = arduino 18 | lib_deps = 19 | robtillaart/DS18B20@^0.1.14 20 | bblanchon/ArduinoJson@6.21.5 21 | https://github.com/Evgen2/AutoConnect@^1.4.4 22 | https://github.com/Evgen2/arduino-home-assistant 23 | build_flags = 24 | -DCORE_DEBUG_LEVEL=0 25 | ; -DCORE_DEBUG_LEVEL=5 26 | ; -DAC_DEBUG 27 | ; -DARDUINOHA_DEBUG 28 | monitor_speed = 115200 29 | upload_port = COM[2] 30 | monitor_port = COM[2] 31 | monitor_filters = default, log2file 32 | 33 | [env:nodemcuv2] 34 | platform = espressif8266 35 | board = nodemcuv2 36 | framework = arduino 37 | lib_deps = 38 | robtillaart/DS18B20@^0.1.14 39 | bblanchon/ArduinoJson@6.21.5 40 | knolleary/PubSubClient@^2.8 41 | https://github.com/Evgen2/AutoConnect@^1.4.3 42 | https://github.com/Evgen2/arduino-home-assistant 43 | build_flags = 44 | -DB_NODEMSU 45 | -DAUTOCONNECT_NOUSE_JSON 46 | -DEX_ARDUINOHA_BUTTON 47 | -DEX_ARDUINOHA_CAMERA 48 | -DEX_ARDUINOHA_COVER 49 | -DEX_ARDUINOHA_DEVICE_TRACKER 50 | -DEX_ARDUINOHA_DEVICE_TRIGGER 51 | -DEX_ARDUINOHA_FAN 52 | -DEX_ARDUINOHA_LIGHT 53 | -DEX_ARDUINOHA_LOCK 54 | -DEX_ARDUINOHA_SCENE 55 | -DEX_ARDUINOHA_SELECT 56 | -DEX_ARDUINOHA_TAG_SCANNER 57 | monitor_speed = 115200 58 | upload_port = COM[9] 59 | monitor_port = COM[9] 60 | -------------------------------------------------------------------------------- /src/As_TCP.h: -------------------------------------------------------------------------------- 1 | /* As_TCP.h */ 2 | 3 | #include 4 | //#include 5 | #include 6 | 7 | class As_TCP 8 | { 9 | public: 10 | int sockfd; 11 | int sts; 12 | unsigned long _t0; 13 | int _timeout; 14 | int nraz; 15 | fd_set fdset; 16 | struct timeval tv; 17 | int id; 18 | 19 | As_TCP(void) 20 | { sts = 0; 21 | sockfd = -1; 22 | _t0 = _timeout = 0; 23 | nraz = 0; 24 | id = 0; 25 | } 26 | int connect_0(IPAddress ip, uint16_t port, int32_t _timeout); 27 | int connect_a(void); 28 | int read_0(void); 29 | int read_a(void); 30 | int Read(char bufin[], int len); 31 | void closeTCP(void); 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /src/DeviceType.h: -------------------------------------------------------------------------------- 1 | /* DeviceType.h */ 2 | 3 | /* 0 - Розетка, 4 | 1 - датчик температуры 5 | 2 - OpenTherm Wifi 6 | 3 - Мост ESP Now - UART/Wifi 7 | 4 - Реле Wifi 8 | 5 - контроллер эмулятора OpenTherm 9 | */ 10 | 11 | #define DS_PLUG 0 // розетка 12 | #define DS_TEMP 1 // датчик температуры 13 | #define DS_OPENTHERM 2 // контроллер OpenTherm 14 | #define DS_BRIDE 3 // Мост ESP Now - UART/Wifi 15 | #define DS_RELAY 4 // Реле Wifi 16 | #define DS_OTHERM_EM 5 // контроллер эмулятора OpenTherm 17 | #define DS_USER_APP 0x0100 // приложение пользователя 18 | #define DS_SMARTSERVER 0x1000 // сервер 19 | 20 | //типы датчиков температуры 21 | #define TEMP_DS18B20 1 22 | #define TEMP_DHT11 2 23 | -------------------------------------------------------------------------------- /src/OT_slave.cpp: -------------------------------------------------------------------------------- 1 | /* OT_slave.cpp */ 2 | 3 | 4 | #include 5 | 6 | #include "SmartDevice.hpp" 7 | #include "Smart_Config.h" 8 | 9 | #include "OpenTherm.h" 10 | #include "SD_OpenTherm.hpp" 11 | 12 | #if ST_VERS == 2 13 | 14 | /************************************/ 15 | extern OpenTherm ot_slave; 16 | extern SD_Termo SmOT; 17 | 18 | 19 | int OTslaveDebugInfo[12] ={0,0,0,0,0, 0,0,0,0,0, 0,0}; 20 | 21 | #if OTSLAVE_DEBUG 22 | void LogOT(int code, byte id, int messagetype, unsigned int u88); 23 | #else 24 | #define LogOT 25 | #endif 26 | /* 27 | 0 get request SUCCESS 28 | 1 get request ok, ot_SlaveRequest = request; 29 | 2 request from panel go to reqest to boiler 30 | 3 get response from slave (boiler), can send response to master (panel) 31 | 4 sendResponse to master (panel) 32 | -1 request TIMEOUT 33 | -2 request NONE 34 | -3 request INVALID 35 | -4 request parity ERR 36 | */ 37 | volatile int ot_SlaveSts = 0; 38 | volatile unsigned long ot_SlaveResponse = 0; 39 | volatile unsigned long ot_SlaveRequest = 0; 40 | unsigned long ot_SlaveRequest_ms = 0; 41 | int OT_slaveloop(void); 42 | int setup_ot_slave(void); 43 | void sendResponse_ot_slave(void); 44 | 45 | void OTlog(unsigned int reqresp, int sts); 46 | 47 | int nslaveint = 0; 48 | 49 | void IRAM_ATTR handleInterruptslave() { 50 | ot_slave.handleInterrupt(); 51 | nslaveint++; 52 | } 53 | 54 | void processRequest(unsigned long request, OpenThermResponseStatus status) { 55 | 56 | unsigned long response = 0; 57 | int parity, messagetype; 58 | static int timeOutcounter = 0; 59 | 60 | #if OT_SLAVE_DEBUG 61 | Serial.printf("Slave processRequest: request %x status %x\n", request, status); 62 | #endif 63 | if (status == OpenThermResponseStatus::SUCCESS) { 64 | ot_SlaveSts = 0; 65 | // SmOT.response = response; 66 | OTslaveDebugInfo[0]++; 67 | } else if (status == OpenThermResponseStatus::NONE) { 68 | // SmOT.stsOT = -1; // ?? 69 | ot_SlaveSts = -2; 70 | #if OT_DEBUG 71 | LogOT(-3, 0, 0, 0); 72 | #endif 73 | OTslaveDebugInfo[2]++; 74 | } else if (status == OpenThermResponseStatus::INVALID) { 75 | ot_SlaveSts = -3; 76 | //SmOT.stsOT = 1; 77 | #if OT_DEBUG 78 | LogOT(-2, 0, 0, 0); 79 | #endif 80 | OTslaveDebugInfo[3]++; 81 | } else if (status == OpenThermResponseStatus::TIMEOUT) { 82 | if(SmOT.ot_slave_stsOT != -1) 83 | { if(timeOutcounter > 10) 84 | { if(SmOT.ot_slave_stsOT != 2) 85 | SmOT.MQTT_need_report = 1; 86 | SmOT.ot_slave_stsOT = 2; 87 | } else { 88 | timeOutcounter++; 89 | } 90 | } 91 | 92 | // if( OTsts != -1) 93 | #if OT_DEBUG 94 | LogOT(-1, 0, 0, 0); 95 | #endif 96 | ot_SlaveSts = -1; 97 | OTslaveDebugInfo[4]++; 98 | return; 99 | } 100 | 101 | #if OT_DEBUG 102 | { unsigned int u88; 103 | byte iid; 104 | u88 = (request & 0xffff); 105 | iid = (request >> 16 & 0xFF); 106 | parity = otslave.parity(request); 107 | messagetype = otslave.getMessageType(request); 108 | if(parity) 109 | LogOT(0, iid, messagetype, u88); 110 | else 111 | LogOT(1, iid, messagetype, u88); 112 | } 113 | #endif 114 | 115 | parity = ot_slave.parity(request); 116 | if(parity) 117 | { OTslaveDebugInfo[1]++; 118 | ot_SlaveSts = -4; 119 | 120 | #if SERIAL_DEBUG 121 | Serial.println(F("Parity error")); 122 | #endif 123 | return; 124 | } 125 | 126 | OpenThermMessageID id = ot_slave.getDataID(request); 127 | uint16_t data = ot_slave.getUInt(request); 128 | messagetype = ot_slave.getMessageType(request); 129 | 130 | // Serial.printf("Slave processRequest: id %x data %x\n", id, data); 131 | 132 | if (!ot_slave.isValidRequest(request)) 133 | { 134 | Serial.printf("Err: invalidRequest %lx\n", request); 135 | //build UNKNOWN-DATAID response 136 | response = ot_slave.buildResponse(OpenThermMessageType::UNKNOWN_DATA_ID, ot_slave.getDataID(request), 0); 137 | //send response 138 | goto SR; 139 | // delay(20); //20..400ms, usually 100ms 140 | // ot.sendResponse(response); 141 | // return; 142 | } 143 | 144 | 145 | // float f = ot.getFloat(request); 146 | 147 | // Serial.printf("Message id %d\n", id); 148 | /*************************************************/ 149 | SmOT.ot_slave_t_lastwork = time(nullptr); 150 | ot_SlaveRequest_ms = millis(); 151 | ot_SlaveRequest = request; 152 | ot_SlaveSts = 1; 153 | SmOT.ot_slave_stsOT = timeOutcounter = 0; 154 | return; 155 | 156 | SR: 157 | // Serial.println("B" + String(response, HEX)); //slave/boiler response 158 | #if OTSLAVE_DEBUG 159 | 160 | { unsigned int u88; 161 | byte iid; 162 | u88 = (response & 0xffff); 163 | iid = (response >> 16 & 0xFF); 164 | parity = otslave.parity(response); 165 | messagetype = otslave.getMessageType(response); 166 | LogOT(5, iid, messagetype, u88); 167 | } 168 | #endif 169 | // Serial.printf("Slave processRequest: id %x data %x\n", id, data); 170 | 171 | SmOT.ot_slave_stsOT = 0; 172 | //send response 173 | ot_SlaveResponse = response; 174 | #if OT_DEBUGLOG 175 | OTlog(response, 3); 176 | #endif 177 | ot_SlaveSts = 3; 178 | // мы тут в прерывании. 179 | // напрямую из прерывания посылать ответ - плохо 180 | // delay(21); //20..400ms, usually 100ms 181 | // ot_slave.sendResponse(response); 182 | } 183 | 184 | int setup_ot_slave(void) 185 | { 186 | if(SmOT.ot_slave_stsOT == -2) 187 | { 188 | // Serial.printf("setup_slave\n"); 189 | 190 | ot_slave.begin(handleInterruptslave, processRequest); 191 | SmOT.ot_slave_stsOT = -1; 192 | } 193 | return 0; 194 | } 195 | 196 | void sendResponse_ot_slave(void) 197 | { 198 | int id; 199 | id = (ot_SlaveResponse >> 16 & 0xFF); 200 | nslaveint = 0; 201 | // if(ot_slave.getMessageType(ot_SlaveResponse) == DATA_INVALID) 202 | // Serial.printf("DATA_INVALID SlaveResponse 2\n"); 203 | 204 | ot_slave.sendResponse(ot_SlaveResponse); 205 | ot_SlaveSts = 4; 206 | } 207 | 208 | 209 | int OT_slaveloop(void) 210 | { 211 | ot_slave.process(); 212 | #if OT_SLAVE_DEBUG 213 | static unsigned long int t0=0; 214 | unsigned long int t; 215 | t = millis(); 216 | if(t-t0>500) 217 | { 218 | ot_SlaveResponse = ot_slave.buildResponse(OpenThermMessageType::READ_ACK, OpenThermMessageID::Status, 0xffff); 219 | Serial.printf("Slave test send Response: %x\n", ot_SlaveResponse); 220 | sendResponse_ot_slave(); 221 | 222 | t0 = t; 223 | } 224 | #else 225 | if(ot_SlaveSts == 3 && ot_slave.isReady()) 226 | { 227 | if(millis() - ot_SlaveRequest_ms > 21) //send response after 21 ms 228 | sendResponse_ot_slave(); 229 | } 230 | #endif 231 | 232 | { time_t now = time(nullptr); 233 | double dt; 234 | dt = difftime(now,SmOT.ot_slave_t_lastwork); 235 | if(dt > 10. && SmOT.ot_slave_stsOT == 0) 236 | SmOT.ot_slave_stsOT = 2; 237 | } 238 | 239 | return 0; 240 | } 241 | 242 | #endif //ST_VERS == 2 243 | 244 | -------------------------------------------------------------------------------- /src/OpenTherm.cpp: -------------------------------------------------------------------------------- 1 | /* slightly changed from v 1.13 + 5 July 2022 2 | https://github.com/ihormelnyk/opentherm_library 3 | 4 | */ 5 | /* 6 | OpenTherm.cpp - OpenTherm Communication Library For Arduino, ESP8266 7 | Copyright 2018, Ihor Melnyk 8 | */ 9 | 10 | #include "OpenTherm.h" 11 | 12 | OpenTherm::OpenTherm(int inPin, int outPin, bool isSlave): 13 | status(OpenThermStatus::NOT_INITIALIZED), 14 | inPin(inPin), 15 | outPin(outPin), 16 | isSlave(isSlave), 17 | response(0), 18 | responseStatus(OpenThermResponseStatus::NONE), 19 | responseTimestamp(0), 20 | handleInterruptCallback(NULL), 21 | processResponseCallback(NULL) 22 | { 23 | status = NOT_INITIALIZED; 24 | Lastresponse = 0; 25 | LastRequestId = 0; 26 | Immergas_fix = false; 27 | } 28 | 29 | //#define PULLUP 0x04 30 | //#define INPUT_PULLUP 0x05 31 | //#define PULLDOWN 0x08 32 | //#define INPUT_PULLDOWN 0x09 33 | 34 | void OpenTherm::begin(void(*handleInterruptCallback)(void), void(*processResponseCallback)(unsigned long, OpenThermResponseStatus)) 35 | { init_OTids(); 36 | if(isSlave) 37 | // pinMode(inPin, INPUT); 38 | // pinMode(inPin, INPUT_PULLDOWN); 39 | pinMode(inPin, INPUT_PULLUP); 40 | else 41 | pinMode(inPin, INPUT); 42 | pinMode(outPin, OUTPUT); 43 | if (handleInterruptCallback != NULL) { 44 | this->handleInterruptCallback = handleInterruptCallback; 45 | attachInterrupt(digitalPinToInterrupt(inPin), handleInterruptCallback, CHANGE); 46 | } 47 | activateBoiler(0); 48 | status = OpenThermStatus::READY; 49 | this->processResponseCallback = processResponseCallback; 50 | } 51 | 52 | /* 53 | void OpenTherm::begin(void(*handleInterruptCallback)(void)) 54 | { 55 | begin(handleInterruptCallback, NULL); 56 | } 57 | */ 58 | bool IRAM_ATTR OpenTherm::isReady() 59 | { 60 | return status == OpenThermStatus::READY; 61 | } 62 | 63 | int IRAM_ATTR OpenTherm::readState() { 64 | return digitalRead(inPin); 65 | } 66 | 67 | void OpenTherm::setActiveState() { 68 | digitalWrite(outPin, LOW); 69 | } 70 | 71 | void OpenTherm::setIdleState() { 72 | digitalWrite(outPin, HIGH); 73 | } 74 | 75 | void OpenTherm::activateBoiler(int wait) { 76 | setIdleState(); 77 | if(wait) 78 | delay(1000); 79 | } 80 | 81 | void OpenTherm::sendBit(bool high) { 82 | if (high) setActiveState(); else setIdleState(); 83 | delayMicroseconds(500); 84 | if (high) setIdleState(); else setActiveState(); 85 | delayMicroseconds(500); 86 | } 87 | 88 | bool OpenTherm::sendRequestAync(unsigned long request) 89 | { 90 | //Serial.println("Request: " + String(request, HEX)); 91 | noInterrupts(); 92 | const bool ready = isReady(); 93 | interrupts(); 94 | 95 | if (!ready) 96 | return false; 97 | 98 | status = OpenThermStatus::REQUEST_SENDING; 99 | response = 0; 100 | responseStatus = OpenThermResponseStatus::NONE; 101 | 102 | sendBit(HIGH); //start bit 103 | for (int i = 31; i >= 0; i--) { 104 | sendBit(bitRead(request, i)); 105 | } 106 | sendBit(HIGH); //stop bit 107 | setIdleState(); 108 | 109 | status = OpenThermStatus::RESPONSE_WAITING; 110 | responseTimestamp = micros(); 111 | return true; 112 | } 113 | 114 | unsigned long OpenTherm::sendRequest(unsigned long request) 115 | { 116 | response =0; 117 | if (!sendRequestAync(request)) return 0; 118 | while (!isReady()) { 119 | process(); 120 | yield(); 121 | } 122 | Lastresponse = response; 123 | return response; 124 | } 125 | 126 | bool OpenTherm::sendResponse(unsigned long request) 127 | { 128 | status = OpenThermStatus::REQUEST_SENDING; 129 | response = 0; 130 | responseStatus = OpenThermResponseStatus::NONE; 131 | 132 | sendBit(HIGH); //start bit 133 | for (int i = 31; i >= 0; i--) { 134 | sendBit(bitRead(request, i)); 135 | } 136 | sendBit(HIGH); //stop bit 137 | setIdleState(); 138 | status = OpenThermStatus::READY; 139 | return true; 140 | } 141 | 142 | unsigned long OpenTherm::getLastResponse() 143 | { 144 | return response; 145 | } 146 | 147 | OpenThermResponseStatus OpenTherm::getLastResponseStatus() 148 | { 149 | return responseStatus; 150 | } 151 | 152 | void IRAM_ATTR OpenTherm::handleInterrupt() 153 | { 154 | if (isReady()) 155 | { 156 | if (isSlave && readState() == HIGH) { 157 | status = OpenThermStatus::RESPONSE_WAITING; 158 | } 159 | else { 160 | return; 161 | } 162 | } 163 | 164 | unsigned long newTs = micros(); 165 | if (status == OpenThermStatus::RESPONSE_WAITING) { 166 | if (readState() == HIGH) { 167 | status = OpenThermStatus::RESPONSE_START_BIT; 168 | responseTimestamp = newTs; 169 | } 170 | else { 171 | status = OpenThermStatus::RESPONSE_INVALID; 172 | responseTimestamp = newTs; 173 | } 174 | } 175 | else if (status == OpenThermStatus::RESPONSE_START_BIT) { 176 | if ((newTs - responseTimestamp < 750) && readState() == LOW) { 177 | status = OpenThermStatus::RESPONSE_RECEIVING; 178 | responseTimestamp = newTs; 179 | responseBitIndex = 0; 180 | } 181 | else { 182 | status = OpenThermStatus::RESPONSE_INVALID; 183 | responseTimestamp = newTs; 184 | } 185 | } 186 | else if (status == OpenThermStatus::RESPONSE_RECEIVING) { 187 | if ((newTs - responseTimestamp) > 750) { 188 | if (responseBitIndex < 32) { 189 | response = (response << 1) | !readState(); 190 | responseTimestamp = newTs; 191 | responseBitIndex++; 192 | } 193 | else { //stop bit 194 | status = OpenThermStatus::RESPONSE_READY; 195 | responseTimestamp = newTs; 196 | } 197 | } 198 | } 199 | } 200 | 201 | void OpenTherm::process() 202 | { 203 | noInterrupts(); 204 | OpenThermStatus st = status; 205 | unsigned long ts = responseTimestamp; 206 | interrupts(); 207 | 208 | if (st == OpenThermStatus::READY) return; 209 | unsigned long newTs = micros(); 210 | if (st != OpenThermStatus::NOT_INITIALIZED && st != OpenThermStatus::DELAY && (newTs - ts) > 1000000) { 211 | status = OpenThermStatus::READY; 212 | responseStatus = OpenThermResponseStatus::TIMEOUT; 213 | if (processResponseCallback != NULL) { 214 | processResponseCallback(response, responseStatus); 215 | } 216 | } 217 | else if (st == OpenThermStatus::RESPONSE_INVALID) { 218 | status = OpenThermStatus::DELAY; 219 | responseStatus = OpenThermResponseStatus::INVALID; 220 | if (processResponseCallback != NULL) { 221 | processResponseCallback(response, responseStatus); 222 | } 223 | } 224 | else if (st == OpenThermStatus::RESPONSE_READY) { 225 | status = OpenThermStatus::DELAY; 226 | responseStatus = (isSlave ? isValidRequest(response) : isValidResponse(response)) ? OpenThermResponseStatus::SUCCESS : OpenThermResponseStatus::INVALID; 227 | if (processResponseCallback != NULL) { 228 | processResponseCallback(response, responseStatus); 229 | } 230 | } 231 | else if (st == OpenThermStatus::DELAY) { 232 | if ((newTs - ts) > 100000) { 233 | status = OpenThermStatus::READY; 234 | } 235 | } 236 | } 237 | 238 | bool OpenTherm::parity(unsigned long frame) //odd parity 239 | { 240 | byte p = 0; 241 | while (frame > 0) 242 | { 243 | if (frame & 1) p++; 244 | frame = frame >> 1; 245 | } 246 | return (p & 1); 247 | } 248 | 249 | OpenThermMessageType OpenTherm::getMessageType(unsigned long message) 250 | { 251 | OpenThermMessageType msg_type = static_cast((message >> 28) & 7); 252 | return msg_type; 253 | } 254 | 255 | OpenThermMessageID OpenTherm::getDataID(unsigned long frame) 256 | { 257 | return (OpenThermMessageID)((frame >> 16) & 0xFF); 258 | } 259 | 260 | unsigned long OpenTherm::buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data) 261 | { 262 | unsigned long request = data; 263 | if (type == OpenThermMessageType::WRITE_DATA) { 264 | request |= 1ul << 28; 265 | } 266 | request |= ((unsigned long)id) << 16; 267 | if (parity(request)) request |= (1ul << 31); 268 | LastRequestId = id; 269 | return request; 270 | } 271 | 272 | unsigned long OpenTherm::buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data) 273 | { 274 | unsigned long response = data; 275 | response |= ((unsigned long)type) << 28; 276 | response |= ((unsigned long)id) << 16; 277 | if (parity(response)) response |= (1ul << 31); 278 | return response; 279 | } 280 | 281 | bool OpenTherm::isValidResponse(unsigned long response) 282 | { 283 | if (parity(response)) return false; 284 | byte msgType = (response << 1) >> 29; 285 | return msgType == READ_ACK || msgType == WRITE_ACK; 286 | } 287 | 288 | bool OpenTherm::isValidRequest(unsigned long request) 289 | { 290 | if (parity(request)) return false; 291 | byte msgType = (request << 1) >> 29; 292 | return msgType == READ_DATA || msgType == WRITE_DATA; 293 | } 294 | 295 | void OpenTherm::end() { 296 | if (this->handleInterruptCallback != NULL) { 297 | detachInterrupt(digitalPinToInterrupt(inPin)); 298 | } 299 | } 300 | 301 | const char *OpenTherm::statusToString(OpenThermResponseStatus status) 302 | { 303 | switch (status) { 304 | case NONE: return "NONE"; 305 | case SUCCESS: return "SUCCESS"; 306 | case INVALID: return "INVALID"; 307 | case TIMEOUT: return "TIMEOUT"; 308 | default: return "UNKNOWN"; 309 | } 310 | } 311 | 312 | const char *OpenTherm::messageTypeToString(OpenThermMessageType message_type) 313 | { 314 | switch (message_type) { 315 | case READ_DATA: return "READ_DATA"; 316 | case WRITE_DATA: return "WRITE_DATA"; 317 | case INVALID_DATA: return "INVALID_DATA"; 318 | case RESERVED: return "RESERVED"; 319 | case READ_ACK: return "READ_ACK"; 320 | case WRITE_ACK: return "WRITE_ACK"; 321 | case DATA_INVALID: return "DATA_INVALID"; 322 | case UNKNOWN_DATA_ID: return "UNKNOWN_DATA_ID"; 323 | default: return "UNKNOWN"; 324 | } 325 | } 326 | 327 | //building requests 328 | 329 | unsigned long OpenTherm::buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool enableWinterMode) { 330 | unsigned int data = enableCentralHeating | (enableHotWater << 1) | (enableCooling << 2) | (enableOutsideTemperatureCompensation << 3) | (enableCentralHeating2 << 4) | (enableWinterMode << 5); 331 | data <<= 8; 332 | if(Immergas_fix) data |= 0xca; 333 | return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Status, data); 334 | } 335 | 336 | unsigned long OpenTherm::buildSetBoilerTemperatureRequest(float temperature) { 337 | unsigned int data = temperatureToData(temperature); 338 | return buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TSet, data); 339 | } 340 | 341 | unsigned long OpenTherm::buildSetBoilerCH2TemperatureRequest(float temperature) { 342 | unsigned int data = temperatureToData(temperature); 343 | return buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TsetCH2, data); 344 | } 345 | 346 | unsigned long OpenTherm::buildSetDHWSetpointTemperatureRequest(float temperature) { 347 | unsigned int data = temperatureToData(temperature); 348 | return buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TdhwSet, data); 349 | } 350 | 351 | unsigned long OpenTherm::buildGetBoilerTemperatureRequest() { 352 | return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tboiler, 0); 353 | } 354 | 355 | unsigned long OpenTherm::buildGetBoilerCH2TemperatureRequest() { 356 | return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::TflowCH2, 0); 357 | } 358 | 359 | 360 | //parsing responses 361 | bool OpenTherm::isFault(unsigned long _response) { 362 | return _response & 0x1; 363 | } 364 | 365 | bool OpenTherm::isCentralHeatingActive(unsigned long _response) { 366 | return _response & 0x2; 367 | } 368 | 369 | bool OpenTherm::isHotWaterActive(unsigned long _response) { 370 | return _response & 0x4; 371 | } 372 | 373 | bool OpenTherm::isFlameOn(unsigned long _response) { 374 | return _response & 0x8; 375 | } 376 | 377 | bool OpenTherm::isCoolingActive(unsigned long _response) { 378 | return _response & 0x10; 379 | } 380 | 381 | bool OpenTherm::isDiagnostic(unsigned long _response) { 382 | return _response & 0x40; 383 | } 384 | 385 | uint16_t OpenTherm::getUInt(const unsigned long _response) const { 386 | const uint16_t u88 = _response & 0xffff; 387 | return u88; 388 | } 389 | 390 | float OpenTherm::getFloat(const unsigned long _response) const { 391 | const uint16_t u88 = getUInt(_response); 392 | const float f = (u88 & 0x8000) ? -(0x10000L - u88) / 256.0f : u88 / 256.0f; 393 | return f; 394 | } 395 | 396 | unsigned int OpenTherm::temperatureToData(float temperature) { 397 | if (temperature < 0) temperature = 0; 398 | if (temperature > 100) temperature = 100; 399 | unsigned int data = (unsigned int)(temperature * 256); 400 | return data; 401 | } 402 | 403 | //basic requests 404 | #if 0 405 | 406 | unsigned long OpenTherm::setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2) { 407 | return sendRequest(buildSetBoilerStatusRequest(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2)); 408 | } 409 | 410 | bool OpenTherm::setBoilerTemperature(float temperature) { 411 | unsigned long response_tmp = sendRequest(buildSetBoilerTemperatureRequest(temperature)); 412 | return isValidResponse(response_tmp); 413 | } 414 | 415 | float OpenTherm::getBoilerTemperature() { 416 | unsigned long response_tmp = sendRequest(buildGetBoilerTemperatureRequest()); 417 | return isValidResponse(response_tmp) ? getFloat(response_tmp) : 0; 418 | } 419 | 420 | float OpenTherm::getReturnTemperature() { 421 | unsigned long response_tmp = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tret, 0)); 422 | return isValidResponse(response_tmp) ? getFloat(response_tmp) : 0; 423 | } 424 | 425 | bool OpenTherm::setDHWSetpoint(float temperature) { 426 | unsigned int data = temperatureToData(temperature); 427 | unsigned long response_tmp = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TdhwSet, data)); 428 | return isValidResponse(response_tmp); 429 | } 430 | 431 | float OpenTherm::getDHWTemperature() { 432 | unsigned long response_tmp = sendRequest(buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tdhw, 0)); 433 | return isValidResponse(response_tmp) ? getFloat(response_tmp) : 0; 434 | } 435 | 436 | float OpenTherm::getModulation() { 437 | unsigned long response_tmp = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::RelModLevel, 0)); 438 | return isValidResponse(response_tmp) ? getFloat(response_tmp) : 0; 439 | } 440 | 441 | float OpenTherm::getPressure() { 442 | unsigned long response_tmp = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::CHPressure, 0)); 443 | return isValidResponse(response_tmp) ? getFloat(response_tmp) : 0; 444 | } 445 | 446 | unsigned char OpenTherm::getFault() { 447 | return ((sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0)) >> 8) & 0xff); 448 | } 449 | 450 | #endif //0 451 | 452 | #if defined(ARDUINO_ARCH_ESP8266) 453 | #define OTD(x) 454 | #elif defined(ARDUINO_ARCH_ESP32) 455 | #define OTD(x) x 456 | #endif 457 | 458 | OpenThermID OT_ids[N_OT_NIDS] = 459 | { 460 | { Status, OtRW, FLAG8, FLAG8, 1, 0, 0, OTD("Master and Slave Status flags") }, 461 | { TSet, OtW, F88, OTNONE, 0, 0, 0, OTD("Control setpoint ie CH water temperature setpoint (°C)")}, 462 | { MConfigMMemberIDcode, OtW, FLAG8, OTU8, 0, 0, 0, OTD("Master Configuration Flags / Master MemberID Code") }, 463 | { SConfigSMemberIDcode, OtR, FLAG8, OTU8, 0, 0, 0, OTD("Slave Configuration Flags / Slave MemberID Code") }, 464 | { RemoteRequest, OtW, OTU8, OTU8, 0, 0, 0, OTD("Remote Command")}, 465 | { ASFflags, OtR, FLAG8, OTU8, 0, 0, 0, OTD("OEM-fault-code Application-specific fault flags and OEM fault code")}, 466 | { RBPflags, OtR, FLAG8, FLAG8, 0, 0, 0, OTD("Remote boiler parameter transfer-enable & read/write flags")}, 467 | { CoolingControl, OtW, F88, OTNONE, 0, 0, 0, OTD("Cooling control signal (%)")}, 468 | { TsetCH2, OtW, F88, OTNONE, 0, 0, 0, OTD("Control setpoint for 2e CH circuit (°C)")}, 469 | { TrOverride, OtR, F88, OTNONE, 0, 0, 0, OTD("Remote override room setpoint")}, 470 | 471 | { TSP, OtR, OTU8, OTU8, 0, 0, 0, OTD("Number of Transparent-Slave-Parameters supported by slave")}, 472 | { TSPindexTSPvalue, OtRW, OTU8, OTU8, 0, 0, 0, OTD("Index number / Value of referred-to transparent slave parameter")}, 473 | { FHBsize, OtR, OTU8, OTU8, 0, 0, 0, OTD("Size of Fault-History-Buffer supported by slave")}, 474 | { FHBindexFHBvalue, OtR, OTU8, OTU8, 0, 0, 0, OTD("Index number / Value of referred-to fault-history buffer entry")}, 475 | { MaxRelModLevelSetting, OtW, F88, OTNONE, 0, 0, 0, OTD("Maximum relative modulation level setting (%)")}, 476 | { MaxCapacityMinModLevel, OtR, OTU8, OTU8, 0, 0, 0, OTD("Maximum boiler capacity (kW) / Minimum boiler modulation level(%)")}, 477 | { TrSet, OtW, F88, OTNONE, 0, 0, 0, OTD("Room Setpoint (°C)")}, 478 | { RelModLevel, OtR, F88, OTNONE, 0, 0, 0, OTD("Relative Modulation Level (%)")}, 479 | { CHPressure, OtR, F88, OTNONE, 0, 0, 0, OTD("Water pressure in CH circuit (bar)")}, 480 | { DHWFlowRate, OtR, F88, OTNONE, 0, 0, 0, OTD("Water flow rate in DHW circuit. (litres/minute)")}, 481 | 482 | { DayTime, OtRW, OTSP, OTU8, 0, 0, 0, OTD("Day of Week and Time of Day")}, 483 | { Date, OtRW, OTU8, OTU8, 0, 0, 0, OTD("Calendar date")}, 484 | { Year, OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Calendar year")}, 485 | { TrSetCH2, OtW, F88, OTNONE, 0, 0, 0, OTD("Room Setpoint for 2nd CH circuit (°C)")}, 486 | { Tr, OtW, F88, OTNONE, 0, 0, 0, OTD("Room temperature (°C)")}, 487 | { Tboiler, OtR, F88, OTNONE, 0, 0, 0, OTD("Boiler flow water temperature (°C)")}, 488 | { Tdhw, OtR, F88, OTNONE, 0, 0, 0, OTD("DHW temperature (°C)")}, 489 | { Toutside, OtR, F88, OTNONE, 0, 0, 0, OTD("Outside temperature (°C)")}, 490 | { Tret, OtR, F88, OTNONE, 0, 0, 0, OTD("Return water temperature (°C)")}, 491 | { Tstorage, OtR, F88, OTNONE, 0, 0, 0, OTD("Solar storage temperature (°C)")}, 492 | 493 | { Tcollector, OtR, F88, OTNONE, 0, 0, 0, OTD("Solar collector temperature (°C)")}, //s16 (p26) or f8.8 (p32) ?? 494 | { TflowCH2, OtR, F88, OTNONE, 0, 0, 0, OTD("Flow water temperature CH2 circuit (°C)")}, 495 | { Tdhw2, OtR, F88, OTNONE, 0, 0, 0, OTD("Domestic hot water temperature 2 (°C)")}, 496 | { Texhaust, OtR, OTS16, OTNONE, 0, 0, 0, OTD("Boiler exhaust temperature (°C)")}, 497 | { TdhwSetUBTdhwSetLB, OtRW, OTS8, OTS8, 0, 0, 0, OTD("DHW setpoint upper & lower bounds for adjustment (°C)")}, //48 498 | { MaxTSetUBMaxTSetLB, OtRW, OTS8, OTS8, 0, 0, 0, OTD("Max CH water setpoint upper & lower bounds for adjustment (°C)")}, 499 | { HcratioUBHcratioLB, OtRW, OTS8, OTS8, 0, 0, 0, OTD("OTC heat curve ratio upper & lower bounds for adjustment")}, 500 | { TdhwSet, OtRW, F88, OTNONE, 0, 0, 0, OTD("DHW setpoint (°C) (Remote parameter 1)")}, // 56 501 | { MaxTSet, OtRW, F88, OTNONE, 0, 0, 0, OTD("Max CH water setpoint (°C) (Remote parameters 2)")}, 502 | { Hcratio, OtRW, F88, OTNONE, 0, 0, 0, OTD("f8.8 OTC heat curve ratio (°C) (Remote parameter 3)")}, 503 | 504 | { RemoteOverrideFunction, OtR, FLAG8, OTNONE, 0, 0, 0, OTD("Function of manual and program changes in master and remote room setpoint")}, // = 100 505 | { OEMDiagnosticCode, OtR, OTU16, OTNONE, 0, 0, 0, OTD("OEM-specific diagnostic/service code")}, // = 115 506 | { SuccessfulBurnerStarts, OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Number of succesful starts burner")}, 507 | { CHPumpStarts, OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Number of starts CH pump")}, 508 | { DHWPumpValveStarts, OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Number of starts DHW pump/valve")}, 509 | 510 | { DHWBurnerStarts, OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Number of starts burner during DHW mode")}, 511 | { BurnerOperationHours, OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Number of hours that burner is in operation (i.e. flame on)")}, 512 | { CHPumpOperationHours, OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Number of hours that CH pump has been running")}, 513 | { DHWPumpValveOperationHours, 514 | OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Number of hours that DHW pump has been running or DHW valve has been opened")}, 515 | { DHWBurnerOperationHours, 516 | OtRW, OTU16, OTNONE, 0, 0, 0, OTD("Number of hours that burner is in operation during DHW mode")}, 517 | 518 | { OpenThermVersionMaster, OtW, F88, OTNONE, 0, 0, 0, OTD("The implemented version of the OpenTherm Protocol Specification in the master")}, 519 | { OpenThermVersionSlave, OtR, F88, OTNONE, 0, 0, 0, OTD("The implemented version of the OpenTherm Protocol Specification in the slave")}, 520 | { MasterVersion, OtW, OTU8, OTU8, 0, 0, 0, OTD("Master product version number and type")}, 521 | { SlaveVersion, OtR, OTU8, OTU8, 0, 0, 0, OTD("Slave product version number and type")} 522 | 523 | }; 524 | 525 | byte id_to_index[128]; 526 | 527 | void OpenTherm::init_OTids(void) 528 | { int i; 529 | i = 0; 530 | for(i=0; i 127) 556 | return 1; 557 | ind = id_to_index[id]; 558 | if(ind == -1) 559 | return 2; 560 | if(OT_ids[ind].used == 2) 561 | { 562 | OT_ids[ind].count++; 563 | if(sts ) 564 | OT_ids[ind].countOk++; 565 | //Serial.printf("OT_ids[%d].count %d %d\n", ind, OT_ids[ind].count, OT_ids[ind].countOk ); 566 | if(OT_ids[ind].count > 8) 567 | { 568 | if(OT_ids[ind].countOk > 4) 569 | OT_ids[ind].used = 1; 570 | else 571 | OT_ids[ind].used = 0; 572 | } 573 | } 574 | 575 | return 0; 576 | } 577 | 578 | int OpenTherm::Get_OTid_count(OpenThermMessageID id, int &count, int &countok) 579 | { int ind; 580 | ind = id_to_index[id]; 581 | count = OT_ids[ind].count; 582 | countok= OT_ids[ind].countOk; 583 | return OT_ids[ind].used; 584 | } 585 | 586 | // use https://github.com/Jeroen88/EasyOpenTherm/blob/main/src/EasyOpenTherm.h 587 | OpenThermVendor OTvendorList[] = 588 | { 1, "Baxi Fourtech/Luna 3", 589 | 2, "AWB/Brink/Viessmann", 590 | 4, "Baxi Slim", 591 | 5, "Itho Daalderop", 592 | 6, "IDEAL", 593 | 8, "Buderus/Bosch/Hoval", 594 | 9, "Ferrolli", 595 | 11, "Remeha", 596 | 16, "Unical", 597 | 24, "Vaillant/Bulex", 598 | 27, "Baxi Eco4s/Luna Duo-Tec P67=0", 599 | 29, "Itho Daalderop", 600 | 33, "Viessmann", 601 | 41, "Italtherm/Radiant", 602 | 56, "Baxi Luna Duo-Tec P67=2", 603 | 131, "Nefit", 604 | 148, "Navien", 605 | 173, "Intergas", 606 | 247, "Baxi Ampera", 607 | 248, "Zota" // Lux-X, mk-s plus 608 | }; 609 | 610 | const char * GetOTVendorName(int id) 611 | { int i, n; 612 | n = sizeof(OTvendorList)/sizeof(OpenThermVendor); 613 | for(i=0;i 20 | #include 21 | 22 | 23 | 24 | enum OpenThermResponseStatus { 25 | NONE, 26 | SUCCESS, 27 | INVALID, 28 | TIMEOUT 29 | }; 30 | 31 | 32 | enum OpenThermMessageType { 33 | /* Master to Slave */ 34 | READ_DATA = B000, 35 | READ = READ_DATA, // for backwared compatibility 36 | WRITE_DATA = B001, 37 | WRITE = WRITE_DATA, // for backwared compatibility 38 | INVALID_DATA = B010, 39 | RESERVED = B011, 40 | /* Slave to Master */ 41 | READ_ACK = B100, 42 | WRITE_ACK = B101, 43 | DATA_INVALID = B110, 44 | UNKNOWN_DATA_ID = B111 45 | }; 46 | 47 | typedef OpenThermMessageType OpenThermRequestType; // for backwared compatibility 48 | 49 | enum OpenThermMessageID { 50 | /* 51 | В частности по регистру ID0: 52 | 53 | ID0:HB0: Master status: CH enable 54 | ID0:HB1: Master status: DHW enable 55 | ID0:HB2: Master status: Cooling enable 56 | ID0:HB3: Master status: OTC active (OTC = Outside Temperature Compensation) 57 | ID0:HB4: Master status: CH2 enable 58 | ID0:HB5: Master status: Summer/winter mode 59 | ID0:HB6: Master status: DHW blocking 60 | ID0:LB0: Slave Status: Fault indication 61 | ID0:LB1: Slave Status: CH mode 62 | ID0:LB2: Slave Status: DHW mode 63 | ID0:LB3: Slave Status: Flame status 64 | ID0:LB4: Slave Status: Cooling status 65 | ID0:LB5: Slave Status: CH2 mode 66 | ID0:LB6: Slave Status: Diagnostic/service indication 67 | ID0:LB7: Slave Status: Electricity production (???) 68 | */ 69 | Status = 0, // flag8/flag8 Master and Slave Status flags. 70 | TSet = 1, // f8.8 Control Setpoint i.e.CH water temperature Setpoint(°C) 71 | MConfigMMemberIDcode = 2, // flag8/u8 Master Configuration Flags / Master MemberID Code 72 | SConfigSMemberIDcode = 3, // flag8/u8 Slave Configuration Flags / Slave MemberID Code 73 | RemoteRequest = 4, // u8/u8 Remote Request 74 | ASFflags = 5, // flag8/u8 Application - specific fault flags and OEM fault code 75 | RBPflags = 6, // flag8/flag8 Remote boiler parameter transfer - enable & read / write flags 76 | CoolingControl = 7, // f8.8 Cooling control signal(%) 77 | TsetCH2 = 8, // f8.8 Control Setpoint for 2e CH circuit(°C) 78 | TrOverride = 9, // f8.8 Remote override room Setpoint 79 | TSP = 10, // u8/u8 Number of Transparent - Slave - Parameters supported by slave 80 | TSPindexTSPvalue = 11, // u8/u8 Index number / Value of referred - to transparent slave parameter. 81 | FHBsize = 12, // u8/u8 Size of Fault - History - Buffer supported by slave 82 | FHBindexFHBvalue = 13, // u8/u8 Index number / Value of referred - to fault - history buffer entry. 83 | MaxRelModLevelSetting = 14, // f8.8 Maximum relative modulation level setting(%) 84 | MaxCapacityMinModLevel = 15, // u8/u8 Maximum boiler capacity(kW) / Minimum boiler modulation level(%) 85 | TrSet = 16, // f8.8 Room Setpoint(°C) 86 | RelModLevel = 17, // f8.8 Relative Modulation Level(%) 87 | CHPressure = 18, // f8.8 Water pressure in CH circuit(bar) 88 | DHWFlowRate = 19, // f8.8 Water flow rate in DHW circuit. (litres / minute) 89 | DayTime = 20, // special/u8 Day of Week and Time of Day 90 | Date = 21, // u8/u8 Calendar date 91 | Year = 22, // u16 Calendar year 92 | TrSetCH2 = 23, // f8.8 Room Setpoint for 2nd CH circuit(°C) 93 | Tr = 24, // f8.8 Room temperature(°C) 94 | Tboiler = 25, // f8.8 Boiler flow water temperature(°C) 95 | Tdhw = 26, // f8.8 DHW temperature(°C) 96 | Toutside = 27, // f8.8 Outside temperature(°C) 97 | Tret = 28, // f8.8 Return water temperature(°C) 98 | Tstorage = 29, // f8.8 Solar storage temperature(°C) 99 | Tcollector = 30, // f8.8 Solar collector temperature(°C) 100 | TflowCH2 = 31, // f8.8 Flow water temperature CH2 circuit(°C) 101 | Tdhw2 = 32, // f8.8 Domestic hot water temperature 2 (°C) 102 | Texhaust = 33, // s16 Boiler exhaust temperature(°C) 103 | TboilerHeatExchanger = 34, // f8.8 Boiler heat exchanger temperature(°C) 104 | BoilerFanSpeedSetpointAndActual = 35, // u8/u8 Boiler fan speed Setpoint and actual value 105 | FlameCurrent = 36, // f8.8 Electrical current through burner flame[μA] 106 | TrCH2 = 37, // f8.8 Room temperature for 2nd CH circuit(°C) 107 | RelativeHumidity = 38, // f8.8 Actual relative humidity as a percentage 108 | TrOverride2 = 39, // f8.8 Remote Override Room Setpoint 2 109 | 110 | TdhwSetUBTdhwSetLB = 48, // s8/s8 DHW Setpoint upper & lower bounds for adjustment(°C) 111 | MaxTSetUBMaxTSetLB = 49, // s8/s8 Max CH water Setpoint upper & lower bounds for adjustment(°C) 112 | HcratioUBHcratioLB = 50, // s8 / s8 OTC heat curve ratio upper & lower bounds for adjustment 113 | 114 | TdhwSet = 56, // f8.8 DHW Setpoint(°C) (Remote parameter 1) 115 | MaxTSet = 57, // f8.8 Max CH water Setpoint(°C) (Remote parameters 2) 116 | Hcratio = 58, // f8.8 OTC heat curve ratio (°C) (Remote parameter 3) 117 | 118 | StatusVentilationHeatRecovery = 70, // flag8/flag8 Master and Slave Status flags ventilation / heat - recovery 119 | Vset = 71, // -/u8 Relative ventilation position (0-100%). 0% is the minimum set ventilation and 100% is the maximum set ventilation. 120 | ASFflagsOEMfaultCodeVentilationHeatRecovery = 72, // flag8/u8 Application-specific fault flags and OEM fault code ventilation / heat-recovery 121 | OEMDiagnosticCodeVentilationHeatRecovery = 73, // u16 An OEM-specific diagnostic/service code for ventilation / heat-recovery system 122 | SConfigSMemberIDCodeVentilationHeatRecovery = 74, // flag8/u8 Slave Configuration Flags / Slave MemberID Code ventilation / heat-recovery 123 | OpenThermVersionVentilationHeatRecovery = 75, // f8.8 The implemented version of the OpenTherm Protocol Specification in the ventilation / heat-recovery system. 124 | VentilationHeatRecoveryVersion = 76, // u8/u8 Ventilation / heat-recovery product version number and type 125 | RelVentLevel = 77, // -/u8 Relative ventilation (0-100%) 126 | RHexhaust = 78, // -/u8 Relative humidity exhaust air (0-100%) 127 | CO2exhaust = 79, // u16 CO2 level exhaust air (0-2000 ppm) 128 | Tsi = 80, // f8.8 Supply inlet temperature (°C) 129 | Tso = 81, // f8.8 Supply outlet temperature (°C) 130 | Tei = 82, // f8.8 Exhaust inlet temperature (°C) 131 | Teo = 83, // f8.8 Exhaust outlet temperature (°C) 132 | RPMexhaust = 84, // u16 Exhaust fan speed in rpm 133 | RPMsupply = 85, // u16 Supply fan speed in rpm 134 | RBPflagsVentilationHeatRecovery = 86, // flag8/flag8 Remote ventilation / heat-recovery parameter transfer-enable & read/write flags 135 | NominalVentilationValue = 87, // u8/- Nominal relative value for ventilation (0-100 %) 136 | TSPventilationHeatRecovery = 88, // u8/u8 Number of Transparent-Slave-Parameters supported by TSP’s ventilation / heat-recovery 137 | TSPindexTSPvalueVentilationHeatRecovery = 89, // u8/u8 Index number / Value of referred-to transparent TSP’s ventilation / heat-recovery parameter. 138 | FHBsizeVentilationHeatRecovery = 90, // u8/u8 Size of Fault-History-Buffer supported by ventilation / heat-recovery 139 | FHBindexFHBvalueVentilationHeatRecovery = 91, // u8/u8 Index number / Value of referred-to fault-history buffer entry ventilation / heat-recovery 140 | 141 | Brand = 93, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number 142 | BrandVersion = 94, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number 143 | BrandSerialNumber = 95, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number 144 | CoolingOperationHours = 96, // u16 Number of hours that the slave is in Cooling Mode. Reset by zero is optional for slave 145 | PowerCycles = 97, // u16 Number of Power Cycles of a slave (wake-up after Reset), Reset by zero is optional for slave 146 | RFsensorStatusInformation = 98, // special/special For a specific RF sensor the RF strength and battery level is written 147 | RemoteOverrideOperatingModeHeatingDHW = 99, // special/special Operating Mode HC1, HC2/ Operating Mode DHW 148 | RemoteOverrideFunction = 100, // flag8/- Function of manual and program changes in master and remote room Setpoint 149 | StatusSolarStorage = 101, // flag8/flag8 Master and Slave Status flags Solar Storage 150 | ASFflagsOEMfaultCodeSolarStorage = 102, // flag8/u8 Application-specific fault flags and OEM fault code Solar Storage 151 | SConfigSMemberIDcodeSolarStorage = 103, // flag8/u8 Slave Configuration Flags / Slave MemberID Code Solar Storage 152 | SolarStorageVersion = 104, // u8/u8 Solar Storage product version number and type 153 | TSPSolarStorage = 105, // u8/u8 Number of Transparent - Slave - Parameters supported by TSP’s Solar Storage 154 | TSPindexTSPvalueSolarStorage = 106, // u8/u8 Index number / Value of referred - to transparent TSP’s Solar Storage parameter. 155 | FHBsizeSolarStorage = 107, // u8/u8 Size of Fault - History - Buffer supported by Solar Storage 156 | FHBindexFHBvalueSolarStorage = 108, // u8/u8 Index number / Value of referred - to fault - history buffer entry Solar Storage 157 | ElectricityProducerStarts = 109, // U16 Number of start of the electricity producer. 158 | ElectricityProducerHours = 110, // U16 Number of hours the electricity produces is in operation 159 | ElectricityProduction = 111, // U16 Current electricity production in Watt. 160 | CumulativElectricityProduction = 112, // U16 Cumulative electricity production in KWh. 161 | UnsuccessfulBurnerStarts = 113, // u16 Number of un - successful burner starts 162 | FlameSignalTooLowNumber = 114, // u16 Number of times flame signal was too low 163 | OEMDiagnosticCode = 115, // u16 OEM - specific diagnostic / service code 164 | 165 | SuccessfulBurnerStarts = 116, // u16 Number of succesful starts burner 166 | CHPumpStarts = 117, // u16 Number of starts CH pump 167 | DHWPumpValveStarts = 118, // u16 Number of starts DHW pump / valve 168 | DHWBurnerStarts = 119, // u16 Number of starts burner during DHW mode 169 | BurnerOperationHours = 120, // u16 Number of hours that burner is in operation(i.e.flame on) 170 | CHPumpOperationHours = 121, // u16 Number of hours that CH pump has been running 171 | DHWPumpValveOperationHours = 122, // u16 Number of hours that DHW pump has been running or DHW valve has been opened 172 | DHWBurnerOperationHours = 123, // u16 Number of hours that burner is in operation during DHW mode 173 | OpenThermVersionMaster = 124, // f8.8 The implemented version of the OpenTherm Protocol Specification in the master. 174 | OpenThermVersionSlave = 125, // f8.8 The implemented version of the OpenTherm Protocol Specification in the slave. 175 | MasterVersion = 126, // u8/u8 Master product version number and type 176 | SlaveVersion = 127, // u8/u8 Slave product version number and type 177 | }; 178 | 179 | enum OpenThermStatus { 180 | NOT_INITIALIZED, 181 | READY, 182 | DELAY, 183 | REQUEST_SENDING, 184 | RESPONSE_WAITING, 185 | RESPONSE_START_BIT, 186 | RESPONSE_RECEIVING, 187 | RESPONSE_READY, 188 | RESPONSE_INVALID 189 | }; 190 | 191 | class OpenTherm 192 | { 193 | public: 194 | 195 | OpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false); 196 | int LastRequestId; 197 | volatile OpenThermStatus status; 198 | unsigned long Lastresponse; 199 | bool Immergas_fix; 200 | // (1) https://arduino.ru/forum/programmirovanie/termostat-opentherm-na-esp8266?page=15#comment-649392 201 | // (2) https://github.com/Laxilef/OTGateway/releases v1.4.5 202 | // (3) https://github.com/esp32m/core/blob/master/esp32m/include/esp32m/dev/opentherm.hpp 609 class ImmergasMaster 203 | 204 | // void begin(void(*handleInterruptCallback)(void)); 205 | void begin(void(*handleInterruptCallback)(void), void(*processResponseCallback)(unsigned long, OpenThermResponseStatus)); 206 | 207 | void init_OTids(void); 208 | int update_OTid(int id, int sts); 209 | int OTid_used(OpenThermMessageID id); 210 | int Get_OTid_count(OpenThermMessageID id, int &count, int &countok); 211 | 212 | bool isReady(); 213 | unsigned long sendRequest(unsigned long request); 214 | bool sendResponse(unsigned long request); 215 | bool sendRequestAync(unsigned long request); 216 | unsigned long buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data); 217 | unsigned long buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data); 218 | unsigned long getLastResponse(); 219 | OpenThermResponseStatus getLastResponseStatus(); 220 | const char *statusToString(OpenThermResponseStatus status); 221 | void handleInterrupt(); 222 | void process(); 223 | void end(); 224 | 225 | bool parity(unsigned long frame); 226 | OpenThermMessageType getMessageType(unsigned long message); 227 | OpenThermMessageID getDataID(unsigned long frame); 228 | const char *messageTypeToString(OpenThermMessageType message_type); 229 | bool isValidRequest(unsigned long request); 230 | bool isValidResponse(unsigned long response); 231 | 232 | //requests 233 | unsigned long buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false, bool enableWinterMode = false); 234 | unsigned long buildSetBoilerTemperatureRequest(float temperature); 235 | unsigned long buildSetBoilerCH2TemperatureRequest(float temperature); 236 | unsigned long buildGetBoilerTemperatureRequest(); 237 | unsigned long buildGetBoilerCH2TemperatureRequest(); 238 | unsigned long buildSetDHWSetpointTemperatureRequest(float temperature); 239 | 240 | //responses 241 | bool isFault(unsigned long response); 242 | bool isCentralHeatingActive(unsigned long response); 243 | bool isHotWaterActive(unsigned long response); 244 | bool isFlameOn(unsigned long response); 245 | bool isCoolingActive(unsigned long response); 246 | bool isDiagnostic(unsigned long response); 247 | uint16_t getUInt(const unsigned long response) const; 248 | float getFloat(const unsigned long response) const; 249 | unsigned int temperatureToData(float temperature); 250 | 251 | //basic requests 252 | #if 0 253 | unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false); 254 | bool setBoilerTemperature(float temperature); 255 | float getBoilerTemperature(); 256 | float getReturnTemperature(); 257 | bool setDHWSetpoint(float temperature); 258 | float getDHWTemperature(); 259 | float getModulation(); 260 | float getPressure(); 261 | unsigned char getFault(); 262 | #endif 263 | private: 264 | const int inPin; 265 | const int outPin; 266 | const bool isSlave; 267 | 268 | volatile unsigned long response; 269 | volatile OpenThermResponseStatus responseStatus; 270 | volatile unsigned long responseTimestamp; 271 | volatile byte responseBitIndex; 272 | 273 | int readState(); 274 | void setActiveState(); 275 | void setIdleState(); 276 | void activateBoiler(int wait); 277 | 278 | void sendBit(bool high); 279 | void(*handleInterruptCallback)(); 280 | void(*processResponseCallback)(unsigned long, OpenThermResponseStatus); 281 | }; 282 | 283 | 284 | enum OpenThermRWtype { 285 | ONONE, 286 | OtR, 287 | OtW, 288 | OtRW 289 | }; 290 | 291 | enum OpenThermMSGpartype { 292 | OTNONE, 293 | FLAG8, //flag8 294 | OTU8, // u8 295 | OTS8, // s8 296 | OTU16, // u16 297 | OTS16, // s16 298 | F88, // f8.8 299 | OTSP // special (DAY TIME only) 300 | }; 301 | 302 | 303 | #define N_OT_NIDS 54 304 | 305 | //ESP8266: too small memory 306 | #pragma pack(1) 307 | class OpenThermID 308 | { 309 | public: 310 | byte /* OpenThermMessageID */ id; 311 | byte rw:2; //read 0x01, write 0x02 312 | byte ptype1:3; //parameter 1 type 313 | byte ptype2:3; //parameter 2 type 314 | byte used:2; //0 not used, 1 used, 2 not tested 315 | byte count:7; // 316 | byte countOk:7; // 317 | #if defined(ARDUINO_ARCH_ESP8266) 318 | //none 319 | #elif defined(ARDUINO_ARCH_ESP32) 320 | const char *descript; //description 321 | #endif 322 | }; 323 | #pragma pack() 324 | 325 | #ifndef ICACHE_RAM_ATTR 326 | #define ICACHE_RAM_ATTR 327 | #endif 328 | 329 | #ifndef IRAM_ATTR 330 | #define IRAM_ATTR ICACHE_RAM_ATTR 331 | #endif 332 | 333 | 334 | struct OpenThermVendor 335 | { int id; 336 | const char *name; 337 | }; 338 | 339 | const char * GetOTVendorName(int id); 340 | 341 | 342 | #endif // OpenTherm_h 343 | -------------------------------------------------------------------------------- /src/Pid.cpp: -------------------------------------------------------------------------------- 1 | /* Pid.cpp */ 2 | #include 3 | #include 4 | #include "Smart_Config.h" 5 | #if PID_USE 6 | #include "pid.hpp" 7 | 8 | void pid::Set_NewTag( float _Tag, float _x) 9 | { float dtag, _xerr, _xerrnew; 10 | 11 | // Serial.printf("**** Set_NewTag: xTag = %f I = %f I*Ki=%f\n", xTag, InT, InT * Ki ); 12 | _xerr = xTag - _x; 13 | _xerrnew = _Tag - _x; 14 | dtag = _Tag - xTag; 15 | xTag = _Tag; 16 | if(fabs(dtag) > 0.5) 17 | { //Init_I(_x); 18 | Init_I(dtag, _xerrnew); 19 | 20 | // Serial.printf("**** Set_NewTag: dtag = %f xerr =%f xerrnew =%f\n", dtag, _xerr, _xerrnew); 21 | // Serial.printf("**** Set_NewTag: xTag = %f I = %f I*Ki=%f\n", xTag, InT, InT * Ki ); 22 | 23 | dSt.n = dSt.ind = 0; 24 | // dSt.nlast = dSt.ind_last = 0; 25 | } 26 | } 27 | 28 | /* 29 | U = Kp*(Xtag-X) + Ki*I 30 | Xtag ->Xtagnew = Xtag + _dtag, I -> Inew = I + di 31 | di = coeff * _dtag 32 | 33 | */ 34 | void pid::Init_I(float _dtag, float _xernew) 35 | { float di, I1; 36 | if(Ki == 0.) 37 | return; 38 | di = _dtag * 2.f/Ki; 39 | // if((_xernew > 0.f && InT < 0.f) || (_xernew < 0.f && InT > 0.f)) 40 | // InT = 0.f; 41 | I1 = InT + di; 42 | if(I1 * Ki > 30.f ) 43 | I1 = 30.f/Ki; 44 | else 45 | if(I1 * Ki < -30.f ) 46 | I1 = -30.f/Ki; 47 | InT = I1; 48 | // Serial.printf("**** Init_I2 InT %f InT * Ki %f\n",InT, InT * Ki ); 49 | } 50 | 51 | void pid::Init_I(float _x) 52 | { float _xerr, I0, I1; 53 | if(Ki == 0.) 54 | return; 55 | //Limit for InT with constant xerr: xerr * dt/Kidiss 56 | _xerr = xTag - _x; 57 | I0 = _xerr * (t_interval) /Kidiss * 0.5; 58 | I1 = I0 * Ki; 59 | if(I1 > 30.f) 60 | { I0 = 30.f / Ki; 61 | } else if(I1 < -30.f) { 62 | I0 = -30.f / Ki; 63 | } 64 | InT = I0; 65 | 66 | // Serial.printf("**** Init_I1 InT %f InT * Ki %f\n",InT, InT * Ki ); 67 | 68 | // Serial.printf("**** Init_I _x =%f, _xerr %f InT %f InT * Ki %f\n", _x, _xerr, InT, InT * Ki ); 69 | 70 | } 71 | 72 | int pid::Pid(float _x, float _u0) 73 | { unsigned long int t, dt, _t; 74 | float _xerr, dX, dtf, _dft, _u; 75 | float _dft0, _Kidiss; 76 | t = millis(); 77 | dt = t - pid_t; // dt, msec 78 | 79 | // Serial.printf("****pid: dt = %ld\n", dt ); 80 | 81 | //P 82 | x = _x; 83 | xerr = xTag - x; //grad 84 | 85 | //D 86 | 87 | // calcD() - derivative calculation, return _dft 88 | // _dft dimension is grad/msec 89 | dSt.calcD(xerr, t, _dft); 90 | // Serial.printf("====>> _dft0=%e _dft=%e diff=%e\n", _dft0, _dft, _dft0 - _dft); 91 | { 92 | dX = _dft * 3600.f* 1000.f; //grad/hour 93 | // Serial.printf("====>> dX=%f\n", dX) ; 94 | } 95 | 96 | dSt.add(xerr, t); 97 | 98 | //Kidiss magic: dissipation of the integral automagically limit of integral & limiting the influence of old values 99 | //characteristic time: t_interval/Kidiss (sec) 100 | //Limit for InT with constant xerr: InTlim = xerr * t_interval/Kidiss 101 | 102 | _Kidiss = Kidiss; 103 | if(fabs(xerr) < 1.f) //Kidiss magic, part 2: 104 | { _Kidiss = Kidiss* fabs(xerr); //Limit to zero dissipation of the integral with small xerr 105 | } 106 | dtf = float(dt) / 1000.f; // dt, sec 107 | _Kidiss = _Kidiss * dtf / float(t_interval); 108 | if(_Kidiss > 0.5) _Kidiss = 0.5; 109 | 110 | InT = InT * (1.f - _Kidiss) + xerr * dtf; // grad * sec 111 | #if SERIAL_DEBUG 112 | // Serial.printf("pid: dt %d xerr=%f, InT=%f dX=%f\n", 113 | // dt , xerr, InT, dX); 114 | // Serial.printf("pid: _x %f xTag=%f, u0=%f\n", 115 | // _x , xTag, _u0); 116 | #endif 117 | dP = xerr * Kp; //dP - grad, Kp - dimensionless 118 | dD = dX * Kd; //dD - grad, Kd - hour 119 | dI = InT * Ki; //dI - grad, Ki - (1/sec) 120 | _u = dP + dD + dI; 121 | if(dSt.n <= 4) 122 | u = _u + _u0; 123 | else 124 | u = (u + _u + _u0) * 0.5; //filter output of pid 125 | 126 | #if SERIAL_DEBUG 127 | // Serial.printf("pid: U= %f u0 = %f _u = %f dP=%f, dD=%f dI=%f\n", 128 | // u, _u0, _u , dP, dD, dI); 129 | #endif 130 | ub = _u0; 131 | 132 | NextTact(); 133 | 134 | return 1; 135 | } 136 | 137 | #define N_X 3 138 | 139 | int IncrCalculateMatrixYfX2(float x, float y, int *Np); 140 | int CalculateMNKYfX2(float coeff[],int *Np); 141 | 142 | int dstack::calcD(float xerr, unsigned long int tt, float &diff) 143 | { int i, ii; 144 | unsigned long int t0, dt, tmid, _t; 145 | float _d, dmid, _xerr, xm, ym, xm2,xym, _x, _y, b; 146 | float _dft, dX; 147 | int Np; 148 | float coeff[N_X]; 149 | 150 | //https://www.freecodecamp.org/news/the-least-squares-regression-method-explained/ 151 | // float d[NB]; 152 | // unsigned long int t[NB]; 153 | 154 | // Serial.printf("dstack::calcD n =%i ind =%d xerr=%f tt=%ld\n",n, ind, xerr, tt ) ; 155 | 156 | // if( n < 2) 157 | if( n < 4) 158 | { diff = 0.f; 159 | return 0; 160 | } 161 | 162 | // t0 = t[ind]; 163 | // get(_xerr, _t); 164 | // Serial.printf("----- _xerr =%f _t = %d t0 %d-------\n", _xerr, _t, t0) ; 165 | // Serial.printf("----- xerr =%f tt = %ld -------\n", xerr, tt) ; 166 | // _dft = float(tt - _t) / 1000.f; // dt, sec 167 | // dX = (xerr - _xerr) /_dft * 3600; //grad/hour 168 | // Serial.printf("----- dX(*) =%e dX =%f dt = %d-------\n", (xerr - _xerr)/float(tt - _t) , dX, tt - _t) ; 169 | 170 | 171 | dmid = 0.f; 172 | tmid = 0; 173 | 174 | for (ii = 0; ii 5 | #include 6 | using WiFiWebServer = ESP8266WebServer; 7 | #define FORMAT_ON_FAIL 8 | #elif defined(ARDUINO_ARCH_ESP32) 9 | #include 10 | #include 11 | using WiFiWebServer = WebServer; 12 | #define FORMAT_ON_FAIL true 13 | #endif 14 | 15 | #include 16 | #include "OpenTherm.h" 17 | #include "Smart_Config.h" 18 | #include "SmartDevice.hpp" 19 | #include "SD_OpenTherm.hpp" 20 | 21 | #if MQTT_USE 22 | 23 | 24 | #include 25 | #include 26 | 27 | void mqtt_start(void); 28 | void mqtt_loop(void); 29 | void mqtt_setup(void); 30 | int MQTT_pub_data(void); 31 | void MQTT_pub_Eff_Mod_h(void); 32 | #if RELAY_USE 33 | void MQTT_pub_relay(void); 34 | #endif 35 | 36 | extern WiFiClient tcp_client; 37 | 38 | WiFiClient espClient; 39 | PubSubClient client(espClient); 40 | extern SD_Termo SmOT; 41 | extern OpenTherm ot; 42 | 43 | /*******************************************************************************/ 44 | //HADevice *pHAdevice; 45 | //HAMqtt *pMqtt; 46 | 47 | HADevice device; 48 | #if RELAY_USE 49 | #if PID_USE 50 | HAMqtt mqtt(espClient, device,28); 51 | #else 52 | HAMqtt mqtt(espClient, device,13); 53 | #endif 54 | #else 55 | #if PID_USE 56 | HAMqtt mqtt(espClient, device,27); 57 | #else 58 | HAMqtt mqtt(espClient, device,12); 59 | #endif 60 | #endif 61 | const char * temperature_str = "temperature"; 62 | 63 | HABinarySensor sensorOT(NULL); 64 | HABinarySensor sensorFlame(NULL); 65 | HABinarySensor sensor_CH(NULL); 66 | HABinarySensor sensor_HW(NULL); 67 | HABinarySensor sensor_CMD_on(NULL); 68 | HABinarySensor sensor_CMD_CH_on(NULL); 69 | #if RELAY_USE 70 | HASwitch relayHA(NULL); 71 | #endif 72 | HASensor sensorModulation(NULL); 73 | HASensor sensorBoilerT(NULL); 74 | HASensor sensorBoilerRetT(NULL); 75 | HASensor sensorPressure(NULL); 76 | HASensor sensorT1(NULL); 77 | HASensor sensorT2(NULL); 78 | HASensor sensorText(NULL); 79 | HASensor sensorFreeRam(NULL); 80 | //HASensor sensor_TestNum(NULL); 81 | 82 | HASensor sensorState(NULL); 83 | #if PID_USE 84 | //HAText textTargetTemp(NULL); 85 | //HAText textPIDinfo(NULL); 86 | //HANumber numPID_v(NULL,HANumber::PrecisionP3); 87 | HANumber numT_outdoor(NULL,HANumber::PrecisionP3); 88 | HANumber numT_indoor(NULL,HANumber::PrecisionP3); 89 | HASensor sensorPID_P(NULL,HANumber::PrecisionP3); 90 | HASensor sensorPID_D(NULL,HANumber::PrecisionP3); 91 | HASensor sensorPID_I(NULL,HANumber::PrecisionP3); 92 | HASensor sensorPID_U(NULL,HANumber::PrecisionP3); 93 | HASensor sensorPID_U0(NULL,HANumber::PrecisionP3); 94 | HASensor sensor_Eff_Mod(NULL,HANumber::PrecisionP3); 95 | 96 | #endif 97 | 98 | // By default HAHVAC supports only reporting of the temperature. 99 | // You can enable feature you need using the second argument of the constructor. 100 | // Please check the documentation of the HAHVAC class. 101 | HAHVAC hvac( 102 | NULL, 103 | HAHVAC::TargetTemperatureFeature | HAHVAC::PowerFeature | HAHVAC::ModesFeature |HAHVAC::ActionFeature, 104 | HANumber::PrecisionP2 105 | ); 106 | 107 | HAHVAC hvacDHW( 108 | NULL, 109 | HAHVAC::TargetTemperatureFeature |HAHVAC::ModesFeature |HAHVAC::ActionFeature, 110 | HANumber::PrecisionP2 111 | ); 112 | 113 | #if PID_USE 114 | HAHVAC hvacPID( 115 | NULL, 116 | HAHVAC::TargetTemperatureFeature | HAHVAC::ModesFeature | HAHVAC::ActionFeature, 117 | HANumber::PrecisionP3 118 | ); 119 | #endif 120 | unsigned long lastReadAt = millis(); 121 | unsigned long lastAvailabilityToggleAt = millis(); 122 | bool lastInputState = false; 123 | void OnMQTTconnected(void); 124 | void OnMQTTdisconnected(void); 125 | 126 | void onTargetTemperatureCommand(HANumeric temperature, HAHVAC* sender) { 127 | float temperatureFloat = temperature.toFloat(); 128 | if(sender == &hvacDHW) 129 | { 130 | SmOT.TdhwSet = temperatureFloat; 131 | SmOT.need_set_dhwT = 2; 132 | #if SERIAL_DEBUG 133 | Serial.print("DHW Target temperature: "); 134 | Serial.println(temperatureFloat); 135 | #endif 136 | sender->setTargetTemperature(temperature); // report target temperature back to the HA panel 137 | 138 | #if PID_USE 139 | } else if (sender == &hvacPID) { 140 | // Serial.print("PID Target temperature: "); 141 | // Serial.println(temperatureFloat); 142 | 143 | SmOT.set_new_PID_setpoint(temperatureFloat, 1); //change mypid.xTag 144 | SmOT.TroomTarget = temperatureFloat; 145 | Serial.printf("**** MQTT Set_NewTag: xTag = %f = %f\n", SmOT.TroomTarget, SmOT.mypid.xTag); 146 | 147 | //todo 148 | #endif 149 | } else { 150 | SmOT.Tset = temperatureFloat; 151 | SmOT.need_set_T = 2; 152 | sender->setTargetTemperature(temperature); // report target temperature back to the HA panel 153 | #if SERIAL_DEBUG 154 | Serial.print("CH Target temperature: "); 155 | Serial.println(temperatureFloat); 156 | #endif 157 | } 158 | } 159 | 160 | void onPowerCommand(bool state, HAHVAC* sender) { 161 | if (state) { 162 | Serial.println("Power on"); 163 | } else { 164 | Serial.println("Power off"); 165 | } 166 | } 167 | 168 | void onModeCommand(HAHVAC::Mode mode, HAHVAC* sender) { 169 | //PID_USE todo 170 | Serial.print("Mode: "); 171 | if (mode == HAHVAC::OffMode) { 172 | Serial.println(F("off")); 173 | SmOT.enable_CentralHeating = false; 174 | } else if (mode == HAHVAC::HeatMode) { 175 | Serial.println("heat"); 176 | SmOT.enable_CentralHeating = true; 177 | 178 | #if 0 179 | } else if (mode == HAHVAC::AutoMode) { 180 | Serial.println("auto"); 181 | } else if (mode == HAHVAC::CoolMode) { 182 | Serial.println("cool"); 183 | } else if (mode == HAHVAC::DryMode) { 184 | Serial.println("dry"); 185 | } else if (mode == HAHVAC::FanOnlyMode) { 186 | Serial.println("fan only"); 187 | #endif 188 | } 189 | 190 | sender->setMode(mode); // report mode back to the HA panel 191 | } 192 | 193 | void onModeCommandPID(HAHVAC::Mode mode, HAHVAC* sender) { 194 | Serial.print("Mode: "); 195 | if (mode == HAHVAC::OffMode) { 196 | Serial.println(F("PID off")); 197 | SmOT.usePID = 0; 198 | } else if (mode == HAHVAC::AutoMode) { 199 | Serial.println("PID on"); 200 | SmOT.usePID = 1; 201 | } 202 | 203 | sender->setMode(mode); // report mode back to the HA panel 204 | } 205 | 206 | void onModeCommandDHW(HAHVAC::Mode mode, HAHVAC* sender) { 207 | Serial.print("Mode: "); 208 | if (mode == HAHVAC::OffMode) { 209 | Serial.println(F("DHW off")); 210 | SmOT.enable_HotWater = false; 211 | } else if (mode == HAHVAC::HeatMode) { 212 | Serial.println("DHW heat"); 213 | SmOT.enable_HotWater = true; 214 | } 215 | 216 | sender->setMode(mode); // report mode back to the HA panel 217 | Serial.printf("SmOT.enable_HotWater %d\n", SmOT.enable_HotWater); 218 | } 219 | 220 | #if PID_USE 221 | 222 | void onNumberCommand(HANumeric number, HANumber* sender) 223 | { float t = number.toFloat(); 224 | // if (sender == &numPID_v) { 225 | // Serial.printf("NumberCommand numPID_v: %f\n",t); 226 | // 227 | // } else 228 | 229 | if (sender == &numT_outdoor) { 230 | #if SERIAL_DEBUG 231 | Serial.printf("NumberCommand numT_outdoor: %f (%d)\n", t, millis()/1000); 232 | #endif 233 | SmOT.OnChangeT(t,4); 234 | 235 | } else if (sender == &numT_indoor) { 236 | #if SERIAL_DEBUG 237 | // Serial.printf("NumberCommand numT_indoor: %f (%d)\n", t, millis()/1000); 238 | #endif 239 | SmOT.OnChangeT(t,3); 240 | } 241 | /* 242 | if (!number.isSet()) { 243 | // the reset command was send by Home Assistant 244 | } else { 245 | // you can do whatever you want with the number as follows: 246 | int8_t numberInt8 = number.toInt8(); 247 | int16_t numberInt16 = number.toInt16(); 248 | int32_t numberInt32 = number.toInt32(); 249 | uint8_t numberUInt8 = number.toUInt8(); 250 | uint16_t numberUInt16 = number.toUInt16(); 251 | uint32_t numberUInt32 = number.toUInt32(); 252 | float numberFloat = number.toFloat(); 253 | } 254 | */ 255 | sender->setState(number); // report the selected option back to the HA panel 256 | } 257 | #endif 258 | 259 | #if RELAY_USE 260 | void onRelayCommand(bool state, HASwitch* sender) 261 | { if(sender == &relayHA) 262 | { 263 | if(state) 264 | SmOT.RelayOnOff(1); 265 | else 266 | SmOT.RelayOnOff(0); 267 | } 268 | } 269 | #endif 270 | 271 | 272 | int statemqtt = -1; 273 | int state_mqtt = -10000; 274 | int attempt_mqtt = 0; 275 | 276 | /************************************************************/ 277 | void mqtt_setup(void) 278 | { bool rc; 279 | char str[80]; 280 | extern unsigned int OTcount; 281 | 282 | if (WiFi.status() != WL_CONNECTED) 283 | return; 284 | 285 | if(SmOT.stsOT == 0) 286 | { if(SmOT.CapabilitiesDetected == 0) 287 | return; 288 | else 289 | SmOT.DetectCapabilities(); 290 | } else { 291 | if(OTcount < 30) 292 | return; 293 | } 294 | 295 | 296 | Serial.printf("SmOT.useMQTT = %d\n", SmOT.useMQTT); 297 | if(SmOT.useMQTT != 0x03) 298 | return; 299 | 300 | 301 | if( mqtt.getDevicesTypesNb_toreg() > mqtt.getDevicesTypesNb()) 302 | { 303 | Serial.printf("Error! Nb = %d, need be %d\n", mqtt.getDevicesTypesNb(), mqtt.getDevicesTypesNb_toreg() ); 304 | //look at 45 HAMqtt mqtt(espClient, device,27); 305 | return; 306 | } 307 | 308 | device.setUniqueIdStr(SmOT.MQTT_topic); 309 | 310 | device.setName(SmOT.MQTT_topic,SmOT.MQTT_devname); //должно быть static!! 311 | { static char str[40]; 312 | sprintf(str,"%d.%d.%d %s" , SmOT.Vers,SmOT.SubVers,SmOT.SubVers1, SmOT.BiosDate); 313 | device.setSoftwareVersion(str); //должно быть static!! 314 | device.setConfigurationUrl(SmOT.LocalUrl);// --//-- 315 | } 316 | device.enableSharedAvailability(); 317 | device.enableLastWill(); 318 | 319 | lastReadAt = millis(); 320 | lastAvailabilityToggleAt = millis(); 321 | sensorOT.setAvailability(false); 322 | sensorOT.setCurrentState(false); 323 | 324 | sensorOT.setNameUniqueIdStr(SmOT.MQTT_topic,"OpenTherm", "OT" ); 325 | sensorOT.setDeviceClass("connectivity"); 326 | 327 | sensorState.setAvailability(true); 328 | sensorState.setNameUniqueIdStr(SmOT.MQTT_topic,"Ошибки", "Err" ); 329 | sensorState.setIcon("mdi:alert-box"); 330 | sensorState.setValue(""); 331 | 332 | sensorFlame.setNameUniqueIdStr(SmOT.MQTT_topic,"Горелка", "Flame"); 333 | sensorFlame.setCurrentState(false); 334 | sensorFlame.setAvailability(false); 335 | sensorFlame.setIcon("mdi:fire"); 336 | // sensorFlame.setDeviceClass("None"); 337 | 338 | sensorModulation.setNameUniqueIdStr(SmOT.MQTT_topic,"Модуляция", "Modulation"); 339 | sensorModulation.setAvailability(false); 340 | sensorModulation.setIcon("mdi:fire"); 341 | sensorModulation.setDeviceClass("power_factor"); 342 | sensorModulation.setUnitOfMeasurement("%"); 343 | /**********/ 344 | sensor_CH.setNameUniqueIdStr(SmOT.MQTT_topic,"Отопление", "CH"); 345 | sensor_CH.setCurrentState(false); 346 | sensor_CH.setAvailability(false); 347 | sensor_CH.setIcon("mdi:heating-coil"); 348 | 349 | if(SmOT.HotWater_present || SmOT.stsOT == -1) 350 | { sensor_HW.setNameUniqueIdStr(SmOT.MQTT_topic,"Горячая вода", "HW"); 351 | sensor_HW.setCurrentState(false); 352 | sensor_HW.setAvailability(false); 353 | sensor_HW.setIcon("mdi:water-thermometer"); 354 | } 355 | 356 | sensor_CMD_on.setNameUniqueIdStr(SmOT.MQTT_topic,"Cmd", "cmd"); 357 | sensor_CMD_on.setCurrentState(false); 358 | sensor_CMD_on.setAvailability(false); 359 | sensor_CMD_on.setIcon("mdi:heating-coil"); 360 | 361 | sensor_CMD_CH_on.setNameUniqueIdStr(SmOT.MQTT_topic,"CmdCH", "cmdCH"); 362 | sensor_CMD_CH_on.setCurrentState(false); 363 | sensor_CMD_CH_on.setAvailability(false); 364 | sensor_CMD_CH_on.setIcon("mdi:heating-coil"); 365 | 366 | /**********/ 367 | 368 | sensorBoilerT.setNameUniqueIdStr(SmOT.MQTT_topic,"Температура теплоносителя", "BoilerT"); 369 | sensorBoilerT.setAvailability(false); 370 | sensorBoilerT.setDeviceClass(temperature_str); 371 | sensorBoilerT.setUnitOfMeasurement("°C"); 372 | #if RELAY_USE 373 | if(SmOT.Relay_present) 374 | { 375 | relayHA.setNameUniqueIdStr(SmOT.MQTT_topic,"Реле", "Relay"); 376 | relayHA.setAvailability(true); 377 | relayHA.setDeviceClass("switch"); 378 | relayHA.setState(SmOT.Relay_sts); 379 | relayHA.onCommand(onRelayCommand); 380 | 381 | } 382 | #endif//RELAY_USE 383 | 384 | if(SmOT.RetT_present) 385 | { sensorBoilerRetT.setNameUniqueIdStr(SmOT.MQTT_topic,"Температура обратки", "RetT"); 386 | sensorBoilerRetT.setAvailability(false); 387 | sensorBoilerRetT.setDeviceClass(temperature_str); 388 | sensorBoilerRetT.setUnitOfMeasurement("°C"); 389 | } 390 | 391 | if(SmOT.Pressure_present) 392 | { sensorPressure.setNameUniqueIdStr(SmOT.MQTT_topic,"Давление", "Pressure"); 393 | sensorPressure.setAvailability(false); 394 | sensorPressure.setDeviceClass("pressure"); 395 | } 396 | 397 | sensorFreeRam.setAvailability(true); 398 | sensorFreeRam.setNameUniqueIdStr(SmOT.MQTT_topic,"Free RAM", "FreeRAM"); 399 | sensorFreeRam.setDeviceClass("data_size"); 400 | sensorFreeRam.setUnitOfMeasurement("B"); 401 | /* 402 | sensor_TestNum.setAvailability(true); 403 | sensor_TestNum.setNameUniqueIdStr(SmOT.MQTT_topic,"Test N", "TestN"); 404 | sensor_TestNum.setDeviceClass("data_size"); 405 | sprintf(str,"0"); 406 | sensor_TestNum.setValue(str); 407 | */ 408 | // assign callbacks (optional) 409 | hvac.onTargetTemperatureCommand(onTargetTemperatureCommand); 410 | hvac.onPowerCommand(onPowerCommand); 411 | hvac.onModeCommand(onModeCommand); 412 | 413 | // configure HVAC (optional) 414 | hvac.setNameUniqueIdStr(SmOT.MQTT_topic,"Котёл", "Boiler"); 415 | 416 | hvac.setMinTemp(SmOT.umin); 417 | hvac.setMaxTemp(SmOT.umax); 418 | hvac.setTempStep(1.); 419 | hvac.setModes(HAHVAC::OffMode|HAHVAC::HeatMode); 420 | #if PID_USE 421 | if(SmOT.enable_CentralHeating_real) 422 | #else 423 | if(SmOT.enable_CentralHeating) 424 | #endif 425 | hvac.setMode(HAHVAC::HeatMode); 426 | else 427 | hvac.setMode(HAHVAC::OffMode); 428 | 429 | hvac.setAvailability(false); 430 | 431 | 432 | if(SmOT.HotWater_present) 433 | { 434 | hvacDHW.onTargetTemperatureCommand(onTargetTemperatureCommand); 435 | hvacDHW.onModeCommand(onModeCommandDHW); 436 | if(SmOT.Use_ID29_DHW_flag && ot.OTid_used(OpenThermMessageID::Tstorage)) 437 | hvacDHW.setNameUniqueIdStr(SmOT.MQTT_topic,"Бойлер", "DHW"); 438 | else 439 | hvacDHW.setNameUniqueIdStr(SmOT.MQTT_topic,"Горячая вода", "DHW"); 440 | hvacDHW.setMinTemp(30); 441 | hvacDHW.setMaxTemp(80); 442 | hvacDHW.setTempStep(1.); 443 | 444 | hvacDHW.setModes(HAHVAC::OffMode|HAHVAC::HeatMode); 445 | 446 | if(SmOT.enable_HotWater) 447 | hvacDHW.setMode(HAHVAC::HeatMode); 448 | else 449 | hvacDHW.setMode(HAHVAC::OffMode); 450 | hvacDHW.setAvailability(false); 451 | } 452 | /*********************************/ 453 | #if PID_USE 454 | hvacPID.onTargetTemperatureCommand(onTargetTemperatureCommand); 455 | hvacPID.setNameUniqueIdStr(SmOT.MQTT_topic,"ПИД", "PID"); 456 | if(SmOT.usePID == 0) 457 | { hvacPID.setMinTemp(MIN_ROOM_TEMP); 458 | hvacPID.setMaxTemp(MAX_ROOM_TEMP); 459 | hvacPID.setAvailability(false); 460 | } else if(SmOT.usePID == 1) { 461 | hvacPID.setMinTemp(MIN_ROOM_TEMP); 462 | hvacPID.setMaxTemp(MAX_ROOM_TEMP); 463 | } else { 464 | hvacPID.setMinTemp(5); 465 | hvacPID.setMaxTemp(80); 466 | } 467 | hvacPID.setTempStep(0.1); 468 | hvacPID.setModes(HAHVAC::OffMode|HAHVAC::AutoMode); 469 | if(SmOT.usePID) 470 | hvacPID.setMode(HAHVAC::AutoMode); 471 | else 472 | hvacPID.setMode(HAHVAC::OffMode); 473 | 474 | hvacPID.onModeCommand(onModeCommandPID); 475 | 476 | //todo 477 | #endif 478 | /*********************************/ 479 | if(SmOT.stsT1 >= 0 ) 480 | { sensorT1.setAvailability(true); 481 | sensorT1.setNameUniqueIdStr(SmOT.MQTT_topic,"T1", "T1"); 482 | sensorT1.setDeviceClass(temperature_str); 483 | sensorT1.setUnitOfMeasurement("°C"); 484 | sprintf(str,"%.3f", SmOT.t1); 485 | sensorT1.setValue(str); 486 | // Serial.printf("***000 MQTT T1=%s\n", str); 487 | 488 | } else { 489 | sensorT1.setAvailability(false); 490 | } 491 | 492 | if(SmOT.stsT2 >= 0 ) 493 | { sensorT2.setAvailability(true); 494 | sensorT2.setNameUniqueIdStr(SmOT.MQTT_topic,"T2", "T2"); 495 | sensorT2.setDeviceClass(temperature_str); 496 | sensorT2.setUnitOfMeasurement("°C"); 497 | sprintf(str,"%.3f", SmOT.t2); 498 | sensorT2.setValue(str); 499 | } else { 500 | sensorT2.setAvailability(false); 501 | } 502 | 503 | if(SmOT.Toutside_present) 504 | { sensorText.setAvailability(false); 505 | sensorText.setNameUniqueIdStr(SmOT.MQTT_topic,"Tвн", "Toutside"); 506 | sensorText.setDeviceClass(temperature_str); 507 | sensorText.setUnitOfMeasurement("°C"); 508 | } 509 | 510 | #if PID_USE 511 | /* 512 | textTargetTemp.setAvailability(true); 513 | textTargetTemp.setNameUniqueIdStr(SmOT.MQTT_topic,"Target Temp", "TargetTemp"); 514 | textTargetTemp.setValue("22"); 515 | 516 | textPIDinfo.setAvailability(true); 517 | textPIDinfo.setNameUniqueIdStr(SmOT.MQTT_topic,"PID info", "PIDinfo"); 518 | textPIDinfo.setValue("BlaBlaBla"); 519 | 520 | numPID_v.setAvailability(true); 521 | numPID_v.setNameUniqueIdStr(SmOT.MQTT_topic,"PID numv", "PIDnumv"); 522 | numPID_v.setMode(HANumber::ModeBox); 523 | numPID_v.setState(23.f, true); 524 | numPID_v.setCurrentState(22.f); 525 | numPID_v.setStep(0.1); 526 | numPID_v.setMin(-50.); 527 | numPID_v.setMax( 100.); 528 | numPID_v.onCommand(onNumberCommand); 529 | */ 530 | numT_outdoor.setAvailability(true); 531 | numT_outdoor.setNameUniqueIdStr(SmOT.MQTT_topic,"T outdoor", "Toutdoor"); 532 | numT_outdoor.setMode(HANumber::ModeBox); 533 | numT_outdoor.setState(10.f, false); 534 | // numT_outdoor.setCurrentState(10.f); 535 | numT_outdoor.setStep(0.1); 536 | numT_outdoor.setMin(-50.); 537 | numT_outdoor.setMax( 50.); 538 | numT_outdoor.onCommand(onNumberCommand); 539 | 540 | numT_indoor.setAvailability(true); 541 | numT_indoor.setNameUniqueIdStr(SmOT.MQTT_topic,"T indoor", "Tindoor"); 542 | numT_indoor.setMode(HANumber::ModeBox); 543 | numT_indoor.setState(10.f, true); 544 | numT_indoor.setStep(0.1); 545 | numT_indoor.setMin(-50.); 546 | numT_indoor.setMax( 50.); 547 | numT_indoor.onCommand(onNumberCommand); 548 | 549 | 550 | sensorPID_P.setAvailability(true); 551 | sensorPID_P.setNameUniqueIdStr(SmOT.MQTT_topic,"dP", "pid_dp"); 552 | sensorPID_P.setDeviceClass(temperature_str); 553 | sensorPID_D.setAvailability(true); 554 | sensorPID_D.setNameUniqueIdStr(SmOT.MQTT_topic,"dD", "pid_dd"); 555 | sensorPID_D.setDeviceClass(temperature_str); 556 | sensorPID_I.setAvailability(true); 557 | sensorPID_I.setNameUniqueIdStr(SmOT.MQTT_topic,"dI", "pid_di"); 558 | sensorPID_I.setDeviceClass(temperature_str); 559 | sensorPID_U.setAvailability(true); 560 | sensorPID_U.setNameUniqueIdStr(SmOT.MQTT_topic,"U", "pid_u"); 561 | sensorPID_U.setDeviceClass(temperature_str); 562 | sensorPID_U0.setAvailability(true); 563 | sensorPID_U0.setNameUniqueIdStr(SmOT.MQTT_topic,"U0", "pid_u0"); 564 | sensorPID_U0.setDeviceClass(temperature_str); 565 | sprintf(str,"%.4f", SmOT.mypid.ub); 566 | sensorPID_U0.setValue(str); 567 | // Serial.printf("sensorPID_U0 =%s\n", str); 568 | 569 | 570 | sensor_Eff_Mod.setAvailability(true); 571 | sensor_Eff_Mod.setNameUniqueIdStr(SmOT.MQTT_topic,"EffModH", "effmod_h"); 572 | sensor_Eff_Mod.setIcon("mdi:fire"); 573 | sensor_Eff_Mod.setDeviceClass("power_factor"); 574 | sensor_Eff_Mod.setUnitOfMeasurement("%"); 575 | 576 | //SmOT.Bstat.Eff_Mod_h_prev 577 | 578 | #endif 579 | mqtt.onConnected(OnMQTTconnected); 580 | mqtt.onDisconnected(OnMQTTdisconnected); 581 | SmOT.stsMQTT = 1; 582 | mqtt._mqtt->setSocketTimeout(1); //not work ??? 583 | 584 | 585 | // rc= mqtt.begin(SmOT.MQTT_server, SmOT.MQTT_user, SmOT.MQTT_pwd); 586 | rc= mqtt.begin(SmOT.MQTT_server, SmOT.MQTT_port, SmOT.MQTT_user, SmOT.MQTT_pwd); 587 | if(rc == true) 588 | { Serial.printf("mqtt.begin ok %s:%d %s %s\n", SmOT.MQTT_server, SmOT.MQTT_port, SmOT.MQTT_user, SmOT.MQTT_pwd); 589 | SmOT.stsMQTT = 2; 590 | } else { 591 | Serial.printf("mqtt.begin false\n"); 592 | } 593 | } 594 | 595 | 596 | void OnMQTTconnected(void) 597 | { 598 | statemqtt = 1; 599 | // Serial.printf("OnMQTTconnected %d\n", statemqtt ); 600 | 601 | } 602 | void OnMQTTdisconnected(void) 603 | { statemqtt = 0; 604 | // Serial.printf("OnMQTT disconnected %d\n", statemqtt ); 605 | } 606 | 607 | void mqtt_start(void) 608 | { 609 | Serial.printf("mqtt_start SmOT.stsMQTT %d\n", SmOT.stsMQTT); 610 | if(SmOT.stsMQTT == 0) 611 | { mqtt_setup(); 612 | } else { 613 | int rc; 614 | rc= mqtt.begin(SmOT.MQTT_server,SmOT.MQTT_user, SmOT.MQTT_pwd); 615 | if(rc == true) 616 | { Serial.printf("(1) mqtt.begin ok %s %s %s\n", SmOT.MQTT_server,SmOT.MQTT_user, SmOT.MQTT_pwd); 617 | SmOT.stsMQTT = 2; 618 | } else { 619 | Serial.printf("(1)mqtt.begin false\n"); 620 | } 621 | } 622 | } 623 | 624 | void mqtt_loop(void) 625 | { char str[80]; 626 | static int st_old = -2; 627 | unsigned long t1; 628 | 629 | 630 | if(SmOT.stsMQTT == 0) 631 | { mqtt_setup(); 632 | return; 633 | } 634 | 635 | mqtt.loop(); 636 | 637 | if(mqtt.isConnected()) 638 | { if(statemqtt != 1) 639 | Serial.println(F("MQTT connected")); 640 | statemqtt = 1; 641 | state_mqtt = mqtt._mqtt->state(); 642 | } else { 643 | if(statemqtt != 0) 644 | Serial.println(F("MQTT DiSconnected")); 645 | statemqtt = 0; 646 | state_mqtt = mqtt._mqtt->state(); 647 | delay(1); 648 | return; // return from mqtt_loop() if not connected 649 | } 650 | 651 | if ((millis() - lastAvailabilityToggleAt) > SmOT.MQTT_interval*1000 || SmOT.MQTT_need_report) 652 | { 653 | if(SmOT.stsOT == -1) 654 | { sensorOT.setAvailability(false); 655 | sensorState.setValue("OpenTherm не подключен"); 656 | } else { 657 | sensorOT.setAvailability(true); 658 | if(SmOT.stsOT == 2) 659 | { 660 | sensorOT.setState(false); 661 | hvac.setAvailability(false); 662 | sensorBoilerT.setAvailability(false); 663 | sensorFlame.setAvailability(false); 664 | sensor_CH.setAvailability(false); 665 | if(SmOT.HotWater_present) 666 | { sensor_HW.setAvailability(false); 667 | hvacDHW.setAvailability(false); 668 | } 669 | sensor_CMD_on.setAvailability(false); 670 | sensor_CMD_CH_on.setAvailability(false); 671 | 672 | sensorModulation.setAvailability(false); 673 | if(SmOT.RetT_present) 674 | sensorBoilerRetT.setAvailability(false); 675 | if(SmOT.Pressure_present) 676 | sensorPressure.setAvailability(false); 677 | if(SmOT.Toutside_present) 678 | sensorText.setAvailability(false); 679 | sensorState.setValue("OpenTherm: потеря связи"); 680 | #if PID_USE 681 | hvacPID.setAvailability(false); 682 | #endif 683 | 684 | } else { 685 | if(st_old != SmOT.stsOT) 686 | { 687 | sensorOT.setState(true); 688 | sensorBoilerT.setAvailability(true); 689 | hvac.setAvailability(true); 690 | //Serial.printf("hvac.setAvailability(true)\n"); 691 | sensorFlame.setAvailability(true); 692 | sensor_CH.setAvailability(true); 693 | if(SmOT.HotWater_present) 694 | { sensor_HW.setAvailability(true); 695 | hvacDHW.setAvailability(true); 696 | } 697 | #if PID_USE 698 | if(SmOT.usePID > 0) 699 | hvacPID.setAvailability(true); 700 | #endif 701 | 702 | sensor_CMD_on.setAvailability(true); 703 | sensor_CMD_CH_on.setAvailability(true); 704 | 705 | sensorModulation.setAvailability(true); 706 | if(SmOT.RetT_present) 707 | sensorBoilerRetT.setAvailability(true); 708 | if(SmOT.Pressure_present) 709 | sensorPressure.setAvailability(true); 710 | if(SmOT.Toutside_present) 711 | sensorText.setAvailability(true); 712 | } 713 | sprintf(str,"%.3f", SmOT.BoilerT); 714 | sensorBoilerT.setValue(str); 715 | hvac.setCurrentTemperature(SmOT.BoilerT); 716 | hvac.setTargetTemperature(SmOT.Tset); 717 | #if PID_USE 718 | if(SmOT.enable_CentralHeating_real) 719 | #else 720 | if(SmOT.enable_CentralHeating) 721 | #endif 722 | hvac.setMode(HAHVAC::HeatMode); 723 | else 724 | hvac.setMode(HAHVAC::OffMode); 725 | 726 | #if PID_USE 727 | if(SmOT.IsSetTemp & 0x01) 728 | hvacPID.setCurrentTemperature(SmOT.tempindoor); 729 | hvacPID.setTargetTemperature(SmOT.TroomTarget); 730 | 731 | #endif 732 | 733 | if(SmOT.BoilerStatus & 0x08) 734 | sensorFlame.setState(true); 735 | else 736 | sensorFlame.setState(false); 737 | 738 | if(SmOT.BoilerStatus & 0x02) 739 | sensor_CH.setState(true); 740 | else 741 | sensor_CH.setState(false); 742 | 743 | if(SmOT.HotWater_present) 744 | { 745 | if(SmOT.BoilerStatus & 0x04) 746 | { sensor_HW.setState(true); 747 | } else { 748 | sensor_HW.setState(false); 749 | } 750 | if(SmOT.enable_HotWater) 751 | hvacDHW.setMode(HAHVAC::HeatMode); 752 | else 753 | hvacDHW.setMode(HAHVAC::OffMode); 754 | 755 | if(SmOT.Use_ID29_DHW_flag && ot.OTid_used(OpenThermMessageID::Tstorage)) 756 | hvacDHW.setCurrentTemperature(SmOT.Tstorage); 757 | else if(SmOT.Dhw_t_present) 758 | hvacDHW.setCurrentTemperature(SmOT.dhw_t); 759 | 760 | hvacDHW.setTargetTemperature(SmOT.TdhwSet); 761 | // Serial.printf("SmOT.TdhwSet %f SmOT.dhw_t %f\n", SmOT.TdhwSet, SmOT.dhw_t ); 762 | 763 | } 764 | 765 | sprintf(str,"%.3f", SmOT.FlameModulation); 766 | sensorModulation.setValue(str); 767 | if(SmOT.RetT_present) 768 | { sprintf(str,"%.3f", SmOT.RetT); 769 | sensorBoilerRetT.setValue(str); 770 | } 771 | if(SmOT.Pressure_present) 772 | { sprintf(str,"%.3f", SmOT.Pressure); 773 | sensorPressure.setValue(str); 774 | } 775 | if(SmOT.Toutside_present) 776 | { sprintf(str,"%.3f", SmOT.Toutside); 777 | sensorText.setValue(str); 778 | } 779 | 780 | #if PID_USE 781 | { 782 | sprintf(str,"%.4f", SmOT.mypid.dP); 783 | sensorPID_P.setValue(str); 784 | sprintf(str,"%.4f", SmOT.mypid.dD); 785 | sensorPID_D.setValue(str); 786 | sprintf(str,"%.4f", SmOT.mypid.dI); 787 | sensorPID_I.setValue(str); 788 | sprintf(str,"%.4f", SmOT.mypid.u); 789 | sensorPID_U.setValue(str); 790 | sprintf(str,"%.4f", SmOT.mypid.ub); 791 | sensorPID_U0.setValue(str); 792 | 793 | // Serial.printf("srcText %d srcTroom %d\n",SmOT.srcText, SmOT.srcTroom ); 794 | 795 | if((SmOT.srcTroom >= 0 && SmOT.srcTroom < 3) && (SmOT.IsSetTemp & 0x01)) 796 | { numT_indoor.setState(SmOT.tempindoor, true); 797 | } 798 | if((SmOT.srcText >= 0 && SmOT.srcText < 3) && (SmOT.IsSetTemp & 0x02)) 799 | { numT_outdoor.setState(SmOT.tempoutdoor, true); 800 | } 801 | 802 | // sprintf(str,"isset %d nx %d xmean %.3f x %.3f", SmOT.t_mean[4].isset, SmOT.t_mean[4].nx, SmOT.t_mean[4].xmean, SmOT.t_mean[4].x); 803 | // textPIDinfo.setValue(str); 804 | 805 | } 806 | 807 | #endif 808 | 809 | /*************************************************/ 810 | if(SmOT.OEMDcode || SmOT.Fault) 811 | { 812 | if(SmOT.Fault) 813 | { if (SmOT.OEMDcode) 814 | { 815 | sprintf(str, "OT Fault %x OEMDcode %x", SmOT.Fault, SmOT.OEMDcode); 816 | } else { 817 | sprintf(str, "OT Fault %x", SmOT.Fault); 818 | } 819 | } else if (SmOT.OEMDcode) { 820 | sprintf(str, "OEMDcode %x", SmOT.OEMDcode); 821 | } 822 | sensorState.setValue(str); 823 | } else { 824 | sensorState.setValue("нет"); 825 | } 826 | 827 | #if 0 828 | todo 829 | if(SmOT.Fault) 830 | { sprintf(str0, "Fault = %x (HB) %x (LB)
", (SmOT.Fault>>8)&0xff, (SmOT.Fault&0xff)); 831 | Info6.value += str0; 832 | if(SmOT.Fault & 0xff00) 833 | { if(SmOT.Fault & 0x0100) 834 | Info6.value += " Service request"; 835 | if(SmOT.Fault & 0x0200) 836 | Info6.value += " Lockout-reset"; 837 | if(SmOT.Fault & 0x0400) 838 | Info6.value += " Lowwater press"; 839 | if(SmOT.Fault & 0x0800) 840 | Info6.value += " Gas/flame fault"; 841 | if(SmOT.Fault & 0x01000) 842 | Info6.value += " Air press fault"; 843 | if(SmOT.Fault & 0x02000) 844 | Info6.value += " Water over-temp fault"; 845 | } 846 | if(SmOT.Fault & 0x00ff) 847 | { sprintf(str0, " OEM-specific fault/error cod = %d ( hex %x)", (SmOT.Fault&0xff), (SmOT.Fault&0xff)); 848 | Info6.value += str0; 849 | } 850 | Info6.value += "
"; 851 | } 852 | if(SmOT.OEMDcode) 853 | { sprintf(str0, "OEM-specific diagnostic/service code = %d ( hex %x)
", SmOT.OEMDcode, SmOT.OEMDcode); 854 | Info6.value += str0; 855 | } 856 | #endif //0 857 | /*******************************************/ 858 | } 859 | } 860 | st_old = SmOT.stsOT; 861 | if(SmOT.stsT1 >= 0) 862 | { 863 | #if PID_USE 864 | if(SmOT.usePID) 865 | { if(SmOT.t_mean[0].can_report) 866 | { sprintf(str,"%.3f", SmOT.t_mean[0].x); 867 | sensorT1.setValue(str); 868 | SmOT.t_mean[0].can_report = 0; 869 | // Serial.printf("***MQTT T1=%s\n", str); 870 | } 871 | } else { 872 | sprintf(str,"%.3f", SmOT.t1); 873 | sensorT1.setValue(str); 874 | } 875 | #else 876 | sprintf(str,"%.3f", SmOT.t1); 877 | sensorT1.setValue(str); 878 | #endif 879 | } 880 | if(SmOT.stsT2 >= 0) 881 | { 882 | #if PID_USE 883 | if(SmOT.usePID) 884 | { if(SmOT.t_mean[1].can_report) 885 | { sprintf(str,"%.3f", SmOT.t_mean[1].x); 886 | sensorT2.setValue(str); 887 | SmOT.t_mean[1].can_report = 0; 888 | } 889 | } else { 890 | sprintf(str,"%.3f", SmOT.t2); 891 | sensorT2.setValue(str); 892 | } 893 | #else 894 | sprintf(str,"%.3f", SmOT.t2); 895 | sensorT2.setValue(str); 896 | #endif 897 | 898 | } 899 | 900 | { static int raz = 0; 901 | if(raz++ == 0) 902 | { sprintf(str,"%d", ESP.getFreeHeap() ); 903 | sensorFreeRam.setValue(str); 904 | } else { 905 | if(raz == 100) 906 | raz = 0; 907 | } 908 | } 909 | 910 | lastAvailabilityToggleAt = millis(); 911 | SmOT.MQTT_need_report = 0; 912 | } 913 | 914 | } 915 | 916 | void MQTT_pub_Eff_Mod_h(void) 917 | { char str[80]; 918 | if(SmOT.stsMQTT != 2) 919 | return; 920 | sprintf(str,"%.4f", SmOT.Bstat.Eff_Mod_h_prev); 921 | sensor_Eff_Mod.setValue(str); 922 | } 923 | 924 | int MQTT_pub_data(void) 925 | { 926 | //Serial.printf("todo %s\n",__FUNCTION__ ); 927 | return 0; 928 | 929 | } 930 | 931 | #if RELAY_USE 932 | void MQTT_pub_relay(void) 933 | { 934 | if(SmOT.stsMQTT != 2) 935 | return; 936 | relayHA.setState(SmOT.Relay_sts); 937 | } 938 | #endif 939 | 940 | void MQTT_pub_cmd2(int val) 941 | { char str[80]; 942 | if(SmOT.stsMQTT != 2) 943 | return; 944 | // sprintf(str,"%d", val); 945 | // sensor_TestNum.setValue(str); 946 | } 947 | 948 | void MQTT_pub_cmd(int on) 949 | { 950 | if(SmOT.stsMQTT == 2) 951 | { if(on) 952 | sensor_CMD_on.setState(true); 953 | else 954 | sensor_CMD_on.setState(false); 955 | } 956 | } 957 | 958 | int MQTT_pub_cmdCH(int on) 959 | { 960 | if(SmOT.stsMQTT == 2) 961 | { if(on) 962 | sensor_CMD_CH_on.setState(true); 963 | else 964 | sensor_CMD_CH_on.setState(false); 965 | return 1; 966 | } else { 967 | return 0; 968 | } 969 | } 970 | 971 | int MQTT_pub_usePID(void) 972 | { 973 | #if PID_USE 974 | if(SmOT.stsMQTT == 2) 975 | { 976 | if(SmOT.usePID) 977 | hvacPID.setMode(HAHVAC::AutoMode); 978 | else 979 | hvacPID.setMode(HAHVAC::OffMode); 980 | } 981 | #endif 982 | return 0; 983 | } 984 | /*******************************************************************************/ 985 | 986 | 987 | #endif //MQTT_USE -------------------------------------------------------------------------------- /src/SD_OpenTherm.hpp: -------------------------------------------------------------------------------- 1 | /* SD_OpenTherm.hpp */ 2 | #ifndef SD_OPENTHERM 3 | #define SD_OPENTHERM 4 | 5 | #include "SmartDevice.hpp" 6 | #include "pid.hpp" 7 | #include "mybuffer.hpp" 8 | 9 | class x_mean 10 | { 11 | public: 12 | float x; 13 | float xmean; //среднее для вычисления x 14 | float x0, xold; 15 | int nx; // число отсчетов xmean 16 | int isset; 17 | int canfilter; 18 | int can_report; 19 | x_mean(void) 20 | { x = xold = 0.f; 21 | init(0); 22 | isset = -1; 23 | canfilter = 0; 24 | can_report = 0; 25 | } 26 | 27 | void init(int canf) 28 | { if(canf) 29 | { xold = x; 30 | canfilter = 1; 31 | } else { 32 | canfilter = 0; 33 | } 34 | xmean = 0; 35 | isset = 0; 36 | nx = 0; 37 | can_report = 1; 38 | } 39 | void add(float _x) 40 | { xmean += _x; 41 | nx++; 42 | //Serial.printf("[%d] _x =%f nx= %d\n",id, _x, nx); 43 | } 44 | float get(void) 45 | { if(nx > 0) 46 | { x0 = xmean/float(nx); 47 | if(isset != 1) 48 | { x = x0; 49 | isset = 1; 50 | } 51 | 52 | if(canfilter) 53 | { x = (x0 + xold) * 0.5; 54 | } else { 55 | x = x0; 56 | } 57 | } 58 | return x; 59 | } 60 | }; 61 | 62 | class BoilerStatisic 63 | { 64 | public: 65 | /* число включений горелки */ 66 | unsigned int NflameOn; //число включений горелки 67 | unsigned int NflameOn_h; //число включений горелки за час 68 | unsigned int NflameOn_day; //число включений горелки за день 69 | unsigned int NflameOn_h_prev; //число включений горелки за предыдущий час 70 | unsigned int NflameOn_day_prev; //число включений горелки за предыдущий день 71 | float ModIntegral_h; //интеграл модуляции с начала часа 72 | float ModIntegral_d; //интеграл модуляции с начала суток 73 | float Eff_Mod_h; // эффективная модуляция за текущий час 74 | float Eff_Mod_d; // эффективная модуляция за текущие сутки 75 | float Eff_Mod_h_prev; // эффективная модуляция за предыдущий час 76 | float Eff_Mod_d_prev; // эффективная модуляция за предыдущие сутки 77 | time_t t_flame_on; //время включения горелки 78 | time_t t_flame_off; //время выключения горелки 79 | time_t t_I_last; //предыдущее время подсчета интеграла 80 | time_t t_HW_on; //время включения горячей воды 81 | time_t t_HW_off; //время выключения горячей воды 82 | unsigned int sec_h; //секунд с начала часа 83 | unsigned int sec_d; //секунд с начала суток 84 | BoilerStatisic() 85 | { 86 | NflameOn = NflameOn_h = NflameOn_day = NflameOn_h_prev = NflameOn_day_prev = 0; 87 | t_flame_on = t_flame_off = t_I_last = 0; 88 | t_HW_on = t_HW_off = 0; 89 | ModIntegral_h = 0.; 90 | ModIntegral_d = 0.; 91 | Eff_Mod_h = Eff_Mod_d = Eff_Mod_h_prev = Eff_Mod_d_prev = 0.; 92 | sec_h = sec_d = 0; 93 | } 94 | void calcNflame(int newSts); 95 | void calcN_HW(int newSts); 96 | void calcIntegral(float flame); 97 | 98 | }; 99 | 100 | class SD_Termo:public SmartDevice 101 | { 102 | public: 103 | short int stsOT; // -1 not init, 0 - normal work, 2 - timeout 104 | time_t t_lastwork; // time of last stsOT = 0 105 | int stsT1; 106 | int stsT2; 107 | float t1; 108 | float t2; 109 | //sizeof(unsigned long)=4 110 | //Set Boiler Status 111 | bool enable_CentralHeating; //user set 112 | #if PID_USE 113 | bool enable_CentralHeating_real;//real used. Without PID equal to enable_CentralHeating 114 | #endif 115 | 116 | bool enable_HotWater; 117 | bool enable_Cooling; 118 | bool enable_CentralHeating2; 119 | 120 | bool HotWater_present; 121 | bool RetT_present; 122 | bool CH2_present; 123 | bool DHW_tank_present; //DHW configuration: storage tank 124 | bool Toutside_present; 125 | bool Pressure_present; 126 | bool Dhw_t_present; //у Buderus'а с косвенным нагревом есть dhw и нет dhw_t 127 | bool Tstorage_present; // ID29 128 | bool MaxRelModLevel_present; // ID14 MaxRelModLevelSetting 129 | bool RemoteRequest_present; // ID4 present, can be used for BLOR = Boiler Lock-out Reset 130 | #if RELAY_USE 131 | bool Relay_present; //Relay present and use 132 | bool Relay_init_sts; //Relay state at start 133 | bool Relay_sts; //Relay state 134 | #endif 135 | #if ST_VERS == 2 136 | bool OT_slave_present; //OT_slave present and use 137 | short int OT_slave_mode; /* 0 slave readonly, 1 master readonly */ 138 | short int ot_slave_stsOT; //-2 not initialise, -1 not init interface, 0 - normal work, 2 - timeout 139 | time_t ot_slave_t_lastwork; // time of last ot_slave_stsOT = 0 140 | #endif 141 | 142 | unsigned int OTmemberCode; 143 | unsigned long response; 144 | float Tset; // Control setpoint ie CH water temperature setpoint (°C) 145 | float Tset_r; // Temp set from responce 146 | float Tset2; // Control setpoint for 2e CH circuit (°C) 147 | float Tset2_r; // Temp2 set from responce 148 | float MaxTSet; // f8.8 Max CH water setpoint (°C) (Remote parameters 2) ID57 149 | 150 | float BoilerT; // Boiler flow water temperature (°C) CH 151 | float BoilerT2; // Boiler CH2 water temperature (°C) CH 152 | float RetT; // Return water temperature (°C) CH 153 | float TdhwSet; // f8.8 DHW setpoint (°C) (Remote parameter 1) 154 | float dhw_t; // DHW temperature (°C) 155 | float Toutside; 156 | float Tstorage; // [Solar] storage temperature (°C) 157 | float Texhaust;// s16 Boiler exhaust temperature (°C) 158 | float FlameModulation; //Relative Modulation Level (%) 159 | float Pressure; // Water pressure in CH circuit (bar) 160 | float MaxRelModLevelSetting; // if MaxRelModLevel_present + need_set_MaxRelModLevel 161 | unsigned int MaxCapacity; 162 | unsigned int MinModLevel; 163 | unsigned int Fault; 164 | unsigned int OEMDcode; 165 | unsigned int rcode[5]; 166 | int BoilerStatus; 167 | int BoilerStatusRequest; 168 | byte need_set_T; 169 | byte need_set_T2; 170 | byte need_set_dhwT; 171 | byte need_set_MaxRelModLevel; 172 | byte need_set_RemoteRequest; 173 | byte need_send_Blor; 174 | byte need_write_f; 175 | byte need_set_MaxTSet; 176 | 177 | int TestCmd; 178 | int TestId; 179 | int TestPar; 180 | int TestResponse; 181 | int TestStatus; 182 | 183 | unsigned long RespMillis; 184 | BoilerStatisic Bstat; 185 | #if OT_DEBUGLOG 186 | bool enable_OTlog; //Включаем лог OT 187 | int nOTlog; //пакетов в логе 188 | short int nOT_need_send;// 189 | int nOTsend;// 190 | myBuffer2 OTlogBuf; 191 | #endif // OT_DEBUGLOG 192 | 193 | 194 | #if MQTT_USE 195 | byte useMQTT; //0 = not use, 1 use but not setup, 0x3 - use & setup 196 | byte stsMQTT; 197 | #if defined(ARDUINO_ARCH_ESP8266) 198 | //20+20+4+10+10+10= 74 199 | char MQTT_server[20]; 200 | char MQTT_topic[20]; 201 | unsigned int MQTT_interval; //sec 202 | char MQTT_user[10]; 203 | char MQTT_pwd[10]; 204 | char MQTT_devname[10]; 205 | #elif defined(ARDUINO_ARCH_ESP32) 206 | //40+40+4+40+20+40= 184 (+110) 207 | char MQTT_server[80]; 208 | char MQTT_topic[40]; 209 | int MQTT_interval; //sec 210 | char MQTT_user[40]; 211 | char MQTT_pwd[20]; 212 | char MQTT_devname[40]; 213 | #endif 214 | unsigned short MQTT_port; /* MQTT port, default 1883 */ 215 | int MQTT_need_report; 216 | #endif //MQTT_USE 217 | #if PID_USE 218 | byte usePID; // 1/0 использовать PID да/нет 219 | signed char srcTroom; // источник температуры в комнате -1 - n/a, 0/1 - T1/T2, 2 - Text, 3,4 MQTT t_indoor/t_outdoor 220 | signed char srcText; // источник температуры на улице -1 - n/a, 0/1 - T1/T2, 2 - Text, 3,4 MQTT t_indoor/t_outdoor 221 | class pid mypid; 222 | x_mean t_mean[8]; 223 | float tempindoor; 224 | #define TroomTarget mypid.xTag 225 | float tempoutdoor; 226 | float _U0start; 227 | int InTstartset; 228 | int IsSetTemp; //01 tempIndoor set | 0x02 tempOutdoor set 229 | #endif 230 | int start_sts; //1 - start state, need ask server for last I and U0(?), &0x02 - OT start log, 0 - not start 231 | unsigned short int UseID2; 232 | unsigned short int ID2masterID; 233 | unsigned short int CH2_DHW_flag; 234 | unsigned short int UseWinterMode; 235 | unsigned short int Use_OTC; 236 | unsigned short int Use_ID29_DHW_flag; 237 | unsigned short int Immergas_fix_flag; 238 | unsigned short int Use_MaxRelModLevel; 239 | 240 | //гистерезис включения отопления, минимальная разница между заданной и текущей температурой теплоносителя 241 | // при которой включится горелка. У Mizudo может быть 15 и больше градусов 242 | float CH_StartGist; 243 | 244 | int CapabilitiesDetected; 245 | time_t t_lastSetPointChange; 246 | int src_lastSetPointChange; 247 | float oldTroomSetpoint; 248 | float umin; //минимальная температура теплоносителя 249 | float umax; //максимальная температура теплоносителя 250 | 251 | SD_Termo(void) 252 | { 253 | enable_CentralHeating = true; 254 | #if PID_USE 255 | enable_CentralHeating_real = enable_CentralHeating; 256 | #endif 257 | #if RELAY_USE 258 | Relay_present = true; 259 | Relay_init_sts = false; 260 | Relay_sts = false; 261 | #endif 262 | HotWater_present = false; 263 | DHW_tank_present = false; 264 | 265 | HotWater_present = false; 266 | Dhw_t_present = false; 267 | RetT_present = false; 268 | CH2_present = false; 269 | Toutside_present = false; 270 | Pressure_present = false; 271 | Tstorage_present = false; 272 | enable_HotWater = true; 273 | enable_Cooling = false; 274 | enable_CentralHeating2 = false; 275 | MaxRelModLevel_present = false; 276 | RemoteRequest_present = false; 277 | 278 | CapabilitiesDetected = 0; 279 | 280 | stsOT = -1; 281 | t_lastwork = 0; 282 | t_lastSetPointChange = 0; 283 | stsT1 = -1; 284 | stsT2 = -1; 285 | t1 = t2 = 0.; 286 | response = 0; 287 | BoilerT = BoilerT2 = 0.; 288 | Tset = 40.; 289 | Tset_r = 0.; 290 | Tset2 = 41.; 291 | Tset2_r = 0.; 292 | 293 | TdhwSet = 40.; 294 | /* look at int OpenTherm::update_OTid(int id, int sts) */ 295 | need_set_T = 9; 296 | need_set_T2 = 0; 297 | need_set_dhwT = 2; 298 | need_set_MaxRelModLevel = 9; 299 | need_set_RemoteRequest = 3; 300 | need_send_Blor = 0; 301 | need_set_MaxTSet = 1; 302 | /********************************/ 303 | need_write_f = 0; 304 | RetT = 0.; 305 | dhw_t = 0.; 306 | Toutside = 0.; 307 | Texhaust = 0.; 308 | Tstorage = 0.; 309 | FlameModulation = 0.; 310 | Pressure = 0.; 311 | MaxRelModLevelSetting = 100.; 312 | MaxCapacity = MinModLevel = 0; 313 | Fault = 0; 314 | OEMDcode = 0; 315 | OTmemberCode = 0; 316 | rcode[0] = rcode[1] = rcode[2] = rcode[3] = rcode[4] = 0; 317 | BoilerStatus = BoilerStatusRequest = 0; 318 | TestCmd = TestId = TestPar = TestResponse = 0; 319 | TestStatus = 0; 320 | RespMillis = 0; 321 | #if OT_DEBUGLOG 322 | enable_OTlog = false; 323 | nOTlog = nOTsend = 0; 324 | nOT_need_send = 0; 325 | #endif 326 | #if MQTT_USE 327 | useMQTT = 0; 328 | stsMQTT = 0; 329 | strcpy(MQTT_server,"192.168.1.1"); 330 | strcpy(MQTT_topic,"ST"); 331 | strcpy(MQTT_devname,"Boiler"); 332 | MQTT_user[0] = 0; 333 | MQTT_pwd[0] = 0; 334 | MQTT_interval = 10; //sec 335 | MQTT_need_report = 0; 336 | MQTT_port = 1883; 337 | #endif 338 | #if PID_USE 339 | usePID = 0; 340 | srcTroom = srcText = 0; 341 | tempindoor = tempoutdoor = 0.; 342 | TroomTarget = 18.f; 343 | IsSetTemp = 0; 344 | #endif 345 | UseID2 = 0; 346 | ID2masterID = 0; 347 | CH2_DHW_flag = 0; 348 | UseWinterMode = 0; 349 | Use_OTC = 0; 350 | Use_ID29_DHW_flag = 0; 351 | Immergas_fix_flag = 0; 352 | CH_StartGist = 10.f; 353 | Use_MaxRelModLevel = 0; 354 | umin = 40; 355 | umax = 80; 356 | MaxTSet = umax; 357 | start_sts = 1; 358 | _U0start = 0; 359 | InTstartset = 0; 360 | oldTroomSetpoint = 0.; 361 | src_lastSetPointChange = -1; 362 | #if ST_VERS == 2 363 | OT_slave_present = true; 364 | OT_slave_mode = 0; /* 0 slave readonly, 1 master readonly */ 365 | ot_slave_stsOT = -2; //-2 not initialise, -1 not init interface, 0 - normal work, 2 - timeout 366 | ot_slave_t_lastwork = 0; // time of last ot_slave_stsOT = 0 367 | #endif 368 | 369 | } 370 | void RelayInit(void); 371 | void RelayOnOff(bool onoff); 372 | void init(int src); 373 | void loop(void); 374 | void OpenThermInfo(void); 375 | void Send_to_server_HandShake(void); 376 | void Send_to_server_IdentifySelf(void); 377 | void Send_to_server_Sts(unsigned char * &MsgOut, int &Lsend, U8 *(*get_buf) (U16 size)); 378 | void Send_to_server_Sts(void); // PACKED unsigned char * &MsgOut, int &Lsend, U8 *(*get_buf) (U16 size)); 379 | #if OT_DEBUGLOG 380 | void Send_to_server_OTlog(void); 381 | int server_answerOTLog( U8 *bf, int len); 382 | #endif 383 | int servercallback_send_Sts_answ( U8 *bf, int len); 384 | int server_answer_IdentifySelf( U8 *bf, int len); 385 | void callback_set_tcp_server( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 386 | 387 | // void udp_OpenThermInfo( U8 *bf, unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 388 | int callback_Get_OpenThermInfo( U8 *bf, int len, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 389 | void callback_Set_OpenThermData( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 390 | void callback_Set_State( U8 *bf, int len, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 391 | 392 | void callback_getdata( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 393 | void callback_testcmd( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 394 | void callback_testcmdanswer( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 395 | // void servercallback_GetOtInfo( U8 *bf, int len); 396 | int callback_Get_Capabilities( U8 *bf, int len, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 397 | 398 | int servercallback_Get_Sts( U8 *bf, int len, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 399 | 400 | #if OT_DEBUGLOG 401 | void callback_GetOTLog( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 402 | #endif 403 | int Write_data_fs(char *path, uint8_t *dataBuff, int len, int mode); 404 | int Read_data_fs(char *path, uint8_t *dataBuff, int len, int &rlen, int mode); 405 | int Read_ot_fs(void); 406 | int Write_ot_fs(void); 407 | int Read_mqtt_fs(void); 408 | int Write_mqtt_fs(void); 409 | 410 | float CHtempLimit(float _t); /* return t within limit MIN_CH_TEMP MAX_CH_TEMP*/ 411 | float RoomtempLimit(float _t); /* return t within limit MIN_ROOM_TEMP MAX_ROOM_TEMP*/ 412 | 413 | void OnChangeT(float t, int src); 414 | void OnOpenThermRestore(void); 415 | #if PID_USE 416 | void loop_PID(void); 417 | void loop_mean(void); //получаем средние значения для используемых температур 418 | int loop_pid_gettemp(int &_start); //получаем значения tindoor и toutdoor 419 | void set_new_PID_setpoint(float Tsetpoint, int src); 420 | #endif 421 | void DetectCapabilities(void); 422 | }; 423 | 424 | #endif // SD_OPENTHERM -------------------------------------------------------------------------------- /src/SD_pid_control.cpp: -------------------------------------------------------------------------------- 1 | /* SD_pid_control.cpp */ 2 | #include 3 | #include 4 | 5 | #include "OpenTherm.h" 6 | #include "SD_OpenTherm.hpp" 7 | #include "Smart_commands.h" 8 | 9 | #if PID_USE 10 | 11 | void MQTT_pub_cmd(int on); 12 | void MQTT_pub_cmd2(int val); 13 | 14 | int debcode = 0; 15 | int wait_if_takt = 60*3; 16 | 17 | void SD_Termo::loop_PID(void) 18 | { static int start = 2; 19 | static int start_heat = 2; 20 | static unsigned long int t0=0, t0_mean=0, t_start_heat=0, t_stop_heat = 0; 21 | static float _ustart = 0.f; 22 | static int OldBoilerStatus=0, issF = 0; 23 | unsigned long int t; 24 | float u0, _u, _uu; 25 | int rc, dt; 26 | time_t now; 27 | extern OpenTherm ot; 28 | int is = 0; 29 | static int need_heat = 0; 30 | 31 | if(!usePID) 32 | return; 33 | 34 | t = millis(); 35 | if(t - t0_mean >= (unsigned long int)(mypid.t_interval*1000)) 36 | { loop_mean(); //получаем средние значения для используемых температур 37 | t0_mean = t; 38 | } 39 | 40 | if((stsOT == 0) && ((OldBoilerStatus & 0x08) != (BoilerStatus & 0x08)) ) //Flame status changed 41 | issF = 4; 42 | 43 | 44 | if(!issF && (t - t0 < (unsigned long int)(mypid.t_interval*1000))) 45 | return; 46 | 47 | // Serial.printf("==>PID dt %d iss %d\n", t-t0, issF); 48 | 49 | OldBoilerStatus = BoilerStatus; 50 | //if Flame status changed then 4 times continue with 4 sec interval 51 | if(issF > 0) 52 | { if(issF < 4 && (t - t0 < 4000)) 53 | return; 54 | issF--; 55 | } 56 | 57 | t0 = t; 58 | 59 | { static int raz = 0; 60 | MQTT_pub_cmd(raz); 61 | raz = (raz + 1)&0x01; 62 | } 63 | 64 | /************** считаем u0 ********************/ 65 | is = loop_pid_gettemp(start); 66 | 67 | if(is & 0x02) 68 | { if(tempoutdoor <= mypid.y0) 69 | u0 = mypid.u0 + (mypid.u1 - mypid.u0) * (tempoutdoor - mypid.y0) /(mypid.y1 - mypid.y0); 70 | else 71 | { if(mypid.y0 != mypid.xTag) 72 | u0 = mypid.xTag + (mypid.u0 - mypid.xTag) * (tempoutdoor - mypid.xTag) /(mypid.y0 - mypid.xTag); 73 | else 74 | u0 = mypid.xTag; 75 | } 76 | } else { //нет внешней температуры 77 | u0 = _U0start; 78 | } 79 | 80 | // Serial.printf("loop_pid_gettemp is =%d start=%d tempoutdoor =%f u0=%f InT=%f\n", 81 | // is, start, tempoutdoor, u0, mypid.InT ); 82 | 83 | /**********************************************/ 84 | if(!(is & 0x01)) // если нет tempindoor 85 | return; 86 | 87 | rc = mypid.Pid(tempindoor, u0); //PID 88 | 89 | // Serial.printf("mypid.Pid rc =%d\n", rc); 90 | 91 | if(rc != 1) // если PID не OK 92 | return; 93 | now = time(nullptr); 94 | 95 | if(HotWater_present) 96 | { if(BoilerStatus & 0x04) /* при включении горячей воды не занимаемся регулированием, хотя PID все равно вызываем */ 97 | return; 98 | if(enable_CentralHeating_real && !(BoilerStatus& 0x08)) //flame off если горелка выключена 99 | { dt = now - Bstat.t_HW_off; 100 | if(dt < 180 /* 500 */) //если HW выключилось 180 сек назад или раньше, то не регулируем 101 | return; 102 | } 103 | } 104 | 105 | _u = mypid.u; 106 | if(_u > umax) 107 | _u = umax; 108 | 109 | if(_u <= mypid.xTag || (_u <= umin - 0.5f) ) 110 | { need_heat = 0; 111 | } else if(_u >= umin + 0.5f) { 112 | need_heat = 1; 113 | } 114 | 115 | // Serial.printf("==>PID _u %f need_heat %d\n", _u, need_heat); 116 | 117 | if(need_heat == 1 && (start_heat == 0 || start_heat == 2)) //включение отопления 118 | { dt = now - t_stop_heat; 119 | if(dt > 180) //3 минуты - защита от кратковременного вЫключения 120 | { 121 | enable_CentralHeating_real = true; 122 | start_heat = 1; 123 | t_start_heat = now; //время включения отопления 124 | _ustart = _u; 125 | } 126 | } else if(need_heat == 0 && (start_heat == 1 || start_heat == 2)) { //выключение отопления 127 | dt = now - t_start_heat; 128 | if(dt > 180) //3 минуты - защита от кратковременного включения 129 | { 130 | enable_CentralHeating_real = false; 131 | _u = umin; 132 | start_heat = 0; 133 | t_stop_heat = now; //время выключения отопления 134 | } 135 | } 136 | 137 | // Serial.printf("==>PID _u %f need_heat %d enable_CentralHeating_real %d\n", 138 | // _u, need_heat, enable_CentralHeating_real); 139 | 140 | if(start_heat == 1 && need_heat == 1) //отопление включено 141 | { if(BoilerStatus& 0x08) //если горелка включена 142 | { dt = now - Bstat.t_flame_on; 143 | _uu = _u; 144 | // if(issF == 3) 145 | // _ustart = _u; 146 | 147 | if(dt < 15*60) //пытаемся плавно повышать температуру 148 | { float r, du; 149 | r = dt/(60.*15.); 150 | _uu = _u * r + _ustart * (1-r); //то корректируем уставку температуры 151 | } 152 | if(BoilerT > _uu) //однако, если температура теплоносителя уже достигла заданного значения 153 | { _uu = BoilerT; 154 | /* пытаемся предотвратить тактование */ 155 | if(_uu - _u > 4.f) /* допускаем повышение температуры бойлера не более чем на 4 градуса выше PID */ 156 | _uu = _u + 4.f; 157 | if(_uu > umax) // ограничиваем max 158 | _uu = umax; 159 | } 160 | 161 | _u = _uu; 162 | 163 | } else { //если горелка еще выключена 164 | if(_u - BoilerT > CH_StartGist) //10.f 165 | { _uu = BoilerT + CH_StartGist; //ограничиваем температуру теплоносителя при включении 166 | if(_uu < umin) 167 | _uu = umin; 168 | _u = _uu; 169 | _ustart = _u; 170 | } 171 | } 172 | } 173 | 174 | Tset = CHtempLimit(_u); 175 | 176 | // Serial.printf("==>PID Tset %f_u %f need_heat %d enable_CentralHeating_real %d\n", 177 | // Tset, _u, need_heat, enable_CentralHeating_real); 178 | need_set_T = 1; // for OpenTherm 179 | #if MQTT_USE 180 | MQTT_need_report = 1; // for MQTT 181 | MQTT_pub_cmd2(millis() - t); 182 | 183 | #endif 184 | 185 | } 186 | 187 | //src = 0 - Web, 1 - MQTT, 2 servercallback_send_Sts_answ, 3 callback_Set_OpenThermData, 188 | // 4 callback_Set_State 189 | void SD_Termo::set_new_PID_setpoint(float Tsetpoint, int src) 190 | { 191 | #if PID_USE 192 | oldTroomSetpoint = mypid.xTag; 193 | src_lastSetPointChange = src; 194 | mypid.Set_NewTag(Tsetpoint, tempindoor); 195 | t_lastSetPointChange = time(nullptr); 196 | #endif 197 | } 198 | 199 | 200 | //получаем средние значения для используемых температур 201 | void SD_Termo::loop_mean(void) 202 | { 203 | for(int i=0; i < 8; i++) 204 | { 205 | if(t_mean[i].isset == -1 && t_mean[i].nx == 0) 206 | continue; 207 | t_mean[i].get(); 208 | //debug 209 | //if(i < 2) 210 | // Serial.printf("t_mean[%d] x=%f mean =%f nx=%d isset %d\n", i, t_mean[i].x, t_mean[i].xmean, t_mean[i].nx, t_mean[i].isset ); 211 | 212 | if(t_mean[i].nx > 2 || (i == 4 && t_mean[i].isset == 1)) /* 4 - outdoor mqtt */ 213 | t_mean[i].init(1); 214 | } 215 | } 216 | 217 | int SD_Termo::loop_pid_gettemp(int &_start) //получаем значения tindoor и toutdoor 218 | { int is=0; 219 | if(_start) 220 | { if(_start == 2) 221 | { // start_t = t; 222 | _start = 1; 223 | mypid.NextTact(); 224 | } 225 | 226 | if(srcTroom < 0 || srcTroom > 4) 227 | { is = 0; 228 | } else { 229 | // Serial.printf("0 srcTroom =%d, isset=%d xmean=%f nx=%d\n", 230 | // srcTroom, t_mean[srcTroom].isset,t_mean[srcTroom].xmean, t_mean[srcTroom].nx); 231 | if(t_mean[srcTroom].isset != -1) 232 | { tempindoor = t_mean[srcTroom].x; 233 | is |= 1; 234 | IsSetTemp |= 0x01; 235 | _start = 0; 236 | } 237 | 238 | if(srcText < 0 || srcText > MAX_PID_SRC) 239 | { is &= ~2; 240 | } else if(t_mean[srcText].isset != -1) { 241 | tempoutdoor = t_mean[srcText].x; 242 | is |= 2; 243 | IsSetTemp |= 0x02; 244 | } 245 | 246 | if(is & 0x01 && InTstartset == 0) 247 | { mypid.Init_I(tempindoor ); 248 | } 249 | 250 | } 251 | } else { // start == 0 252 | if(srcTroom >= 0 && srcTroom <= 3 ) // !4 253 | { 254 | if(t_mean[srcTroom].isset >= 0) 255 | { tempindoor = t_mean[srcTroom].x; 256 | is |= 1; 257 | IsSetTemp |= 0x01; 258 | } 259 | } 260 | if((srcText >= 0 && srcText <= 2) || (srcText >= 4 && srcText <= MAX_PID_SRC)) // !3 MAX_PID_SRC!! 261 | { 262 | if(t_mean[srcText].isset >= 0) 263 | { tempoutdoor = t_mean[srcText].x; 264 | #if DEBUG_WITH_EMULATOR //translate to emulator tempoutdoor as TdhwSet 265 | need_set_dhwT = 1; 266 | #endif 267 | is |= 2; 268 | IsSetTemp |= 0x02; 269 | } 270 | } 271 | } 272 | /************ endof if(_start) **********************************/ 273 | return is; 274 | } 275 | 276 | #endif // PID_USE 277 | -------------------------------------------------------------------------------- /src/SmartDevice.cpp: -------------------------------------------------------------------------------- 1 | /* SmartDevice.cpp */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #if defined(ARDUINO_ARCH_ESP8266) 8 | #include 9 | #include 10 | typedef ESP8266WebServer WEBServer; 11 | #elif defined(ARDUINO_ARCH_ESP32) 12 | #include 13 | #include 14 | typedef WebServer WEBServer; 15 | #endif 16 | 17 | #include "SmartDevice.hpp" 18 | #include "Smart_commands.h" 19 | 20 | extern int TCPserver_close_on_send; 21 | 22 | 23 | /*********************************************************************************/ 24 | // MCMD_HAND_SHAKE 25 | void SmartDevice::callback_HandShake( U8 *bf,PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 26 | { int l; 27 | l = sizeof(HAND_SHAKE_OUT); 28 | Lsend = l + sizeof(short int)*3; //16 29 | MsgOut = get_buf(Lsend); 30 | memcpy((void *)&MsgOut[0],(void *)bf,6); 31 | #if SERIAL_DEBUG 32 | // Serial.printf("MCMD_HAND_SHAKE Lsend=%i\n", Lsend ); 33 | #endif 34 | 35 | 36 | if((bf[6+l-1] == 0) && !strncmp((char *)&bf[6], HAND_SHAKE_INP,l)) 37 | { memcpy(&MsgOut[6], HAND_SHAKE_OUT,l); 38 | } else { 39 | memcpy(&MsgOut[6], HAND_SHAKE_ERR,l); 40 | } 41 | /* 42 | for(int i=0; i= 16) 56 | { l = sizeof(HAND_SHAKE_OUT); 57 | if(strncmp((char *)&bf[6], HAND_SHAKE_OUT,l) == 0) 58 | { 59 | // Serial.printf("HAND_SHAKE_OUT detected\n"); 60 | rc = 1; 61 | TCPserver_rc = MCMD_HAND_SHAKE; 62 | TCPserver_close_on_send = 0; 63 | } else { 64 | TCPserver_close_on_send = 1; 65 | // Serial.printf("HAND_SHAKE_OUT NOT detected\n"); 66 | } 67 | } 68 | 69 | return rc; 70 | } 71 | 72 | 73 | //SCMD_GET_HAND_SHAKE 74 | int SmartDevice::server_send_HandShake(unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 75 | { int l, rc = 0; 76 | 77 | TCPserver_rc = SCMD_GET_HAND_SHAKE; 78 | TCPserver_sts2 = 1; 79 | #if SERIAL_DEBUG 80 | // Serial.printf("server_send_HandShake SCMD_GET_HAND_SHAKE\n"); 81 | // Serial.printf("TCPserver_sts2 = 1\n"); 82 | #endif 83 | 84 | /* 85 | struct Msg1 *msg; 86 | extern int indcmd; 87 | 88 | #if SERIAL_DEBUG 89 | Serial.printf("server_send_HandShake\n"); 90 | #endif 91 | 92 | l = strlen(HAND_SHAKE_INP); 93 | 94 | Lsend = 6 + l; 95 | 96 | MsgOut = get_buf(Lsend); 97 | msg = (struct Msg1 *)MsgOut; 98 | msg->cmd0 = 0x22; 99 | msg->cmd = MCMD_HAND_SHAKE; 100 | msg->ind = indcmd++; 101 | memcpy(&MsgOut[6], HAND_SHAKE_INP,l); 102 | TCPserver_close_on_send = 0; 103 | */ 104 | return rc; 105 | } 106 | 107 | //MD_ECHO 108 | void SmartDevice::callback_Echo( U16 len, U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 109 | { Lsend = len; 110 | MsgOut = get_buf(Lsend); 111 | memcpy((void *)&MsgOut[0],(void *)bf, Lsend); 112 | } 113 | 114 | //MD_IDENTIFY 115 | void SmartDevice::callback_Identify( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 116 | { int l, lp; 117 | 118 | l = strlen((PGM_P)IDENTIFY_TEXT); 119 | 120 | lp = sizeof(int)*6 + 6 + 12 + l; 121 | Lsend = 6 + sizeof(short int) + lp; 122 | 123 | MsgOut = get_buf(Lsend); 124 | memcpy((void *)&MsgOut[0],(void *)bf,6); 125 | *((PACKED short int *) (&MsgOut[6])) = (short int)lp; 126 | // *((PACKED int *) (&MsgOut[8])) = IDENTIFY_TYPE; 127 | *((unsigned short int *) (&MsgOut[8])) = IDENTIFY_TYPE; 128 | *((unsigned short int *) (&MsgOut[10])) = IDENTIFY_SUBTYPE; 129 | 130 | *((PACKED int *) (&MsgOut[12])) = IDENTIFY_CODE; 131 | *((PACKED int *) (&MsgOut[16])) = IdNumber; 132 | *((PACKED int *) (&MsgOut[20])) = Vers; 133 | *((PACKED int *) (&MsgOut[24])) = SubVers; 134 | *((PACKED int *) (&MsgOut[28])) = SubVers1; 135 | memcpy((void *)&MsgOut[32],(void *)BiosDate,12); 136 | 137 | memcpy((void *)&MsgOut[44],(void *)&Mac[0],6); 138 | memcpy_P((void *)&MsgOut[50],(void *)(PGM_P)IDENTIFY_TEXT, l); 139 | 140 | // Serial.printf("IDENTIFY_TEXT 4 l=%i Lsend =%i\n", l, Lsend ); 141 | // Serial.print(IDENTIFY_TEXT); 142 | } 143 | 144 | /* 145 | struct timeb 146 | { time_t time; 147 | unsigned short millitm; 148 | short int timezone; 149 | short int dstflag; 150 | }; 151 | */ 152 | 153 | //MCMD_GETTIME прочесть время 154 | void SmartDevice::callback_gettime( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 155 | { int tm_l; 156 | struct timeval tv; 157 | 158 | tm_l = sizeof(time_t); //4 ESP32, 8 ESP8266 159 | Lsend = 6 + 2 + tm_l + 4; 160 | if(tm_l == 4) 161 | Lsend += 4; 162 | 163 | MsgOut = get_buf(Lsend); 164 | time_t now = time(nullptr); 165 | 166 | gettimeofday(&tv, NULL); 167 | // Serial.println(ctime(&now)); 168 | // Serial.printf("Sizeof timeb %i\n", sizeof(timeb)); 169 | 170 | memcpy((void *)&MsgOut[0],(void *)&bf[0],6); 171 | memcpy((void *)&MsgOut[6],(void *)&tm_l,2); 172 | if(tm_l == 4) 173 | { 174 | memcpy((void *)&MsgOut[8],(void *)&tv.tv_sec,tm_l); 175 | memcpy((void *)&MsgOut[12],(void *)&tv.tv_usec,4); 176 | // Serial.printf("################## tv %d %d\n", tv.tv_sec, tv.tv_usec); 177 | } else { 178 | memcpy((void *)&MsgOut[8],(void *)&tv.tv_sec,tm_l); 179 | memcpy((void *)&MsgOut[8+tm_l],(void *)&tv.tv_usec,4); 180 | } 181 | } 182 | 183 | //MCMD_SETTIME установить время 184 | void SmartDevice::callback_settime( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 185 | { int tm_l; 186 | struct timeb tb; 187 | char tzbuf[20]; 188 | timeval tv = { 0, 0 }; 189 | tm_l = sizeof(time_t); 190 | Lsend = 6; 191 | MsgOut = get_buf(Lsend); 192 | 193 | memcpy((void *)&MsgOut[0],(void *)&bf[0],6); 194 | 195 | memcpy((void *)&tv.tv_sec,(void *)&bf[6], 4); 196 | memcpy((void *)&tv.tv_usec,(void *)&bf[10],4); 197 | // Serial.printf("###****################ tv %d %d\n", tv.tv_sec, tv.tv_usec); 198 | 199 | //{ time_t now; 200 | // now = time(nullptr); 201 | // Serial.printf("1 %s\n", ctime(&now)); 202 | //} 203 | 204 | 205 | settimeofday(&tv, nullptr); 206 | 207 | #if SERIAL_DEBUG 208 | //{ time_t now; 209 | // now = time(nullptr); 210 | // Serial.printf("2 %s\n", ctime(&now)); 211 | //} 212 | #endif 213 | } 214 | 215 | //MCMD_SET_UDPSERVER 216 | void SmartDevice::callback_set_udp_server( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 217 | { int s, dt, p; // i, rc; 218 | // char tzbuf[20]; 219 | extern int Udp_RemotePort; 220 | 221 | Lsend = 6; 222 | MsgOut = get_buf(Lsend); 223 | 224 | memcpy((void *)&MsgOut[0],(void *)&bf[0],6); 225 | 226 | memcpy((void *)&s,(void *)&bf[6],4); 227 | memcpy((void *)&dt, (void *)&bf[10],4); 228 | memcpy((void *)&p,(void *)&bf[14],4); 229 | UDPserver_sts = s; 230 | if(s) 231 | UDPserver_t = millis(); 232 | UDPserver_port = p; 233 | Udp_RemotePort = p; 234 | UDPserver_repot_period = dt; 235 | } 236 | -------------------------------------------------------------------------------- /src/SmartDevice.hpp: -------------------------------------------------------------------------------- 1 | /* SmartDevice.hpp */ 2 | #ifndef SMARTDEVICE 3 | #define SMARTDEVICE 4 | 5 | #include "Smart_Config.h" 6 | 7 | #pragma pack(1) 8 | 9 | 10 | #define MASTER_BIOSCODE 23 11 | #define MASTER_VERSION 0 12 | #define MASTER_SUBVERSION 8 13 | #define MASTER_SUBVERSION1 4 14 | /***************/ 15 | 16 | #define U8 unsigned char 17 | #define U16 unsigned short 18 | #define U32 unsigned int 19 | #define S8 char 20 | #define S16 short 21 | #define S32 int 22 | #define Bool unsigned char; 23 | #define TRUE 1 24 | #define FALSE 0 25 | #define PACKED /* */ 26 | 27 | #define FLASH_MARK 0x1AA11BBf 28 | #define FLASH_WRITESIZE (36+4*16) 29 | 30 | //макрос для автоматического определения размера статической части класса для записи-чтения во флеш 31 | #define FLASH_WRITESIZE0 ((int)(&this->sts) - (int)(&this->mark)) 32 | 33 | 34 | struct Msg1 35 | { short int cmd0; //команда 36 | short int cmd; //команда 37 | short int ind; //параметр 38 | unsigned char Buf[128]; 39 | }; 40 | 41 | class SmartDevice 42 | { 43 | public: 44 | const int mark; /* пометка для определения наличия во флеше */ 45 | const int size; /* размер для чтения-записи-сравнения версий */ 46 | /* константы */ 47 | const int BiosCode; /* код биоса */ 48 | const int Vers; /* версия */ 49 | const int SubVers; /* подверсия */ 50 | const int SubVers1; /* версия подверсии */ 51 | #if defined(ARDUINO_ARCH_ESP8266) 52 | static char BiosDate[12]; /* дата компиляции биоса */ 53 | #elif defined(ARDUINO_ARCH_ESP32) 54 | const char BiosDate[12]; /* дата компиляции биоса */ 55 | #endif 56 | 57 | static char LocalUrl[24]; /* smth like http://192.168.200.201 */ 58 | int IdNumber; /* номер устройства */ 59 | #if defined(ARDUINO_ARCH_ESP8266) 60 | unsigned char Mac[6]; /* mac - адрес */ 61 | unsigned char _foo[2]; 62 | #elif defined(ARDUINO_ARCH_ESP32) 63 | unsigned char Mac[6]; /* mac - адрес */ 64 | #endif 65 | int TZoffset; /* *time zone offset */ 66 | unsigned short int UDPserver_port; /* порт сервера */ 67 | int UDPserver_repot_period; /* периодичность отправки данных серверу, сек */ 68 | int UDPserver_sts; /* статус сервера */ 69 | long int UDPserver_t; /* время последнего сообщения серверу, следующее через server_repot_period */ 70 | 71 | unsigned short int TCPserver_port; /* порт сервера */ 72 | int TCPserver_report_period; /* периодичность отправки данных серверу, сек */ 73 | int TCPserver_sts; /* статус сервера */ 74 | int TCPserver_sts2; /* статус2 сервера */ 75 | int TCPserver_rc; /* статус ответа сервера */ 76 | unsigned short int Use_remoteTCPserver; 77 | 78 | long int TCPserver_t; /* время последнего сообщения серверу, следующее через server_repot_period */ 79 | int ClientId; /* Client Id */ 80 | int ClientId_k; /* Client Id key */ 81 | 82 | IPAddress udp_remoteIP; 83 | IPAddress tcp_remoteIP; 84 | 85 | 86 | const int ReservParam[16]; /* резерв параметры */ 87 | 88 | int status; 89 | int sts; /* состояние */ 90 | int sts_next; /* состояние на следующий такт */ 91 | 92 | #if defined(ARDUINO_ARCH_ESP8266) 93 | SmartDevice(void):mark(FLASH_MARK), size (FLASH_WRITESIZE0), 94 | BiosCode(MASTER_BIOSCODE),Vers(MASTER_VERSION), 95 | SubVers(MASTER_SUBVERSION), SubVers1(MASTER_SUBVERSION1), 96 | ReservParam() 97 | #elif defined(ARDUINO_ARCH_ESP32) 98 | SmartDevice(void):mark(FLASH_MARK), size (FLASH_WRITESIZE0), 99 | BiosCode(MASTER_BIOSCODE),Vers(MASTER_VERSION), 100 | SubVers(MASTER_SUBVERSION), 101 | SubVers1(MASTER_SUBVERSION1), 102 | BiosDate(__DATE__), 103 | ReservParam() 104 | #endif 105 | 106 | { // int i; 107 | sts = sts_next = 0; 108 | UDPserver_port = 0; 109 | UDPserver_repot_period = 0; 110 | UDPserver_sts = 0; 111 | UDPserver_t = 0; 112 | TCPserver_port = 0; 113 | TCPserver_report_period = 0; 114 | TCPserver_sts = 0; 115 | TCPserver_sts2 = 0; 116 | TCPserver_rc = 0; 117 | Use_remoteTCPserver = 0; 118 | TCPserver_t = 0; 119 | TZoffset = 3600*3; //MSK-3 120 | status = -1; 121 | Mac[0]= Mac[1] = Mac[2] =Mac[3] =Mac[4] =Mac[5] = 0; 122 | IdNumber = 0; 123 | ClientId = ClientId_k = 0; 124 | 125 | } 126 | 127 | void callback_HandShake( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 128 | void callback_Echo( U16 len, U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 129 | void callback_Identify( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 130 | void callback_gettime( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 131 | void callback_settime( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 132 | void callback_set_udp_server( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 133 | virtual void callback_set_tcp_server( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 134 | {}; 135 | 136 | int servercallback_HandShake( U8 *bf, int len); 137 | int server_send_HandShake(unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)); 138 | virtual int server_answer_IdentifySelf( U8 *bf, int len) 139 | { return 0; }; 140 | 141 | virtual int servercallback_Get_Sts( U8 *bf, int len, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 142 | { return 0; }; 143 | virtual int servercallback_send_Sts_answ( U8 *bf, int len) 144 | { return 0; }; 145 | 146 | // virtual void servercallback_GetOtInfo( U8 *bf, int len) 147 | // { }; 148 | 149 | // virtual void udp_OpenThermInfo( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 150 | // {}; 151 | virtual void callback_getdata( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 152 | { }; 153 | 154 | virtual void callback_testcmd( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 155 | { }; 156 | virtual void callback_testcmdanswer( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 157 | { }; 158 | virtual int callback_Get_OpenThermInfo( U8 *bf, int len, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 159 | { return 0; }; 160 | virtual void callback_Set_OpenThermData( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 161 | { }; 162 | virtual void callback_Set_State( U8 *bf, int len, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 163 | { }; 164 | virtual int callback_Get_Capabilities( U8 *bf, int len, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 165 | { return 0; }; 166 | 167 | 168 | #if OT_DEBUGLOG 169 | virtual void callback_GetOTLog( U8 *bf, PACKED unsigned char * &MsgOut,int &Lsend, U8 *(*get_buf) (U16 size)) 170 | { }; 171 | virtual int server_answerOTLog( U8 *bf, int len) 172 | { return 0; }; 173 | 174 | #endif 175 | 176 | }; 177 | #pragma pack() 178 | 179 | #endif //SMARTDEVICE 180 | -------------------------------------------------------------------------------- /src/Smart_Config.h: -------------------------------------------------------------------------------- 1 | /* Smart_Config.h */ 2 | #ifndef SMART_CONFIG 3 | #define SMART_CONFIG 4 | 5 | #include "DeviceType.h" 6 | 7 | #define CONFIG_VERSIONBASE 0x1003 8 | 9 | //ST_VERS 0 SmartTherm32 10 | //ST_VERS 1 SmartTherm = SmartTherm32 + Relay 11 | //ST_VERS 2 SmartTherm 2 = SmartTherm32 + Relay + 2 OpenTherm (master+slave) 12 | #define ST_VERS 0 13 | 14 | #define SERIAL_DEBUG 0 15 | #define OT_DEBUG 0 16 | #define SERVER_DEBUG 0 17 | #define T_DEBUG 0 18 | 19 | #define OT_DEBUGLOG 1 //default 20 | 21 | #if ST_VERS == 0 22 | #if defined(ARDUINO_ARCH_ESP8266) 23 | #define MQTT_USE 0 24 | #else 25 | #define MQTT_USE 1 26 | #endif 27 | #define RELAY_USE 0 28 | #define CONFIG_VERSION CONFIG_VERSIONBASE 29 | #elif ST_VERS == 1 30 | #define MQTT_USE 1 31 | #define RELAY_USE 1 32 | #define CONFIG_VERSION (CONFIG_VERSIONBASE|0x4000) 33 | #elif ST_VERS == 2 34 | #define MQTT_USE 1 35 | #define RELAY_USE 1 36 | #define CONFIG_VERSION (CONFIG_VERSIONBASE|0x8000) 37 | #define OT_SLAVE_DEBUG 0 38 | #else 39 | error 40 | #endif 41 | 42 | 43 | #if MQTT_USE 44 | #define PID_USE 1 45 | #else 46 | #define PID_USE 0 47 | #endif 48 | 49 | /* Min & max CH temp */ 50 | #define MIN_CH_TEMP 25 51 | #define MAX_CH_TEMP 80 52 | 53 | /* Room setpoint Min & max */ 54 | #define MIN_ROOM_TEMP 5 55 | #define MAX_ROOM_TEMP 35 56 | 57 | #define MAX_PID_SRC 4 58 | 59 | #define IDENTIFY_TYPE DS_OPENTHERM 60 | #define IDENTIFY_SUBTYPE ST_VERS 61 | /* TCP/UDP buffer size in bytes */ 62 | #define UDP_TSP_BUFSIZE 128 63 | 64 | //есть датчик температуры 65 | #define USE_SENSOR_T 1 66 | 67 | #ifndef PROSESSOR_CODE 68 | #if defined(ARDUINO_ARCH_ESP8266) 69 | #define PROSESSOR_CODE 1 70 | #define IDENTIFY_TEXT F("Умный контроллер SmartTherm ESP8266") 71 | #elif defined(ARDUINO_ARCH_ESP32) 72 | #define PROSESSOR_CODE 2 73 | #if ST_VERS == 0 74 | #define IDENTIFY_TEXT F("Умный контроллер SmartTherm ESP32") 75 | #elif ST_VERS == 1 76 | #define IDENTIFY_TEXT F("Умный контроллер SmartTherm") 77 | #elif ST_VERS == 2 78 | #define IDENTIFY_TEXT F("Умный контроллер SmartTherm 2") 79 | #endif 80 | #endif 81 | 82 | #define IDENTIFY_CODE (PROSESSOR_CODE<<24)|(USE_SENSOR_T<<8) 83 | 84 | 85 | // AutoConnect menu title 86 | // Predefined parameters 87 | // SSID that Captive portal started. 88 | //remove warning on redefined 89 | #if defined(AUTOCONNECT_MENU_TITLE) 90 | #undef AUTOCONNECT_MENU_TITLE 91 | #endif 92 | #if defined(AUTOCONNECT_APID) 93 | #undef AUTOCONNECT_APID 94 | #endif 95 | 96 | #if defined(ARDUINO_ARCH_ESP8266) 97 | #define AUTOCONNECT_MENU_TITLE "SmartTherm ESP8266" 98 | #define AUTOCONNECT_APID "ST_ESP8266" 99 | #elif defined(ARDUINO_ARCH_ESP32) 100 | #define AUTOCONNECT_MENU_TITLE "SmartTherm" 101 | #if ST_VERS == 0 102 | #define AUTOCONNECT_APID "ST_ESP32" 103 | #elif ST_VERS == 1 104 | #define AUTOCONNECT_APID "ST" 105 | #elif ST_VERS == 2 106 | #define AUTOCONNECT_APID "ST2" 107 | #endif 108 | 109 | #else 110 | error not used in this config 111 | #endif // !ARDUINO_ARCH_ESP8266 112 | #endif // PROSESSOR_CODE 113 | 114 | #if defined(ARDUINO_ARCH_ESP8266) 115 | // #define LED_BUILTIN 2 116 | #elif defined(ARDUINO_ARCH_ESP32) 117 | #define LED_BUILTIN 2 118 | #endif 119 | 120 | #endif //SMART_CONFIG 121 | 122 | -------------------------------------------------------------------------------- /src/Smart_commands.h: -------------------------------------------------------------------------------- 1 | /* Smart_commands.h */ 2 | #ifndef SMART_COMMANDS 3 | #define SMART_COMMANDS 4 | 5 | /* команды мастеру */ 6 | #define HAND_SHAKE_INP "TCPiptEsT" 7 | #define HAND_SHAKE_OUT "ipTCPTeSt" 8 | #define HAND_SHAKE_ERR "NoOkError" 9 | 10 | #define MCMD_HAND_SHAKE 0x2020 11 | #define MCMD_ECHO 0x01 12 | #define MCMD_IDENTIFY 0x80 //запрос у сервера 13 | #define MCMD_INTRODUCESELF 0x81 //передача данных о себе серверу 14 | 15 | #define MCMD_GETINFO 0x02 //запрос информации 16 | #define MCMD_VIRT_UART 0x03 //виртуальный уарт 17 | #define MCMD_GETCONFIG 0x04 //запрос информации о конфигурации 18 | #define MCMD_DATA_FROM 0x05 //данные от станции 19 | #define MCMD_DATA_TO 0x06 //передать данные/команду станции 20 | #define MCMD_SETCONFIG 0x07 //установить информацию о конфигурации 21 | #define MCMD_SETDEVUPTIME 0x08 //установить информацию об аптайме и количестве просыпаний устройства 22 | #define MCMD_DATA 0x09 //передать данные (отладка) 23 | 24 | #define MCMD_GETTIME 0x10 // читать/задать время RTC 25 | #define MCMD_SETTIME 0x11 // читать/задать время RTC 26 | #define MCMD_GETDATA 0x12 //получить данные 27 | #define MCMD_TESTCMD 0x13 28 | #define MCMD_TESTCMDANSWER 0x14 29 | #define MCMD_SET_UDPSERVER 0x20 // задать UDP сервер, порт и время обновления информации 30 | #define MCMD_OT_INFO 0x21 //Open Therm info 31 | #define MCMD_SET_TCPSERVER 0x22 // задать TCP сервер, порт и время обновления информации 32 | #define MCMD_GET_OT_INFO 0x23 // get Open Therm info 33 | #define MCMD_SET_OT_DATA 0x24 // set Open Therm data 34 | #define MCMD_OT_DEBUG 0x30 // set/get Open Therm debug 0/1 35 | #define MCMD_GET_CAP 0x31 // get capabilities 36 | 37 | #define MCMD_GETLOG 0x15 //запрос лога 38 | #define MCMD_LOGON 0x16 // лог вкючить-выключить 39 | #define MCMD_GET_PAR 0x17 // 40 | #define MCMD_GET_ADC 0x18 // 41 | 42 | #define SCMD_GET_STS 0x30 // get controller sts (server ask controller) 43 | #define SCMD_GET_HAND_SHAKE 0x31 // server ask handhake 44 | #define CCMD_SEND_STS_S 0x40 // controller send sts (controller send server) 45 | #define ACMD_ASK_STS_S 0x50 // applcation ask is controller known (applcation ask server) 46 | #define ACMD_GET_STS_S 0x51 // applcation get controller sts (applcation ask server) 47 | 48 | #define ACMD_SET_STATE_C 0x52 // applcation set controller state (applcation send to controller) 49 | #define ACMD_SET_STATE_S 0x53 // applcation set controller state via server (applcation send to server) 50 | #define SCMD_SET_STATE_C 0x54 // server set controller state (server send to controller) 51 | 52 | #define CCMD_SEND_OTLOG_S 0x55 // controller send server OT log (controller send to server) 53 | #define SCMD_SEND_OTLOG_C 0x56 // server send answer to controller OT log (server send to controller) 54 | 55 | 56 | 57 | #endif //SMART_COMMANDS 58 | 59 | -------------------------------------------------------------------------------- /src/UDP_TCP.cpp: -------------------------------------------------------------------------------- 1 | /* UDP_TCP.cpp */ 2 | 3 | #include 4 | 5 | #if defined(ARDUINO_ARCH_ESP8266) 6 | #include 7 | #include 8 | typedef ESP8266WebServer WEBServer; 9 | #elif defined(ARDUINO_ARCH_ESP32) 10 | #include 11 | #include 12 | typedef WebServer WEBServer; 13 | #endif 14 | #include 15 | 16 | #include "SmartDevice.hpp" 17 | #include "Smart_commands.h" 18 | #include "As_TCP.h" 19 | 20 | /************************************/ 21 | 22 | void setup_tcpudp(SmartDevice *psd); 23 | void loop_udp(int sts); 24 | void loop_tcp(int sts); 25 | void loop_servertcp(void); 26 | 27 | int net_callback(U8 *bf, int bflen, PACKED unsigned char * &bt, int &btlen, int btmax, U8 *(*get_buf) (U16 size)); 28 | int net_ServerCallback(U8 *bf, int len, PACKED unsigned char * &MsgOut, int &Lsend, int btmax, U8 *(*get_buf) (U16 size)); 29 | 30 | U8 *esp_get_buf (U16 size); 31 | U8 *server_get_buf (U16 size); 32 | 33 | /************************************/ 34 | 35 | extern AutoConnectConfig config; 36 | extern AutoConnect portal; 37 | 38 | WiFiUDP Udp; 39 | WiFiClient tcp_client; 40 | unsigned int g_port = 6769; 41 | 42 | #if defined(ARDUINO_ARCH_ESP8266) 43 | WiFiServer server(g_port); 44 | WiFiClient client2; 45 | 46 | #elif defined(ARDUINO_ARCH_ESP32) 47 | WiFiServer server; 48 | #endif 49 | 50 | 51 | class SmartDevice *p_sd = NULL; 52 | 53 | static char buf_tcpudp_out[UDP_TSP_BUFSIZE]; 54 | static char buf_tcpserver_out[UDP_TSP_BUFSIZE]; 55 | static char tcpudp_incomingPacket[UDP_TSP_BUFSIZE]; 56 | static int tcpudp_incomingPacket_Len = 0; 57 | 58 | int TcpUdp_Lsend=0; 59 | int TcpServer_Lsend=0; 60 | int TCPserver_close_on_send = 0; 61 | 62 | int Udp_RemotePort = 0; 63 | int Tcp_RemotePort = 0; 64 | class As_TCP asTCP; 65 | class As_TCP asTCPserver; 66 | 67 | static int sts=0, raz=0; 68 | 69 | unsigned char *Udp_MsgOut=NULL; 70 | unsigned char *TCP_server_MsgOut=NULL; 71 | 72 | 73 | //функция, выделяющая память под буфер для отправки по UDP 74 | U8 *esp_get_buf (U16 size) 75 | { 76 | if(size < UDP_TSP_BUFSIZE) 77 | return (U8 *)buf_tcpudp_out; 78 | else 79 | return 0; 80 | } 81 | 82 | U8 *server_get_buf (U16 size) 83 | { 84 | if(size < UDP_TSP_BUFSIZE) 85 | return (U8 *)buf_tcpserver_out; 86 | else 87 | return 0; 88 | } 89 | 90 | 91 | 92 | void setup_tcpudp(SmartDevice *psd) 93 | { 94 | p_sd = psd; 95 | Udp.begin(g_port); 96 | server.begin(g_port); 97 | asTCP.id = 1; 98 | asTCPserver.id = 2; 99 | } 100 | 101 | int tcp_sts = 0; 102 | int t0 = 0; 103 | int t00 = 0; 104 | 105 | void loop_tcp(int sts) 106 | { static int count = 0; 107 | int rc, len; 108 | static int nb = 0; 109 | 110 | static int ols_sts=-1; 111 | if(tcp_sts != ols_sts) 112 | { 113 | #if SERIAL_DEBUG 114 | // Serial.printf("tcp_sts=%d\n", tcp_sts); 115 | #endif 116 | ols_sts = tcp_sts; 117 | } 118 | 119 | 120 | switch(tcp_sts) 121 | { 122 | case 0:// listen for incoming clients) 123 | //Next tcp_sts: 0/1 (sts=0) | 0/3 (sts=2) 124 | // Doc: Gets a client that is connected to the server and has data available for reading. 125 | // The connection persists when the returned client object goes out of scope 126 | // tcp_client = server.available(); //WiFiServer::available(uint8_t*)' is deprecated: Renamed to accept(). [-Wdeprecated-declarations] 127 | tcp_client = server.accept(); 128 | if(tcp_client) 129 | { t0 = millis(); 130 | tcp_client.setTimeout(5); 131 | nb = 0; 132 | tcp_sts++; 133 | // Serial.printf("%ld tcp client from %s to port %d\n", 134 | // millis(),tcp_client.remoteIP().toString().c_str(), tcp_client.localPort() ); 135 | 136 | } else if (sts == 2 && TcpUdp_Lsend > 0) { 137 | // Serial.printf("sts == 2 && TcpUdp_Lsend %d\n", TcpUdp_Lsend ); 138 | tcp_sts = 3; 139 | } 140 | break; 141 | 142 | case 1: 143 | //Next tcp_sts: 0/1/2 (sts=0) 144 | rc = tcp_client.available(); 145 | if(rc > 0) 146 | tcp_sts++; 147 | else 148 | count++; 149 | 150 | if(!tcp_client.connected() || millis() - t0 > 5000) 151 | { 152 | #if SERIAL_DEBUG 153 | if(!tcp_client.connected()) 154 | Serial.printf("(1)tcp_client disconnected at %d\n", count); 155 | else 156 | Serial.printf("(2)tcp_client disconnected at %d dt=%d\n", count, millis() - t0 ); 157 | #endif 158 | tcp_client.stop(); 159 | tcp_sts = 0; 160 | } else { 161 | delay(1); 162 | } 163 | break; 164 | 165 | case 2: 166 | rc = tcp_client.available(); 167 | if(rc > 0) 168 | { t0 = millis(); 169 | // Serial.printf("(1)tcp_client available rc %d\n", rc); 170 | #if SERIAL_DEBUG 171 | // if(nb == 0) 172 | // { Serial.printf("%ld tcp client from %s to port %d ", 173 | // millis(),tcp_client.remoteIP().toString().c_str(), tcp_client.localPort() ); 174 | // } 175 | #endif 176 | if(rc != nb) 177 | { // Serial.printf("%d available %d\n", millis(), rc); 178 | if(rc >= UDP_TSP_BUFSIZE) 179 | rc = UDP_TSP_BUFSIZE-1; 180 | len = tcp_client.readBytes(tcpudp_incomingPacket, rc); 181 | #if SERIAL_DEBUG 182 | if(nb == 0) 183 | ; 184 | // Serial.printf("readBytes %d\n", len); 185 | else 186 | Serial.printf("%ld +readBytes %d\n", millis(), len); 187 | #endif 188 | rc = net_callback((U8 *)tcpudp_incomingPacket, len, Udp_MsgOut, TcpUdp_Lsend, UDP_TSP_BUFSIZE, esp_get_buf); 189 | 190 | #if SERIAL_DEBUG 191 | // Serial.printf("%li net_callback rc %d, TcpUdp_Lsend=%d\n", millis(), rc, TcpUdp_Lsend) ; 192 | #endif 193 | if(rc == 0) 194 | {// Serial.printf("net_callback rc=%i l=%i\n", rc, TcpUdp_Lsend); 195 | // Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); 196 | tcp_client.write(Udp_MsgOut, TcpUdp_Lsend); 197 | TcpUdp_Lsend = 0; 198 | nb = 0; 199 | t0 = millis(); 200 | } 201 | } 202 | 203 | nb = rc; 204 | 205 | if(!tcp_client.connected()) 206 | { 207 | #if SERIAL_DEBUG 208 | Serial.printf("(3) tcp_client disconnected at %d\n", count); 209 | #endif 210 | tcp_client.stop(); 211 | tcp_sts = 0; 212 | } else { 213 | #if SERIAL_DEBUG 214 | // Serial.printf("."); 215 | #endif 216 | delay(1); 217 | } 218 | } else { 219 | // Serial.printf("(2)tcp_client available rc %d\n", rc); 220 | tcp_sts = 1; 221 | } 222 | break; 223 | 224 | case 3: 225 | //Next tcp_sts: 0/4 (sts=2) 226 | if(!p_sd->tcp_remoteIP) //Empty IP !!! 227 | { tcp_sts = 0; 228 | } else { 229 | #if SERIAL_DEBUG 230 | Serial.printf("send to IP: %s", p_sd->tcp_remoteIP.toString().c_str()); 231 | // Serial.println(p_sd->tcp_remoteIP.toString()); 232 | Serial.printf(" port %d bytes %d\n", p_sd->TCPserver_port, TcpUdp_Lsend); 233 | #endif 234 | t00 = millis(); 235 | rc = asTCP.connect_0(p_sd->tcp_remoteIP,p_sd->TCPserver_port,500); //todo 500 ->timeout 236 | if(rc == 1) 237 | { tcp_sts = 4; 238 | #if SERIAL_DEBUG 239 | Serial.printf("(%d) Ok connect_0 in %ld ms\n", asTCP.id, millis()-t00); 240 | #endif 241 | } else { 242 | #if SERIAL_DEBUG 243 | Serial.printf("(%d) Error connect_0 in %ld ms\n", asTCP.id, millis()-t00); 244 | #endif 245 | TcpUdp_Lsend = 0; 246 | tcp_sts = 0; 247 | } 248 | } 249 | break; 250 | 251 | case 4: 252 | //Next tcp_sts: 0/5 (sts=2) 253 | rc = asTCP.connect_a(); 254 | if(rc == 0) //wait 255 | { //Serial.printf("Wait connection\n"); 256 | } else if(rc == 1) { 257 | #if SERIAL_DEBUG 258 | Serial.printf("(%d) Establish a connection\n", asTCP.id); 259 | #endif 260 | tcp_sts = 5; 261 | } else { 262 | #if SERIAL_DEBUG 263 | Serial.printf("(%d) Cann't establish a connection\n", asTCP.id); 264 | #endif 265 | TcpUdp_Lsend = 0; 266 | tcp_sts = 0; 267 | asTCP.closeTCP(); 268 | 269 | } 270 | break; 271 | 272 | case 5: 273 | //Next tcp_sts: 6 (sts=2) 274 | #if defined(ARDUINO_ARCH_ESP8266) 275 | rc = client2.write(buf_tcpudp_out, TcpUdp_Lsend); 276 | #if SERIAL_DEBUG 277 | Serial.printf("client2.write rc = %d\n", rc); 278 | if (rc > 0) 279 | { int i; 280 | for(i=0; i 0) 315 | { tcp_sts = 8; 316 | tcpudp_incomingPacket_Len = rc; 317 | } else { 318 | tcp_sts = 0; 319 | asTCP.closeTCP(); 320 | } 321 | 322 | break; 323 | 324 | case 8: 325 | //Next tcp_sts: 0 (sts=2) 326 | #if SERIAL_DEBUG 327 | Serial.printf("case 8, time used %ld ms\n", millis()-t00); 328 | #endif 329 | tcp_sts = 0; 330 | asTCP.closeTCP(); 331 | break; 332 | 333 | } 334 | 335 | } 336 | 337 | void loop_udp(int sts) 338 | { int rc; 339 | // if (TcpUdp_Lsend > 0) 340 | // Serial.printf("udp send l=%i\n", TcpUdp_Lsend); 341 | 342 | int packetSize = Udp.parsePacket(); 343 | if (packetSize) 344 | { 345 | //Serial.printf("Received %d bytes from %s, port %d\n", packetSize, Udp.remoteIP().toString().c_str(), Udp.remotePort()); 346 | int len = Udp.read(tcpudp_incomingPacket, UDP_TSP_BUFSIZE-1); 347 | if (len > 0) 348 | { 349 | tcpudp_incomingPacket[len] = '\0'; 350 | } 351 | rc = net_callback((U8 *)tcpudp_incomingPacket, len, Udp_MsgOut, TcpUdp_Lsend, UDP_TSP_BUFSIZE, esp_get_buf); 352 | if(rc == 0) 353 | {// Serial.printf("net_callback rc=%i l=%i\n", rc, TcpUdp_Lsend); 354 | Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); 355 | Udp.write(Udp_MsgOut, TcpUdp_Lsend); 356 | TcpUdp_Lsend = 0; 357 | Udp.endPacket(); 358 | } 359 | } else if (sts && TcpUdp_Lsend > 0) { 360 | #if SERIAL_DEBUG 361 | Serial.printf("udp send l=%i bytes to %s port %d\n", TcpUdp_Lsend, p_sd->udp_remoteIP.toString().c_str(), Udp_RemotePort); 362 | #endif 363 | Udp.beginPacket( p_sd->udp_remoteIP, Udp_RemotePort); 364 | Udp.write(Udp_MsgOut, TcpUdp_Lsend); 365 | TcpUdp_Lsend = 0; 366 | Udp.endPacket(); 367 | } 368 | } 369 | 370 | 371 | /* bf - полученный буфер 372 | bflen - длина буфера 373 | MsgOut - буфер на отправку 374 | Lsend - длина буфера на отправку 375 | btmax - макс длина буфера на отправку 376 | get_buf - функция, выделяющая память под буфер для отправки 377 | */ 378 | int net_callback(U8 *bf, int len, PACKED unsigned char * &MsgOut, int &Lsend, int btmax, U8 *(*get_buf) (U16 size)) 379 | { short int cmd; 380 | unsigned short int par; 381 | static unsigned short lastInd=0; 382 | static unsigned int jj=0xffff, Nlost=0; 383 | 384 | 385 | cmd = *((short int *)&bf[2]); 386 | par = *((unsigned short int *)&bf[4]); 387 | if(par != (lastInd+1)) /* параметр всегда должен инкрементироваться, чтобы обеспечить защиту от повторных посылок */ 388 | { //if(par == lastInd) 389 | // isrep = 1; /* повторная посылка */ 390 | //else 391 | Nlost++; /* потеря данных */ 392 | } 393 | #if SERIAL_DEBUG 394 | // Serial.printf("net_callback cmd %i (%x) par %i (%x)\n", cmd, cmd, par, par); 395 | #endif 396 | lastInd = par; 397 | if(cmd & 0x8000) //тест обмена 398 | { Lsend = len; 399 | if(Lsend > 1400) Lsend = 1400; 400 | MsgOut = get_buf(Lsend); 401 | 402 | memcpy((void *)&MsgOut[0],(void *)bf,6); 403 | 404 | // *((PACKED int *) (&MsgOut[6])) = ++raz; 405 | ++raz; 406 | memcpy((void *)&MsgOut[6],(void *)&raz,4); 407 | 408 | *((PACKED short int *) (&MsgOut[10])) = p_sd->BiosCode; 409 | *((PACKED short int *) (&MsgOut[12])) = p_sd->Vers; 410 | *((PACKED short int *) (&MsgOut[14])) = p_sd->SubVers; 411 | *((PACKED short int *) (&MsgOut[16])) = 0; 412 | *((PACKED short int *) (&MsgOut[18])) = 0; 413 | 414 | *((PACKED int *) (&MsgOut[20])) = cmd; 415 | *((PACKED int *) (&MsgOut[24])) = par; 416 | *((PACKED int *) (&MsgOut[28])) = jj; 417 | *((PACKED int *) (&MsgOut[32])) = Nlost; 418 | memcpy((void *)&MsgOut[36],(void *)&p_sd->BiosDate,12); 419 | *((PACKED int *) (&MsgOut[48])) = sts; 420 | *((PACKED int *) (&MsgOut[52])) = lastInd; 421 | *((PACKED int *) (&MsgOut[56])) = MASTER_VERSION; 422 | 423 | MsgOut[60] = jj; 424 | MsgOut[61] = jj; 425 | MsgOut[62] = jj; 426 | MsgOut[63] = jj; 427 | MsgOut[64] = jj; 428 | MsgOut[65] = jj; 429 | MsgOut[66] = jj; 430 | MsgOut[67] = jj; 431 | MsgOut[68] = jj; 432 | MsgOut[69] = jj; 433 | lastInd = par; 434 | 435 | // Serial.printf("TestT end\n"); 436 | return (0); 437 | } 438 | switch(cmd) 439 | { case MCMD_HAND_SHAKE: 440 | p_sd->callback_HandShake(bf, MsgOut, Lsend,get_buf); 441 | break; 442 | 443 | case MCMD_ECHO: // эхо 444 | p_sd->callback_Echo(len,bf, MsgOut, Lsend,get_buf); 445 | break; 446 | 447 | case MCMD_IDENTIFY: // идентификация 448 | p_sd->callback_Identify(bf, MsgOut, Lsend,get_buf); 449 | break; 450 | case MCMD_GETTIME: 451 | p_sd->callback_gettime(bf, MsgOut, Lsend,get_buf); 452 | break; 453 | case MCMD_SETTIME: 454 | p_sd->callback_settime(bf, MsgOut, Lsend, get_buf); 455 | break; 456 | 457 | case MCMD_GETDATA: 458 | p_sd->callback_getdata(bf, MsgOut, Lsend, get_buf); 459 | // p_sd->callback_set_tcp_server(bf, MsgOut, Lsend, get_buf); 460 | break; 461 | 462 | case MCMD_TESTCMD: 463 | p_sd->callback_testcmd(bf, MsgOut, Lsend, get_buf); 464 | break; 465 | 466 | case MCMD_TESTCMDANSWER: 467 | p_sd->callback_testcmdanswer(bf, MsgOut, Lsend, get_buf); 468 | break; 469 | 470 | case MCMD_SET_UDPSERVER: 471 | p_sd->callback_set_udp_server(bf, MsgOut, Lsend, get_buf); 472 | p_sd->udp_remoteIP = Udp.remoteIP(); 473 | break; 474 | 475 | case MCMD_SET_TCPSERVER: 476 | p_sd->callback_set_tcp_server(bf, MsgOut, Lsend, get_buf); 477 | break; 478 | 479 | case MCMD_GET_OT_INFO: 480 | p_sd->callback_Get_OpenThermInfo(bf, len, MsgOut, Lsend, get_buf); 481 | break; 482 | 483 | case MCMD_SET_OT_DATA: 484 | p_sd->callback_Set_OpenThermData(bf, MsgOut, Lsend, get_buf); 485 | break; 486 | 487 | case ACMD_SET_STATE_C: 488 | p_sd->callback_Set_State(bf, len, MsgOut, Lsend, get_buf); 489 | break; 490 | 491 | case MCMD_GET_CAP: 492 | p_sd->callback_Get_Capabilities(bf,len, MsgOut, Lsend, get_buf); 493 | break; 494 | 495 | #if OT_DEBUGLOG 496 | case MCMD_OT_DEBUG: 497 | p_sd->callback_GetOTLog(bf, MsgOut, Lsend, get_buf); 498 | break; 499 | #endif 500 | 501 | default: 502 | #if SERIAL_DEBUG 503 | Serial.printf("net_callback Unknown cmd %i\n", cmd); 504 | #endif 505 | Lsend = 6+sizeof(int)+sizeof(short int); 506 | MsgOut = get_buf(Lsend); 507 | memcpy((void *)&MsgOut[0],(void *)&bf[0],6); 508 | *((PACKED short int *) (&MsgOut[0])) |= 0x8000; /* error: wrong cmd */ 509 | *((PACKED int *) (&MsgOut[6])) = cmd; /* wrong cmd */ 510 | break; 511 | } 512 | 513 | return (0); 514 | } 515 | /**********************************************************************/ 516 | int tcp_serversts = 0; 517 | unsigned long t00_server=0; 518 | 519 | void loop_servertcp(void) 520 | { static int count = 0; 521 | int rc, len; 522 | static int nb = 0; 523 | 524 | static int ols_sts=-1; 525 | if(tcp_serversts != ols_sts) 526 | { 527 | #if SERIAL_DEBUG 528 | // Serial.printf("tcp_serversts=%d\n", tcp_serversts); 529 | #endif 530 | ols_sts = tcp_serversts; 531 | } 532 | 533 | 534 | switch(tcp_serversts) 535 | { 536 | case 0: 537 | if (TcpServer_Lsend > 0) { 538 | tcp_serversts = 3; 539 | } 540 | break; 541 | 542 | case 3: 543 | //Next tcp_sts: 0/4 (sts=2) 544 | if(!p_sd->tcp_remoteIP) //Empty IP !!! 545 | { tcp_serversts = 0; 546 | TcpServer_Lsend = 0; 547 | } else { 548 | #if SERIAL_DEBUG 549 | // Serial.printf("send to server IP: %s", p_sd->tcp_remoteIP.toString().c_str()); 550 | // Serial.printf(" port %d bytes %d\n", p_sd->TCPserver_port, TcpServer_Lsend); 551 | #endif 552 | t00_server = millis(); 553 | rc = asTCPserver.connect_0(p_sd->tcp_remoteIP,p_sd->TCPserver_port,5000); //todo 500 ->timeout 554 | if(rc == 1) 555 | { tcp_serversts = 4; 556 | #if SERIAL_DEBUG 557 | // Serial.printf("server Ok connect_0 in %ld ms\n", millis()-t00_server); 558 | #endif 559 | } else { 560 | #if SERIAL_DEBUG 561 | // Serial.printf("server Error connect_0 in %ld ms\n", millis()-t00_server); 562 | #endif 563 | TcpServer_Lsend = 0; 564 | tcp_serversts = 0; 565 | } 566 | } 567 | break; 568 | 569 | case 4: 570 | //Next tcp_sts: 0/5 (sts=2) 571 | rc = asTCPserver.connect_a(); 572 | if(rc == 0) //wait 573 | { //Serial.printf("Wait connection\n"); 574 | } else if(rc == 1) { 575 | #if SERIAL_DEBUG 576 | // Serial.printf("Establish a connection to server\n"); 577 | #endif 578 | tcp_serversts = 5; 579 | } else { 580 | #if SERIAL_DEBUG 581 | // Serial.printf("Cann't establish a connection to server rc %d\n", rc); 582 | #endif 583 | TcpServer_Lsend = 0; 584 | tcp_serversts = 0; 585 | //asTCPserver.closeTCP(); 586 | //todo reset timeout in 587 | } 588 | break; 589 | 590 | case 5: 591 | if(TcpServer_Lsend == 0) 592 | { 593 | if(millis() - t00_server > 5000) 594 | { TcpServer_Lsend = 0; 595 | tcp_serversts = 0; 596 | asTCPserver.closeTCP(); 597 | } 598 | break; 599 | } 600 | #if defined(ARDUINO_ARCH_ESP32) 601 | rc = send(asTCPserver.sockfd, buf_tcpserver_out, TcpServer_Lsend, 0); 602 | #endif // 603 | 604 | TcpServer_Lsend = 0; 605 | if(TCPserver_close_on_send ) 606 | { tcp_serversts = 0; 607 | asTCPserver.closeTCP(); 608 | } else { 609 | tcp_serversts = 6; 610 | asTCPserver.read_0(); 611 | } 612 | break; 613 | 614 | case 6: 615 | //Next tcp_sts: 0/7 (sts=2) 616 | rc = asTCPserver.read_a(); 617 | if( rc == 0) //wait 618 | { 619 | } else if (rc == 1) { //read ready 620 | tcp_serversts = 7; 621 | } else { // -1 - error, 2 timeout 622 | tcp_serversts = 0; 623 | // asTCPserver.closeTCP(); 624 | } 625 | break; 626 | 627 | case 7: 628 | //Next tcp_sts: 0/8 (sts=2) 629 | rc = asTCPserver.Read(tcpudp_incomingPacket, sizeof(tcpudp_incomingPacket)); 630 | if(rc > 0) 631 | { tcp_serversts = 8; 632 | tcpudp_incomingPacket_Len = rc; 633 | } else { 634 | tcp_serversts = 0; 635 | asTCPserver.closeTCP(); 636 | } 637 | break; 638 | 639 | case 8: 640 | //Next tcp_sts: 0 (sts=2) 641 | //int net_ServerCallback(U8 *bf, int len, PACKED unsigned char * &MsgOut, int &Lsend, int btmax, U8 *(*get_buf) (U16 size)) 642 | 643 | rc = net_ServerCallback((U8 *)tcpudp_incomingPacket, tcpudp_incomingPacket_Len, TCP_server_MsgOut, TcpServer_Lsend, UDP_TSP_BUFSIZE, server_get_buf); 644 | if(rc == 0) 645 | { tcp_serversts = 0; 646 | asTCPserver.closeTCP(); 647 | } else { 648 | tcp_serversts = 5; 649 | } 650 | #if SERIAL_DEBUG 651 | // Serial.printf("case 8, time used %ld ms\n", millis()-t00_server); 652 | #endif 653 | break; 654 | 655 | 656 | } 657 | } 658 | 659 | /* bf - полученный буфер 660 | bflen - длина буфера 661 | MsgOut - буфер на отправку 662 | Lsend - длина буфера на отправку 663 | btmax - макс длина буфера на отправку 664 | get_buf - функция, выделяющая память под буфер для отправки 665 | 666 | rc = 0; Close TCP 667 | rc != 0; Not close TCP 668 | */ 669 | int net_ServerCallback(U8 *bf, int len, PACKED unsigned char * &MsgOut, int &Lsend, int btmax, U8 *(*get_buf) (U16 size)) 670 | { short int cmd; 671 | unsigned short int par; 672 | int rc = 0; 673 | static unsigned short lastInd=0; 674 | static unsigned int jj=0xffff, Nlost=0; 675 | 676 | 677 | cmd = *((short int *)&bf[2]); 678 | par = *((unsigned short int *)&bf[4]); 679 | if(par != (lastInd+1)) /* параметр всегда должен инкрементироваться, чтобы обеспечить защиту от повторных посылок */ 680 | { //if(par == lastInd) 681 | // isrep = 1; /* повторная посылка */ 682 | //else 683 | Nlost++; /* потеря данных */ 684 | } 685 | #if SERIAL_DEBUG 686 | // Serial.printf("net_ServerCallback cmd %x par %x len %d\n", cmd, par, len); 687 | #endif 688 | 689 | lastInd = par; 690 | switch(cmd) 691 | { case MCMD_HAND_SHAKE: 692 | rc = p_sd->servercallback_HandShake(bf, len); 693 | break; 694 | 695 | case SCMD_GET_STS: 696 | rc = p_sd->servercallback_Get_Sts(bf, len, MsgOut, Lsend, get_buf); 697 | // rc = p_sd->servercallback_GetOtInfo(bf, len); 698 | // 699 | break; 700 | 701 | case CCMD_SEND_STS_S: 702 | rc = p_sd->servercallback_send_Sts_answ(bf, len); 703 | // rc = p_sd->servercallback_GetOtInfo(bf, len); 704 | // 705 | break; 706 | 707 | case SCMD_GET_HAND_SHAKE: 708 | rc = p_sd->server_send_HandShake( MsgOut, Lsend, get_buf); 709 | 710 | break; 711 | 712 | case MCMD_INTRODUCESELF: 713 | rc = p_sd->server_answer_IdentifySelf( bf, len); 714 | 715 | break; 716 | #if OT_DEBUGLOG 717 | case SCMD_SEND_OTLOG_C: 718 | rc = p_sd->server_answerOTLog(bf,len); 719 | break; 720 | #endif 721 | 722 | // case MCMD_SET_TCPSERVER: 723 | // p_sd->callback_Get_OpenThermInfo(bf, MsgOut, Lsend, get_buf); 724 | // break; 725 | 726 | 727 | default: 728 | #if SERIAL_DEBUG 729 | Serial.printf("ERROR: net_ServerCallback: Unknown cmd %i\n", cmd); 730 | #endif 731 | Lsend = 6+sizeof(int)+sizeof(short int); 732 | MsgOut = get_buf(Lsend); 733 | memcpy((void *)&MsgOut[0],(void *)&bf[0],6); 734 | *((PACKED short int *) (&MsgOut[0])) |= 0x8000; /* error: wrong cmd */ 735 | *((PACKED int *) (&MsgOut[6])) = cmd; /* wrong cmd */ 736 | break; 737 | } 738 | 739 | return (rc); 740 | } 741 | /**********************************************************************/ 742 | 743 | /**********************************************************************/ 744 | 745 | #if defined(ARDUINO_ARCH_ESP8266) 746 | int As_TCP::connect_0(IPAddress ip, uint16_t port, int32_t __timeout) 747 | { int rc; 748 | // Serial.printf("TODO %s\n", __FUNCTION__); 749 | 750 | client2.setTimeout(_timeout); 751 | rc = client2.connect(ip, port); 752 | _timeout = __timeout; 753 | 754 | // Serial.printf("TODO %s\n", __FUNCTION__); 755 | // Serial.print(ip.toString()); 756 | // Serial.printf(" port %d\n", port); 757 | 758 | // Serial.printf("rc = %d\n", rc); 759 | return rc; 760 | } 761 | 762 | // -1 - error 763 | // 0 wait connect 764 | // 1 connect 765 | // 2 timeout 766 | 767 | int As_TCP::connect_a(void) 768 | { int rc; 769 | 770 | // Serial.printf("TODO %s\n", __FUNCTION__); 771 | 772 | rc = client2.connected(); 773 | 774 | return rc; 775 | } 776 | 777 | int As_TCP::read_0(void) 778 | { 779 | t0 = millis(); 780 | return 0; 781 | } 782 | 783 | // -1 - error 784 | // 0 wait read 785 | // 1 read ready 786 | // 2 timeout 787 | 788 | int As_TCP::read_a(void) 789 | { int rc; 790 | rc = client2.available(); 791 | if (rc == 0) 792 | { 793 | if(int(millis()-t0) > _timeout) 794 | { 795 | #if SERIAL_DEBUG 796 | Serial.printf("read_a returned due to timeout %d ms\n", timeout); 797 | #endif 798 | closeTCP(); 799 | rc = 2; 800 | } 801 | } 802 | 803 | return rc; 804 | } 805 | 806 | 807 | int As_TCP::Read(char bufin[], int len) 808 | { int rc=-1; 809 | #if SERIAL_DEBUG 810 | Serial.printf("TODO %s\n", __FUNCTION__); 811 | #endif 812 | return rc; 813 | } 814 | 815 | void As_TCP::closeTCP(void) 816 | { 817 | #if SERIAL_DEBUG 818 | Serial.printf("TODO %s\n", __FUNCTION__); 819 | #endif 820 | client2.stop(); 821 | 822 | } 823 | 824 | /**********************************************************************/ 825 | /**********************************************************************/ 826 | #elif defined(ARDUINO_ARCH_ESP32) 827 | int As_TCP::connect_0(IPAddress ip, uint16_t port, int32_t __timeout) 828 | { 829 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 830 | if (sockfd < 0) { 831 | log_e("socket: %d", errno); 832 | return 0; 833 | } 834 | fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); 835 | 836 | uint32_t ip_addr = ip; 837 | struct sockaddr_in serveraddr; 838 | memset((char *) &serveraddr, 0, sizeof(serveraddr)); 839 | serveraddr.sin_family = AF_INET; 840 | memcpy((void *)&serveraddr.sin_addr.s_addr, (const void *)(&ip_addr), 4); 841 | serveraddr.sin_port = htons(port); 842 | int res = connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); 843 | if (res < 0 && errno != EINPROGRESS) { 844 | #if SERIAL_DEBUG 845 | Serial.printf("(%d) connect on fd %d, errno: %d, \"%s\"", id, sockfd, errno, strerror(errno)); 846 | #endif 847 | closeTCP(); 848 | return 0; 849 | } 850 | _t0 = millis(); 851 | nraz = 0; 852 | _timeout = __timeout; 853 | return 1; 854 | } 855 | 856 | // -1 - error 857 | // 0 wait connect 858 | // 1 connect 859 | // 2 timeout 860 | 861 | int As_TCP::connect_a(void) 862 | { int res; 863 | 864 | FD_ZERO(&fdset); 865 | FD_SET(sockfd, &fdset); 866 | tv.tv_sec = 0; 867 | tv.tv_usec = 10000; // 10ms 868 | 869 | res = select(sockfd + 1, nullptr, &fdset, nullptr, &tv); 870 | if (res < 0) { 871 | #if SERIAL_DEBUG 872 | Serial.printf("(%d) select on fd %d, errno: %d, \"%s\"\n", id, sockfd, errno, strerror(errno)); 873 | #endif 874 | closeTCP(); 875 | return -1; 876 | } else if (res == 0) { 877 | if(millis() - _t0 > _timeout) 878 | { 879 | #if SERIAL_DEBUG 880 | // Serial.printf("(%d) connect_a returned due to timeout %d ms for fd %d, nraz %d\n", id, _timeout, sockfd, nraz); 881 | #endif 882 | closeTCP(); 883 | return 2; 884 | } 885 | nraz++; 886 | return 0; 887 | } else { 888 | int sockerr; 889 | socklen_t len = (socklen_t)sizeof(int); 890 | res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sockerr, &len); 891 | 892 | if (res < 0) { 893 | #if SERIAL_DEBUG 894 | Serial.printf("(%d) getsockopt on fd %d, errno: %d, \"%s\"\n", id, sockfd, errno, strerror(errno)); 895 | #endif 896 | closeTCP(); 897 | return -2; 898 | } 899 | 900 | if (sockerr != 0) { 901 | #if SERIAL_DEBUG 902 | Serial.printf("socket error on fd %d, errno: %d, \"%s\"\n", sockfd, sockerr, strerror(sockerr)); 903 | #endif 904 | closeTCP(); 905 | return -3; 906 | } 907 | } 908 | 909 | // fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) & (~O_NONBLOCK) ); 910 | // clientSocketHandle.reset(new WiFiClientSocketHandle(sockfd)); 911 | // _rxBuffer.reset(new WiFiClientRxBuffer(sockfd)); 912 | // _connected = true; 913 | return 1; 914 | } 915 | 916 | int As_TCP::read_0(void) 917 | { 918 | _t0 = millis(); 919 | return 0; 920 | } 921 | 922 | // -1 - error 923 | // 0 wait read 924 | // 1 read ready 925 | // 2 timeout 926 | 927 | int As_TCP::read_a(void) 928 | { int res; 929 | 930 | FD_ZERO(&fdset); 931 | FD_SET(sockfd, &fdset); 932 | tv.tv_sec = 0; 933 | tv.tv_usec = 10000; // 10ms 934 | 935 | #if SERIAL_DEBUG 936 | // Serial.printf("read_a select call %d %d\n", millis()-t0, t0 ); 937 | #endif 938 | res = select(sockfd + 1, &fdset, nullptr, nullptr, &tv); 939 | #if SERIAL_DEBUG 940 | // Serial.printf("read_a select rc=%d\n",res); 941 | #endif 942 | if (res < 0) 943 | { 944 | #if SERIAL_DEBUG 945 | Serial.printf("(%d) select on fd %d, errno: %d, \"%s\"\n", id, sockfd, errno, strerror(errno)); 946 | #endif 947 | closeTCP(); 948 | return -1; 949 | } else if (res == 0) { 950 | if(millis() - _t0 > _timeout) 951 | { 952 | #if SERIAL_DEBUG 953 | Serial.printf("(%d) read_a returned due to timeout %d ms for fd %d\n", id, _timeout, sockfd); 954 | #endif 955 | closeTCP(); 956 | return 2; 957 | } 958 | return 0; 959 | } 960 | return res; 961 | } 962 | 963 | 964 | int As_TCP::Read(char bufin[], int len) 965 | { int rc; 966 | socklen_t addr_len; 967 | struct sockaddr client; //адрес клиента 968 | 969 | addr_len = sizeof(struct sockaddr); 970 | 971 | rc = recvfrom(sockfd, bufin, len, 0, &client, &addr_len); 972 | if (rc < 0) 973 | { 974 | #if SERIAL_DEBUG 975 | Serial.printf("(%d) select on fd %d, errno: %d, \"%s\"\n", id, sockfd, errno, strerror(errno)); 976 | #endif 977 | closeTCP(); 978 | return -1; 979 | } 980 | #if SERIAL_DEBUG 981 | // Serial.printf("(%d) read %d bytes\n", id, rc); 982 | #endif 983 | return rc; 984 | } 985 | 986 | void As_TCP::closeTCP(void) 987 | { 988 | if(sockfd >= 0) 989 | { close(sockfd); 990 | sockfd = -1; 991 | } 992 | 993 | } 994 | 995 | #endif // 996 | /**********************************************************************/ 997 | -------------------------------------------------------------------------------- /src/myBuffer.cpp: -------------------------------------------------------------------------------- 1 | /* mybuffer.cpp */ 2 | #include 3 | #include 4 | 5 | #include "mybuffer.hpp" 6 | 7 | #if OT_DEBUGLOG 8 | 9 | /* отдаем под myBuffer lb байт по адресу pb */ 10 | void myBuffer::Init(char *pb, int Lb) 11 | { pbuf = pb; 12 | Lbuf = Lb; 13 | ibuf = ifree = 0; 14 | } 15 | 16 | /* отдаем под myBuffer lb байт по адресу pb, запоминаем длину элемента _Litem */ 17 | void myBuffer2::Init(void *pb, int Lb, int _Litem) 18 | { pbuf = (char *)pb; 19 | Litem = _Litem; 20 | Lbuf = Lb/Litem*Litem; // Lbuf выравнивается на длину элемента 21 | ibuf = ifree = 0; 22 | } 23 | 24 | /* добавить в myBuffer Lb байт с адреса pb */ 25 | int myBuffer::Add(char *pb, int Lb) 26 | { 27 | if(GetFree() <= Lb) 28 | return 0; 29 | 30 | if((ifree + Lb) < Lbuf) 31 | { memcpy((void *)&pbuf[ifree],(void *)pb ,Lb); 32 | ifree += Lb; 33 | } else { 34 | int k0, k1; 35 | k0 = ifree + Lb - Lbuf; 36 | k1 = Lb - k0; 37 | memcpy((void *)&pbuf[ifree],(void *)pb, k1 ); 38 | if(k0) 39 | memcpy((void *)&pbuf[0], (void *)&pb[k1], k0); 40 | ifree = k0; 41 | 42 | } 43 | return 1; 44 | } 45 | 46 | 47 | /* добавить в myBuffer один байт */ 48 | int myBuffer::Add(char byte) 49 | { 50 | if(GetFree() <= 1) 51 | return 0; 52 | pbuf[ifree] = byte; 53 | if((ifree + 1) < Lbuf) 54 | { ifree++; 55 | } else { 56 | ifree = 0; 57 | } 58 | return 1; 59 | } 60 | 61 | 62 | /* добавить в myBuffer2 один элемент */ 63 | int myBuffer2::Add(void *ptr) 64 | { int is_wrap = 0, l; 65 | l = GetFree(); 66 | if(l < Litem) 67 | return 0; 68 | if(l == Litem) 69 | is_wrap = 1; 70 | 71 | memcpy((void *)&pbuf[ifree], ptr, Litem); 72 | if((ifree + Litem) < Lbuf) 73 | { ifree +=Litem; 74 | } else { 75 | ifree = 0; 76 | } 77 | if(is_wrap) 78 | { if((ibuf + Litem) < Lbuf) 79 | { ibuf +=Litem; 80 | } else { 81 | ibuf = 0; 82 | } 83 | } 84 | return 1; 85 | } 86 | 87 | /* Вернуть элемент буфера и освободить место */ 88 | int myBuffer::Get(void) 89 | { int rc; 90 | if(GetLbuf() <= 0) 91 | return -1; 92 | rc = pbuf[ibuf]; 93 | ibuf++; 94 | if(ibuf == Lbuf) 95 | ibuf = 0; 96 | return rc; 97 | } 98 | 99 | /* Вернуть элемент буфера и освободить место */ 100 | int myBuffer2::Get(void *ptr) 101 | { 102 | if(GetLbuf() <= 0) 103 | return -1; 104 | memcpy(ptr,(void *)&pbuf[ibuf], Litem); 105 | ibuf += Litem; 106 | if(ibuf == Lbuf) 107 | ibuf = 0; 108 | return 0; 109 | } 110 | 111 | /* Вернуть элемент буфера и не освободить место */ 112 | /* порядок использования: 113 | N = GetLbuf() //сколько элементов в буфере 114 | StartRead(); //запоминаем ibuf2 115 | Read(); // читаем элемент и продвигаем позицию ibuf2 не более N раз 116 | EndRead(); // освобождаем место ibuf = ibuf2 117 | */ 118 | void myBuffer2::Read(void *ptr) 119 | { 120 | memcpy(ptr,(void *)&pbuf[ibuf2], Litem); 121 | ibuf2 += Litem; 122 | if(ibuf2 == Lbuf) 123 | ibuf2 = 0; 124 | } 125 | 126 | /**************************************************/ 127 | /* Вернуть элемент буфера и и не освободить место */ 128 | /* начать чтение */ 129 | void myBuffer::StartRead(void) 130 | { ibuf2 = ibuf; 131 | } 132 | /* закончить чтение и освободить место в буфере */ 133 | void myBuffer::EndRead(void) 134 | { 135 | ibuf = ibuf2; 136 | } 137 | 138 | int myBuffer::GetUnread(void) 139 | { int l, l2; 140 | /*******************/ 141 | l = ifree - ibuf; 142 | if(l < 0) l += Lbuf; // l = длина буфера 143 | if(l <= 0) 144 | return 0; // буфер пустой 145 | // ibuf...................ifree 146 | // ....|...ibuf2.........|..... 147 | l2 = ibuf2 - ibuf; 148 | if(l2 < 0) l2 += Lbuf; // l2 = длина прочитанной части буфера 149 | if(l2 >= l) 150 | return 0; // буфер прочитан 151 | return l - l2; 152 | } 153 | 154 | /* Собственно Вернуть элемент буфера и не освободить место */ 155 | int myBuffer::Read(char *el) 156 | { int l, l2; 157 | /*******************/ 158 | l = ifree - ibuf; 159 | if(l < 0) l += Lbuf; // l = длина буфера 160 | if(l <= 0) 161 | return -1; // буфер пустой 162 | // ibuf...................ifree 163 | // ....|...ibuf2.........|..... 164 | l2 = ibuf2 - ibuf; 165 | if(l2 < 0) l2 += Lbuf; // l2 = длина прочитанной части буфера 166 | if(l2 >= l) 167 | return -2; // буфер прочитан 168 | 169 | /******************/ 170 | *el = pbuf[ibuf2]; 171 | ibuf2++; 172 | if(ibuf2 == Lbuf) 173 | ibuf2 = 0; 174 | return 0; 175 | } 176 | 177 | /* Сколько свободно в буфере */ 178 | int myBuffer::GetFree(void) 179 | { 180 | return Lbuf - GetLbuf(); 181 | } 182 | 183 | /* Длина занятого буфера в char */ 184 | int myBuffer::GetLbuf(void) 185 | { int l; 186 | l = ifree - ibuf; 187 | if(l < 0) l += Lbuf; 188 | return l; 189 | } 190 | /* Длина занятого буфера в элементах */ 191 | int myBuffer2::GetLbuf(void) 192 | { int l; 193 | l = ifree - ibuf; 194 | if(l < 0) l += Lbuf; 195 | return l/Litem; 196 | } 197 | 198 | #endif //OT_DEBUGLOG 199 | -------------------------------------------------------------------------------- /src/mybuffer.hpp: -------------------------------------------------------------------------------- 1 | /* mybuffer.hpp */ 2 | #ifndef MYBUFFER 3 | #define MYBUFFER 4 | #include "Smart_Config.h" 5 | 6 | #if OT_DEBUGLOG 7 | class myBuffer 8 | { 9 | public: 10 | char *pbuf; // указатель на буфер 11 | int Lbuf; // длина буфера в байтах 12 | int ibuf; // индекс начала занятой области 13 | int ibuf2; // индекс начала прочитанной области 14 | int ifree; // индекс начала свободной области 15 | // int Litem; // длина элемента в int, 0=переменная длина элемента 16 | 17 | myBuffer(void) 18 | { pbuf = NULL; 19 | Lbuf = 0; 20 | ibuf = ibuf2 = ifree = 0; 21 | // Litem = 0; 22 | }; 23 | void Init(char *pb, int Lb); 24 | int Add(char byte); 25 | int Add(char *pb, int Lb); 26 | // int Add2(int *pb, int Lb); 27 | int Get(void); 28 | void StartRead(void); 29 | int GetUnread(void); 30 | int Read(char *el); 31 | void EndRead(void); 32 | int GetFree(void); 33 | int GetLbuf(void); 34 | }; 35 | 36 | class myBuffer2:public myBuffer 37 | { 38 | public: 39 | int Litem; // длина элемента в int, (???)0=переменная длина элемента 40 | 41 | myBuffer2(void) 42 | { Litem = 0; 43 | }; 44 | void Init(void *pb, int Lb, int _Litem); 45 | int Add(void *prt); 46 | int Get(void *prt); 47 | int GetLbuf(void); 48 | void Read(void *prt); 49 | }; 50 | 51 | #endif //OT_DEBUGLOG 52 | #endif // MYBUFFER 53 | -------------------------------------------------------------------------------- /src/pid.hpp: -------------------------------------------------------------------------------- 1 | /* pid.hpp */ 2 | #ifndef PID_DEFINED 3 | #define PID_DEFINED 4 | 5 | #if PID_USE 6 | 7 | /* циклический стек/буфер для хранения последних NB значений */ 8 | /* нужен для корректного расчета дифференциальной части PID */ 9 | #define NB 16 10 | class dstack 11 | { 12 | public: 13 | float d[NB]; 14 | unsigned long int t[NB]; 15 | int ind; 16 | int n; 17 | dstack(void) 18 | { int i; 19 | for(i=0; i= NB) ind = 0; 30 | if(n < NB) n++; 31 | } 32 | 33 | void get( float &_d, unsigned long int &_t) 34 | { int i; 35 | if( n < NB) 36 | { if(n == 0) 37 | { _d = 0.f; 38 | _t = 0; 39 | } else { 40 | _d = d[0]; 41 | _t = t[0]; 42 | } 43 | } else { 44 | i = ind; 45 | if(i >= NB) i = 0; 46 | _d = d[i]; 47 | _t = t[i]; 48 | } 49 | } 50 | 51 | int calcD(float _d, unsigned long int _t, float &diff); 52 | }; 53 | 54 | class TempStack:public dstack 55 | { 56 | public: 57 | // int nlast; 58 | // int ind_last; 59 | TempStack(void) 60 | { // nlast = ind_last = 0; 61 | 62 | } 63 | void add (float _d, unsigned long int _t) 64 | { 65 | dstack::add(_d, _t); 66 | #if 0 67 | if(nlast == 0) 68 | { ind_last = ind; 69 | dstack::add(_d, _t); 70 | // nlast++; 71 | } else { 72 | 73 | // Serial.printf("ind_last %d _d=%f _t=%li d[ind_last] =%f t[ind_last] %d\n", ind_last, _d, _t, d[ind_last] , t[ind_last] ); 74 | 75 | d[ind_last] = d[ind_last] + (_d - d[ind_last]) / float(nlast +1); 76 | t[ind_last] = t[ind_last] + (_t - t[ind_last]) / (nlast +1); 77 | nlast++; 78 | // Serial.printf("ind_last %d d[ind_last] =%f t[ind_last] %d %d\n", ind_last, d[ind_last] , t[ind_last] , nlast); 79 | if(nlast > 3) //4 раза считаем среднее 80 | nlast = 0; 81 | } 82 | #endif 83 | } 84 | }; 85 | 86 | /* PID регулятор */ 87 | class pid 88 | { 89 | public: 90 | float x; // управляемый сигнал (температура) 91 | float xTag; // целевое значение 92 | float u; // управляющий сигнал 93 | float ub; // Базовый управляющий сигнал 94 | float y; // параметр (внешняя температура) 95 | float xerr; 96 | float dP; 97 | float dD; 98 | float dI; 99 | float InT; 100 | 101 | int t_interval; //интервал времени цикла управления, сек 102 | float Kp; 103 | float Kd; 104 | float Ki; 105 | float Kidiss; // коэффициент диссипации интеграла 106 | 107 | float u0; //базовый управляющий сигнал при y = y0; 108 | float y0; 109 | float u1; //базовый управляющий сигнал при y = y1; 110 | float y1; 111 | 112 | long int pid_t; /* время начала такта */ 113 | TempStack dSt; 114 | // dstack dSt0; 115 | 116 | pid(void) 117 | { Kp = 1.; 118 | Kd = 0.2; 119 | Ki = 0.002; 120 | x = xTag = 0.; 121 | t_interval = 30; 122 | Kidiss = 0.005 * t_interval / 60.f; 123 | u0 = 40.; 124 | y0 = 10.; 125 | u1 = 80.; 126 | y1 = -30.; 127 | // u = u0 + (u1 - u0) * (y - y0)/(y1 - y0); 128 | x = xerr = 0.; 129 | dP = dD = dI = 0.; 130 | InT = 0; 131 | u = ub = 0; 132 | NextTact(); 133 | } 134 | void NextTact(void) 135 | { pid_t = millis(); 136 | } 137 | 138 | int Pid(float _x, float u0); 139 | void Init_I(float _x); 140 | void Init_I(float _dtag, float _xernew); 141 | void Set_NewTag( float _Tag, float _x); 142 | }; 143 | 144 | #endif //PID_USE 145 | #endif //PID_DEFINED --------------------------------------------------------------------------------