Думается мне что здесь проблема - вынеси обработку Web/MQTT из этого условия - должно реагировать получше. Мне приехали мои ESPшки - буду собирать свой ОТ контроллер, на этой же базе - в 2 головы глядишь и интереснее пойдет.
OldNavi, нет, не помогает. Весь код, что в условии, что за его пределми - 1сек. Как только комментирую ot.begin(handleInterrupt); сервера летают, датчик тоже, но ОТ не работает.
обработка прерываний не должна брать много - код под вопросмо что я вижу в библиотеке openterm - это sendRequest()
unsigned long OpenTherm::sendRequest(unsigned long request)
{
if (!sendRequestAync(request)) return 0;
while (!isReady()) {
process();
yield();
}
return response;
}
Закоментируй
ot.sendRequest(request); в loop() и посмотри на поведение, если залетает (но ОТ не будет работать)
то вижу несколько вариантов
1/ Воспользоваться processResponseCallback(). который передаешь в конструкторе и там добавлять обработку
2/ использовать sendRequestAsync() и прописать свой while(!isRead) {} цикл
Ну и видится проблема в том, что ты заспамил шину ОТ запросами (а запрос ответ может быть до 1сек согласно спецификации).
У тебя в кажом начале цикла есть - вызов setBolierStatus() который в в ответе уже дает много флагов статуса (есть ошибка или нет, включена горелка и т.д.) см спецификацию протокола
Дальше этот код можно наверное утащить в setup() и если что дергать при реконекте
// Записать ID-2; мастер-код MemberID
unsigned int data = 0x0004;
unsigned long request = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, data);
ot.sendRequest(request);
Которые внутри шлют новые запросы по ОТ, как пример
unsigned long getCentralHeatingEnabled() {
unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
if (responseStatus = OpenThermResponseStatus::SUCCESS)
return ot.isCentralHeatingActive(response);
}
ну и так далее... Вообщем ИМХО тут есть место для оптимизации ибо сам ОТ протокол ну очень медленный -
Bit rate : 1000 bits/sec nominal - по сути 15 сообщений максимум по ОТ за секунду (сообщение + ответ = 64 бита) - и каждый посыл блокирующий
ИМХО тут надо и цикл оптимизировать и разводить запросы по времени набирая полный набор параметров секунд за 10 - примерно как в tasmota сделано. Желание знать актуальную температуру каждую секунду похвально - но бессмыслено - она и раз за минуту не изменится. ну и тд и тп.
Сейчас на полный цикл обработки данных уходит 10-12 сек. Оригинальный код автора библиотеки http://ihormelnyk.com/mqtt_thermostat кушает на треть меньше. но там и данных меньше. Про код с ID2, что его можно в setup(), не прокатывает, а без него мой котёл только мониторится, а вот отправлять код каждые раз в 50 секунд вполне (тут надо покопаться в инфе, фигурируют цифры 40 и 20 сек.). Если котёл в течении этого времени не получит кода подтверждения из ID3 режим ОТ отключается, термостат только мониторит и то не всё. Другим котлам это по барабану. В любом случае сама библиотека ОТ вызывает вопросы.
Я полазил по коду библиотеки - он впринципе нормально реализован, из минусов он только работает нормально под core 2.3.0 - под 2.5.2 падает. Но не суть - проблема таже - sendRequest() это синхронный запрос и он может возвращать данные до 1 секунды. 2-я проблема - это спамминг шины ОТ - если читать спецификацию то следующий запрос можно делать не ранее чем через 100 милисекунд после последнего ответа. Думается мне, что я попробую реализацию через шедуллер с 2-мя тасками через https://www.arduinolibraries.info/libraries/esp8266-scheduler - в одном будет коммуникация с ОТ через библиотеку с таймингами, во второй таске уже MQTT и Вебсервак с блекджеком и б..ми. Но это пока не скоро - надо платку согласования еще распаять.
void loop(void) {
new_ts = millis();
if (new_ts - ts > 1000) {
unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2);
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
//=============================================================================================
// Оператор switch сравнивает значение переменной со значением, определенном в операторах case.
// Когда найден оператор case, значение которого равно значению переменной, выполняется
// программный код в этом операторе.
//=============================================================================================
int step = loop_counter++;
switch (step) {
case 0:
if (responseStatus == OpenThermResponseStatus::SUCCESS) {
//=======================================================================================
// Эта группа элементов данных определяет информацию о конфигурации как на ведомых, так
// и на главных сторонах. Каждый из них имеет группу флагов конфигурации (8 бит)
// и код MemberID (1 байт). Перед передачей информации об управлении и состоянии
// рекомендуется обмен сообщениями о допустимой конфигурации ведомого устройства
// чтения и основной конфигурации записи. Нулевой код MemberID означает клиентское
// неспецифическое устройство. Номер/тип версии продукта следует использовать в сочетании
// с "кодом идентификатора участника", который идентифицирует производителя устройства.
//=======================================================================================
unsigned long request3 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SConfigSMemberIDcode, 0xFFFF);
unsigned long respons3 = ot.sendRequest(request3);
uint8_t SlaveMemberIDcode = respons3 >> 0 & 0xFF;
unsigned long request2 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, SlaveMemberIDcode);
unsigned long respons2 = ot.sendRequest(request2);
unsigned long request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
unsigned long respons127 = ot.sendRequest(request127);
uint16_t dataValue127_type = respons127 & 0xFFFF;
uint8_t dataValue127_num = respons127 & 0xFF;
unsigned result127_type = dataValue127_type / 256;
unsigned result127_num = dataValue127_num;
unsigned long request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, 0x013F);
unsigned long respons126 = ot.sendRequest(request126);
uint16_t dataValue126_type = respons126 & 0xFFFF;
uint8_t dataValue126_num = respons126 & 0xFF;
unsigned result126_type = dataValue126_type / 256;
unsigned result126_num = dataValue126_num;
Serial.println("Тип и версия термостата: тип " + String(result126_type) + ", версия " + String(result126_num));
Serial.println("Тип и версия котла: тип " + String(result127_type) + ", версия " + String(result127_num));
}
break;
case 1:
if (responseStatus == OpenThermResponseStatus::SUCCESS) {
if (iv_mode == 1) {
op = pid(sp, pv, pv_last, ierr, dt);
ot.setBoilerTemperature(op); // Записываем заданную температуру СО, вычисляемую ПИД регулятором (переменная op)
}
if (iv_mode == 2) {
op = curve(temp_n);
ot.setBoilerTemperature(op); // Записываем заданную температуру СО, вычисляемую кривыми
}
if (iv_mode == 3) {
op = curve(temp_n, temp_k, temp_t);
ot.setBoilerTemperature(op); // Записываем заданную температуру СО, вычисляемую кривыми с учётом температуры в помещении
}
break;
}
case 2:
if (responseStatus == OpenThermResponseStatus::SUCCESS) {
ot.setDHWTemperature(spdhw); // Записываем заданную температуру ГВС
}
break;
case 3:
if (responseStatus == OpenThermResponseStatus::SUCCESS) {
ot.setMaxCHTemperature(spmaxch); // Записываем максимальную температуру СО
}
break;
default:
loop_counter = 0; // Начинаем с шага 0
return;
}
***********
}
Переделал часть кода, теперь котёл сам запрашивает подтверждения от термостата, точнее он и раньше это делал, но ответы валились каждую секунду, теперь раз в 45 сек. и ОТ не отваливается. То же справедливо и для уставок, запись идёт только когда есть запрос на это. Полагаю, что и остальные запросы на чтение ОТ надо поместить в case, решит проюлему спаминга.
Вроде добился, что контекст псевдо 2-х поточного кода заработал. У меня теперь веб сервер отвечает в течении порядка 100ms - т.е практически сразу. Заодно пришил и конфигуратор и OTA.
О_О. Поимел сегодня секса с этой библиотекой opentherm_library. Выяснилась такая бяка - если использовать SPIFFS для сохранения конфига и в это время прилетает прерывание от ОТ адаптера - все падает в корку. После долго дебага и гугления с рашифровкой стек трейса выяснилась собственно простая вещь - при записи файла SPIFFS отключает флеш от основного адресного пространства и соответственно когда прилетает прерывание у обработчики и вызываевых им функций не стоит атрибута ICACHE_RAM_ATTR и соответственно инструкция пытается выполнится из флеша (а его то уже тютю) и мы падаем в эксепшен. Соответственно пришлось поправить библиотеку в OpenTherm.cpp добавив необходимые аттрибуты и все перестало падать. Кому надо берите исправленную библиотеку здесь - https://github.com/OldNavi/opentherm_library.git. Pull Requst в основной репозиторий я сделал - но хз приет его автор или нет, посмотрим.
Да, эту информацию будет давать Home Assistant. Кстати вопрос такой - а с другими параметрами - например ID=54 Room Temperature - не пробовали ? Может БАКСИ сам будет все считать, есть у меня подозрение что он внутри себя все может делать и не надо всякие PID прикручивать.
По внешнему подключению температурного датчика думал, у меня вся автоматика на modbus сделана и датчиков в доме штук 5. Пока не стал заморачиваться...
Нет 54-го. К сожалению возможности Слима ограничены, даже модуляцию не выдаёт, по сему АЦП задействовал. Вот можно глянуть какие ID в Бакси Слим рулятся http://otgw.tclcode.com/matrix.cgi#boilers Кстати, в моей прошивке по ссылке выше, есть ещё 2 регулятора (кривые).
По внешнему подключению температурного датчика думал, у меня вся автоматика на modbus сделана и датчиков в доме штук 5. Пока не стал заморачиваться...
Нет 54-го. К сожалению возможности Слима ограничены, даже модуляцию не выдаёт, по сему АЦП задействовал. Вот можно глянуть какие ID в Бакси Слим рулятся http://otgw.tclcode.com/matrix.cgi#boilers Кстати, в моей прошивке по ссылке выше, есть ещё 2 регулятора (кривые).
Я переехал с самодельной автоматики на HA (он модбас тоже поддерживает) - вещь оказалось собственно. А там написать автоматизацию которая с датчиков будет что то усредненное считать по температуре и слать через MQTT dв бакси - это как два байта об асфальт.
У меня так и было сделано, температура усреднялась от 4-х датчиков, в операторской панели Weintek MT6070iH, 7″ был сделан термостат, он всё вычислял и рулил Слимом в режиме вкл/выкл 3 года. Захотелось ОТ...
Нее, не отвечает засранец :-) Ну и ладно - и так обойдемся
Добавил обработку флагов ошибки (а не только кода OEM) -
----------- Конфигурация ---------------
Границы установок контура отопления = от 0 до 0
Границы установок контура ГВС = от 0 до 0
ГВС встроен = 0
Тип управления = Модуляция
ГВС = проточная
----------- Статусы ---------------
Ошибка котла = 0
Код ошибки = E0
Сервис требуется = нет
Удаленный сброс разрешен = нет
Ошибка низкого давления воды = нет
Ошибка по газу/огню = нет
Ошибка по тяге воздуха = нет
Перегрев теплоносителя = нет
ID 5 Request - 50000 Response - C0050000 Status: SUCCESS
ID 9 Request - 90000 Response - F0090000 Status: INVALID
ID 10 Request - A0000 Response - C00A3900 Status: SUCCESS
ID 11 Request - 800B0000 Response - C00B001A Status: SUCCESS
ID 12 Request - C0000 Response - F00C0000 Status: INVALID
ID 13 Request - 800D0000 Response - 700D0000 Status: INVALID
ID 15 Request - F0000 Response - F00F0000 Status: INVALID
ID 16 Request - 90101500 Response - 70100000 Status: INVALID
ID 17 Request - 110000 Response - 60110000 Status: INVALID
ID 18 Request - 120000 Response - F0120000 Status: INVALID
ID 19 Request - 80130000 Response - 70130000 Status: INVALID
ID 20 Request - 140000 Response - F0140000 Status: INVALID
ID 21 Request - 80150000 Response - 70150000 Status: INVALID
ID 22 Request - 80160000 Response - 70160000 Status: INVALID
ID 23 Request - 10171500 Response - F0170000 Status: INVALID
ID 24 Request - 10181500 Response - F0180000 Status: INVALID
ID 28 Request - 801C0000 Response - E01C0000 Status: INVALID
ID 33 Request - 210000 Response - F0210000 Status: INVALID
ID 58 Request - 3A0000 Response - F03A0000 Status: INVALID
ID 100 Request - 80640000 Response - 70640000 Status: INVALID
ID 115 Request - 80730000 Response - 70730000 Status: INVALID
ID 116 Request - 740000 Response - F0740000 Status: INVALID
ID 117 Request - 80750000 Response - 70750000 Status: INVALID
ID 118 Request - 80760000 Response - 70760000 Status: INVALID
ID 119 Request - 770000 Response - F0770000 Status: INVALID
ID 120 Request - 780000 Response - F0780000 Status: INVALID
ID 121 Request - 80790000 Response - 70790000 Status: INVALID
ID 122 Request - 807A0000 Response - 707A0000 Status: INVALID
ID 123 Request - 7B0000 Response - F07B0000 Status: INVALID
Зарефакторил код. Сделал универсальную обработку json - одну на mqtt и web и пришил кривые из твоего кода (не уверен что правильно) . Осталось нарисовать вед морду с javascript и положить в SPIFFS и будет готово.
По поводу ПИД и кривых. Путанница какая то, ваша переменная heat_temp_set это уставка для ID-1 котла, она же в моём коде OP, вычисляемая ПИД и кривыми. И не вижу уставки целевой комнатной температуры (в моём коде SP), участвует в вычислении и ПИД, и кривых. PV понятно, прилетает из вне и становится house_temp.
А уровень модуляции ID-17? В моём Бакси её нет. Я её вычисляю АЦП ЕСПешки из ШИМ с катушки модуляции котла.
enableCentralHeating управляется? Когда house_temp_compsenation = true и температура уставки в помещении heat_temp_set меньше чем текущая температура в помещении house_temp. В данном условии, false.
enableCentralHeating управляется - но я не хочу его вводить в ПИД или куда еще - отключая котел. Ведь в этом случае выключается циркуляция. У меня идея в том что при TCHset меньше чем нижняя граница - котел сам отключит пламя.
Да, постциркуляция вечная получается, но если enableCentralHeating = false котёл не отключается он в ждущем режиме, и постциркуляция в этом случае или 3 мин, или 4 часа как дип в плате управления выбран.
Интересно.... Даже очень.
А киньте рабочий вариант на почту. Проверю на себе без изменений.
Вот этот работает
#include <ESP8266WiFi.h> #include <PubSubClient.h> #include <WiFiClient.h> #include <OneWire.h> #include <DallasTemperature.h> #include <OpenTherm.h> //Входные и выходные контакты OpenTherm, подключены к 4 и 5 контактам платы const int inPin = 4; //D2 const int outPin = 5; //D1 #define ONE_WIRE_BUS 14 // D5 Data wire is connected to 14 pin on the OpenTherm Shield #define BUILTIN_LED 2 // D4 Встроенный LED #define EXTERNAL_LED_3 12 // D6 На внешний LED индикации работы СО #define EXTERNAL_LED 13 // D7 На внешний LED индикации работы ГВС #define EXTERNAL_LED_1 15 // D8 На внешний LED индикации работы горелки #define EXTERNAL_LED_2 0 // D3 На внешний LED индикации работы авария const char* ssid = "***"; const char* password = "***"; const char* mqtt_server = "*****"; const int mqtt_port = 12345; const char* mqtt_user = "*****"; const char* mqtt_password = "*****"; OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); OpenTherm ot(inPin, outPin); WiFiClient espClient; PubSubClient client(espClient); char buf[50]; float sp = 20, // точка отсчета комнатной температуры pv = 21, // текущая температура в комнате pv_last = 0, // предыдущая температура ierr = 0, // интегральная погрешность dt = 0, // время между измерениями op = 0, // выход ПИД контроллера spdhw = 50; // точка отсчета температуры горячей воды контура ГВС unsigned long ts = 0, new_ts = 0; // отметки времени unsigned int hex56 = (spdhw * 256 * 16 / 16); // точка отсчета температуры горячей воды контура ГВС, но в (HEX) unsigned int data126 = 0x013F; // тип и версия термостата (HEX) bool enableCentralHeating = true; bool enableHotWater = true; bool enableCooling = false; //=============================================================== // Обрабатываем внешние прерывания //=============================================================== void handleInterrupt() { ot.handleInterrupt(); } float getTemp() { return sensors.getTempCByIndex(0); } float getBoilerTemp() { return ot.getBoilerTemperature(); } float getDHWTemp() { unsigned long request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0); unsigned long respons26 = ot.sendRequest(request26); uint16_t dataValue26 = respons26 & 0xFFFF; float result26 = dataValue26 / 256; return result26; } float getOutsideTemp() { unsigned long request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0); unsigned long respons27 = ot.sendRequest(request27); uint16_t dataValue27 = respons27 & 0xFFFF; if (dataValue27 > 32768) { //negative float result27 = -(65536 - dataValue27) / 256; return result27; } else { //positive float result27 = dataValue27 / 256; return result27; } } unsigned long getFault() { unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); if (responseStatus = OpenThermResponseStatus::SUCCESS) return ot.isFault(response); } unsigned long getCentralHeatingEnabled() { unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); if (responseStatus = OpenThermResponseStatus::SUCCESS) return ot.isCentralHeatingActive(response); } unsigned long getHotWaterEnabled() { unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); if (responseStatus = OpenThermResponseStatus::SUCCESS) return ot.isHotWaterActive(response); } unsigned long getFlameOn() { unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); if (responseStatus = OpenThermResponseStatus::SUCCESS) return ot.isFlameOn(response); } float setDHWTemp() { unsigned long request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56); unsigned long respons56 = ot.sendRequest(request56); uint16_t dataValue56 = respons56 & 0xFFFF; float result56 = dataValue56 / 256; return result56; } unsigned int getFaultCode() { unsigned long request5 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0); unsigned long respons5 = ot.sendRequest(request5); uint8_t dataValue5 = respons5 & 0xFF; unsigned result5 = dataValue5; return result5; } //=============================================================== // Вычисляем коэффициенты ПИД регулятора //=============================================================== float pid(float sp, float pv, float pv_last, float& ierr, float dt) { float Kc = 10.0; // K / %Heater float tauI = 50.0; // sec float tauD = 1.0; // sec // ПИД коэффициенты float KP = Kc; float KI = Kc / tauI; float KD = Kc * tauD; // верхняя и нижняя границы уровня нагрева float ophi = 100; float oplo = 0; // вычислить ошибку float error = sp - pv; // calculate the integral error ierr = ierr + KI * error * dt; // вычислить производную измерения float dpv = (pv - pv_last) / dt; // рассчитать выход ПИД регулятора float P = KP * error; // пропорциональная составляющая float I = ierr; // интегральная составляющая float D = -KD * dpv; // дифференциальная составляющая float op = P + I + D; // защита от сброса if ((op < oplo) || (op > ophi)) { I = I - KI * error * dt; // выход регулятора, он же уставка для ID-1 (температура теплоносителя контура СО котла) op = max(oplo, min(ophi, op)); } ierr = I; Serial.println("Заданное значение температуры в помещении = " + String(sp) + " °C"); Serial.println("Текущее значение температуры в помещении = " + String(pv) + " °C"); Serial.println("Выхов ПИД регулятора = " + String(op)); //Serial.println("Время между измерениями = " + String(dt) + "; ПИД коэффициенты: П = " + String(P) + "; И = " + String(I) + "; Д = " + String(D)); return op; } //============================================================== // Подключаемся к сети WiFi //============================================================== void setup_wifi() { delay(10); // подключение к сети Wi-Fi Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); // Подключение к маршрутизатору Wi-Fi // дождаться соединения while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // если соединение успешно, показывает IP-адрес в мониторе Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } //============================================================== // Функция SETUP //============================================================== void setup() { pinMode(BUILTIN_LED, OUTPUT); pinMode(EXTERNAL_LED, OUTPUT); pinMode(EXTERNAL_LED_1, OUTPUT); pinMode(EXTERNAL_LED_2, OUTPUT); pinMode(EXTERNAL_LED_3, OUTPUT); digitalWrite(BUILTIN_LED, LOW); digitalWrite(EXTERNAL_LED, LOW); digitalWrite(EXTERNAL_LED_1, LOW); digitalWrite(EXTERNAL_LED_2, LOW); digitalWrite(EXTERNAL_LED_3, LOW); Serial.begin(115200); setup_wifi(); //============================================================== // Инициализация датчика температуры DS18B20 //============================================================== sensors.begin(); sensors.requestTemperatures(); sensors.setWaitForConversion(false); // Переключиться в асинхронный режим pv, pv_last = sensors.getTempCByIndex(0); ts = millis(); //============================================================== // Инициализация OpenTherm //============================================================== ot.begin(handleInterrupt); //============================================================== // Инициализация MQTT клиента //============================================================== client.setServer(mqtt_server, mqtt_port); // подключаемся к MQTT client.setCallback(callback); // функция получения топиков с брокера } void publish_temperature() { Serial.println("MQTT, Current Room Temperature, °C = " + String(pv)); String(pv).toCharArray(buf, 50); client.publish("pv", buf); } void publish_boilertemp() { Serial.println("MQTT, CH Temperature, °C = " + String(getBoilerTemp())); String(getBoilerTemp()).toCharArray(buf, 50); client.publish("cht", buf); } void publish_dhwtemp() { Serial.println("MQTT, DHW Temperature, °C = " + String(getDHWTemp())); String(getDHWTemp()).toCharArray(buf, 50); client.publish("dhwt", buf); } void publish_outtemp() { Serial.println("MQTT, Outside Temperature, °C = " + String(getOutsideTemp())); String(getOutsideTemp()).toCharArray(buf, 50); client.publish("outt", buf); } void publish_statusCH() { Serial.println("MQTT, Status Central Heating = " + String(getCentralHeatingEnabled())); String(getCentralHeatingEnabled()).toCharArray(buf, 50); client.publish("sch", buf); } void publish_statusDWH() { Serial.println("MQTT, Status Hot Water = " + String(getHotWaterEnabled())); String(getHotWaterEnabled()).toCharArray(buf, 50); client.publish("sdwh", buf); } void publish_statusFlame() { Serial.println("MQTT, Status Central Heating = " + String(getFlameOn())); String(getFlameOn()).toCharArray(buf, 50); client.publish("sfl", buf); } void publish_setBoilerTemperature() { Serial.println("MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = " + String(op)); String(op).toCharArray(buf, 50); client.publish("op", buf); } // функция обратного вызова, чтение топиков void callback(char* topic, byte* payload, unsigned int length) { payload[length] = '\0'; String strTopic = String(topic); String strPayload = String((char*)payload); if (strTopic == "sp") { sp = strPayload.toFloat(); } if (strTopic == "spdhw") { spdhw = strPayload.toFloat(); } Serial.println("MQTT,topic sp = " + String(sp)); Serial.println("MQTT,topic spdhw = " + String(spdhw)); } void reconnect() { while (!client.connected()) { Serial.print("Attempting MQTT connection..."); // Attempt to connect if (client.connect("ESP8266Client", mqtt_user, mqtt_password)) { Serial.println("connected"); // после подключения публикуем объявление... publish_temperature(); publish_boilertemp(); publish_dhwtemp(); publish_outtemp(); publish_statusCH(); publish_statusDWH(); publish_statusFlame(); publish_setBoilerTemperature(); // ... и перезаписываем client.subscribe("sp"); client.subscribe("spdhw"); } else { Serial.print("failed, rc ="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); // Подождать 5 сек. перед повторной попыткой delay(5000); } } } //============================================================== // Функция LOOP //============================================================== void loop() { new_ts = millis(); if (new_ts - ts > 1000) { unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); if (responseStatus = OpenThermResponseStatus::SUCCESS) { //Serial.println("Error: Invalid boiler response " + String(response, HEX)); } if (responseStatus == OpenThermResponseStatus::NONE) { Serial.println("Error: OpenTherm is not initialized"); } else if (responseStatus == OpenThermResponseStatus::INVALID) { Serial.println("Error: Invalid response " + String(response, HEX)); } else if (responseStatus == OpenThermResponseStatus::TIMEOUT) { Serial.println("Error: Response timeout"); } pv = sensors.getTempCByIndex(0); dt = (new_ts - ts) / 1000.0; ts = new_ts; if (responseStatus == OpenThermResponseStatus::SUCCESS) { op = pid(sp, pv, pv_last, ierr, dt); // Заданная температура СО ot.setBoilerTemperature(op); // Заданная температура ГВС hex56 = (spdhw * 256 * 16 / 16); unsigned int setDHWTemp(hex56); pv_last = pv; sensors.requestTemperatures(); //Асинхронный запрос температуры } // Записать ID-2; мастер-код MemberID unsigned int data = 0x0004; unsigned long request = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, data); ot.sendRequest(request); // Выводим в монитор Serial.println("Текущий статус системы отопления: " + String(ot.isCentralHeatingActive(response) ? "on" : "off")); Serial.println("Текущий статус горячей воды: " + String(ot.isHotWaterActive(response) ? "on" : "off")); Serial.println("Текущий статус горелки: " + String(ot.isFlameOn(response) ? "on" : "off")); Serial.println("Уставка температуры ГВС = " + String(setDHWTemp()) + " °C"); Serial.println("Текущая температура контура ГВС = " + String(getDHWTemp()) + " °C"); Serial.println("Текущая температура контура СО = " + String(getBoilerTemp()) + " °C"); Serial.println("Температура на улице = " + String(getOutsideTemp()) + " °C"); Serial.println("Индикация состояния неисправности: " + String(ot.isFault(response) ? "fault" : "no fault")); // обработка включения СО. if (pv >= sp) { enableCentralHeating = false; } else { enableCentralHeating = true; } // обработка индикации состояния СО. if (getCentralHeatingEnabled() == true) { digitalWrite(EXTERNAL_LED_3, HIGH); } else { digitalWrite(EXTERNAL_LED_3, LOW); } // обработка индикации состояния ГВС. if (getHotWaterEnabled() == true) { digitalWrite(EXTERNAL_LED, HIGH); } else { digitalWrite(EXTERNAL_LED, LOW); } // обработка индикации состояния горелки. if (getFlameOn() == true) { digitalWrite(EXTERNAL_LED_1, HIGH); } else { digitalWrite(EXTERNAL_LED_1, LOW); } // обработка индикации состояния аварии, сигнал SOS. if (getFault() == true) { Serial.println("Код неисправности: E " + String(getFaultCode())); digitalWrite(EXTERNAL_LED_2, HIGH); } else { digitalWrite(EXTERNAL_LED_2, LOW); } publish_temperature(); publish_boilertemp(); publish_dhwtemp(); publish_outtemp(); publish_statusCH(); publish_statusDWH(); publish_statusFlame(); publish_setBoilerTemperature(); } // MQTT Loop if (!client.connected()) { reconnect(); } client.loop(); }void callback(char* topic, byte* payload, unsigned int length) { payload[length] = '\0'; String strTopic = String(topic); String strPayload = String((char*)payload); if (strTopic == "sp") { sp = strPayload.toFloat(); } if (strTopic == "spdhw") { spdhw = strPayload.toFloat(); } Serial.println("MQTT,topic sp = " + String(sp)); Serial.println("MQTT,topic spdhw = " + String(spdhw));Не работает функция обратного вызова, чтение топиков
Опять приветствую многоуважаемое сообщество. Возвращаюсь к началу поста и описываемой проблеме. Причина с медлительностью веб сервера в библиотеке
Точнее в setup
Весь код начинает работать с циклом в 1 сек. Особенность библиотеки такая. Возможно ли решить некую многозадачность?
306voidloop() {307new_ts = millis();308if(new_ts - ts > 1000) {Думается мне что здесь проблема - вынеси обработку Web/MQTT из этого условия - должно реагировать получше. Мне приехали мои ESPшки - буду собирать свой ОТ контроллер, на этой же базе - в 2 головы глядишь и интереснее пойдет.
OldNavi, нет, не помогает. Весь код, что в условии, что за его пределми - 1сек. Как только комментирую ot.begin(handleInterrupt); сервера летают, датчик тоже, но ОТ не работает.
обработка прерываний не должна брать много - код под вопросмо что я вижу в библиотеке openterm - это sendRequest()
unsigned long OpenTherm::sendRequest(unsigned long request) { if (!sendRequestAync(request)) return 0; while (!isReady()) { process(); yield(); } return response; }Закоментируй
ot.sendRequest(request); в loop() и посмотри на поведение, если залетает (но ОТ не будет работать)то вижу несколько вариантов1/ ВоспользоватьсяprocessResponseCallback(). который передаешь в конструкторе и там добавлять обработку2/ использовать sendRequestAsync() и прописать свой while(!isRead) {} цикл
3/ Посмотреть в сторону Scheduler https://www.arduino.cc/en/Reference/Scheduler
Залетает...и без запросов то же.
Ну и видится проблема в том, что ты заспамил шину ОТ запросами (а запрос ответ может быть до 1сек согласно спецификации).
У тебя в кажом начале цикла есть - вызов setBolierStatus() который в в ответе уже дает много флагов статуса (есть ошибка или нет, включена горелка и т.д.) см спецификацию протокола
Дальше этот код можно наверное утащить в setup() и если что дергать при реконекте
// Записать ID-2; мастер-код MemberID unsigned int data = 0x0004; unsigned long request = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, data); ot.sendRequest(request);Дальше в цикле ты публикуешь в MQ
publish_statusCH(); publish_statusDWH(); publish_statusFlame();Которые внутри шлют новые запросы по ОТ, как пример
unsigned long getCentralHeatingEnabled() { unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); if (responseStatus = OpenThermResponseStatus::SUCCESS) return ot.isCentralHeatingActive(response); }ну и так далее... Вообщем ИМХО тут есть место для оптимизации ибо сам ОТ протокол ну очень медленный -
Bit rate : 1000 bits/sec nominal - по сути 15 сообщений максимум по ОТ за секунду (сообщение + ответ = 64 бита) - и каждый посыл блокирующий
ИМХО тут надо и цикл оптимизировать и разводить запросы по времени набирая полный набор параметров секунд за 10 - примерно как в tasmota сделано. Желание знать актуальную температуру каждую секунду похвально - но бессмыслено - она и раз за минуту не изменится. ну и тд и тп.
Сейчас на полный цикл обработки данных уходит 10-12 сек. Оригинальный код автора библиотеки http://ihormelnyk.com/mqtt_thermostat кушает на треть меньше. но там и данных меньше. Про код с ID2, что его можно в setup(), не прокатывает, а без него мой котёл только мониторится, а вот отправлять код каждые раз в 50 секунд вполне (тут надо покопаться в инфе, фигурируют цифры 40 и 20 сек.). Если котёл в течении этого времени не получит кода подтверждения из ID3 режим ОТ отключается, термостат только мониторит и то не всё. Другим котлам это по барабану. В любом случае сама библиотека ОТ вызывает вопросы.
Я полазил по коду библиотеки - он впринципе нормально реализован, из минусов он только работает нормально под core 2.3.0 - под 2.5.2 падает. Но не суть - проблема таже - sendRequest() это синхронный запрос и он может возвращать данные до 1 секунды. 2-я проблема - это спамминг шины ОТ - если читать спецификацию то следующий запрос можно делать не ранее чем через 100 милисекунд после последнего ответа. Думается мне, что я попробую реализацию через шедуллер с 2-мя тасками через https://www.arduinolibraries.info/libraries/esp8266-scheduler - в одном будет коммуникация с ОТ через библиотеку с таймингами, во второй таске уже MQTT и Вебсервак с блекджеком и б..ми. Но это пока не скоро - надо платку согласования еще распаять.
void loop(void) { new_ts = millis(); if (new_ts - ts > 1000) { unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2); OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); //============================================================================================= // Оператор switch сравнивает значение переменной со значением, определенном в операторах case. // Когда найден оператор case, значение которого равно значению переменной, выполняется // программный код в этом операторе. //============================================================================================= int step = loop_counter++; switch (step) { case 0: if (responseStatus == OpenThermResponseStatus::SUCCESS) { //======================================================================================= // Эта группа элементов данных определяет информацию о конфигурации как на ведомых, так // и на главных сторонах. Каждый из них имеет группу флагов конфигурации (8 бит) // и код MemberID (1 байт). Перед передачей информации об управлении и состоянии // рекомендуется обмен сообщениями о допустимой конфигурации ведомого устройства // чтения и основной конфигурации записи. Нулевой код MemberID означает клиентское // неспецифическое устройство. Номер/тип версии продукта следует использовать в сочетании // с "кодом идентификатора участника", который идентифицирует производителя устройства. //======================================================================================= unsigned long request3 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SConfigSMemberIDcode, 0xFFFF); unsigned long respons3 = ot.sendRequest(request3); uint8_t SlaveMemberIDcode = respons3 >> 0 & 0xFF; unsigned long request2 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, SlaveMemberIDcode); unsigned long respons2 = ot.sendRequest(request2); unsigned long request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0); unsigned long respons127 = ot.sendRequest(request127); uint16_t dataValue127_type = respons127 & 0xFFFF; uint8_t dataValue127_num = respons127 & 0xFF; unsigned result127_type = dataValue127_type / 256; unsigned result127_num = dataValue127_num; unsigned long request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, 0x013F); unsigned long respons126 = ot.sendRequest(request126); uint16_t dataValue126_type = respons126 & 0xFFFF; uint8_t dataValue126_num = respons126 & 0xFF; unsigned result126_type = dataValue126_type / 256; unsigned result126_num = dataValue126_num; Serial.println("Тип и версия термостата: тип " + String(result126_type) + ", версия " + String(result126_num)); Serial.println("Тип и версия котла: тип " + String(result127_type) + ", версия " + String(result127_num)); } break; case 1: if (responseStatus == OpenThermResponseStatus::SUCCESS) { if (iv_mode == 1) { op = pid(sp, pv, pv_last, ierr, dt); ot.setBoilerTemperature(op); // Записываем заданную температуру СО, вычисляемую ПИД регулятором (переменная op) } if (iv_mode == 2) { op = curve(temp_n); ot.setBoilerTemperature(op); // Записываем заданную температуру СО, вычисляемую кривыми } if (iv_mode == 3) { op = curve(temp_n, temp_k, temp_t); ot.setBoilerTemperature(op); // Записываем заданную температуру СО, вычисляемую кривыми с учётом температуры в помещении } break; } case 2: if (responseStatus == OpenThermResponseStatus::SUCCESS) { ot.setDHWTemperature(spdhw); // Записываем заданную температуру ГВС } break; case 3: if (responseStatus == OpenThermResponseStatus::SUCCESS) { ot.setMaxCHTemperature(spmaxch); // Записываем максимальную температуру СО } break; default: loop_counter = 0; // Начинаем с шага 0 return; } *********** }Переделал часть кода, теперь котёл сам запрашивает подтверждения от термостата, точнее он и раньше это делал, но ответы валились каждую секунду, теперь раз в 45 сек. и ОТ не отваливается. То же справедливо и для уставок, запись идёт только когда есть запрос на это. Полагаю, что и остальные запросы на чтение ОТ надо поместить в case, решит проюлему спаминга.
Определённо сдвиг в лучшую сторону есть. Нужен совет. Как лучше организовать чтение переменных ОТ в одном case или каждую в отдельном?
ИМХО лучше в отдельном case
Сервер заработал :-)
Выложил бы полный код куданить на Гитхаб например.
Вроде добился, что контекст псевдо 2-х поточного кода заработал. У меня теперь веб сервер отвечает в течении порядка 100ms - т.е практически сразу. Заодно пришил и конфигуратор и OTA.
Всё же есть одна особенность. Обработка 3 и 2 ID должны быть в одном case.
Положил свою реализацию на основе async запросов и параллельных тасков на Гитхаб
https://github.com/OldNavi/OpenThermController.git
Пока реализовано
1/ WiFi manager - для конфига WiFI и MQTT
2/ Сохранение конфига при применении команд
3/ MQTT с JSON
4/ HTTP Server пока простой
5/ ОТА поддерживается
6/ Сброс бойлера через MQTT
Мой файл на https://yadi.sk/d/aigzaKc_TUQsnQ
О_О. Поимел сегодня секса с этой библиотекой opentherm_library. Выяснилась такая бяка - если использовать SPIFFS для сохранения конфига и в это время прилетает прерывание от ОТ адаптера - все падает в корку. После долго дебага и гугления с рашифровкой стек трейса выяснилась собственно простая вещь - при записи файла SPIFFS отключает флеш от основного адресного пространства и соответственно когда прилетает прерывание у обработчики и вызываевых им функций не стоит атрибута ICACHE_RAM_ATTR и соответственно инструкция пытается выполнится из флеша (а его то уже тютю) и мы падаем в эксепшен. Соответственно пришлось поправить библиотеку в OpenTherm.cpp добавив необходимые аттрибуты и все перестало падать. Кому надо берите исправленную библиотеку здесь - https://github.com/OldNavi/opentherm_library.git. Pull Requst в основной репозиторий я сделал - но хз приет его автор или нет, посмотрим.
Я библиотеку немного модифицировал, добавил к базовым ещё обработку нескольких ID.
Прикрутил EEPROM - но кторый rotating - так глядишь не быстро сдохнет espшка
Я свою год гоняю, до полсотни раз за день шью... Ничего, жива...
Гляжу в коде нет датчика температуры (помещения), с других устройств используете?
Да, эту информацию будет давать Home Assistant. Кстати вопрос такой - а с другими параметрами - например ID=54 Room Temperature - не пробовали ? Может БАКСИ сам будет все считать, есть у меня подозрение что он внутри себя все может делать и не надо всякие PID прикручивать.
По внешнему подключению температурного датчика думал, у меня вся автоматика на modbus сделана и датчиков в доме штук 5. Пока не стал заморачиваться...
Нет 54-го. К сожалению возможности Слима ограничены, даже модуляцию не выдаёт, по сему АЦП задействовал. Вот можно глянуть какие ID в Бакси Слим рулятся http://otgw.tclcode.com/matrix.cgi#boilers Кстати, в моей прошивке по ссылке выше, есть ещё 2 регулятора (кривые).
Нда, жалко - ну да пофиг, и так сделаем хорошо
По внешнему подключению температурного датчика думал, у меня вся автоматика на modbus сделана и датчиков в доме штук 5. Пока не стал заморачиваться...
Нет 54-го. К сожалению возможности Слима ограничены, даже модуляцию не выдаёт, по сему АЦП задействовал. Вот можно глянуть какие ID в Бакси Слим рулятся http://otgw.tclcode.com/matrix.cgi#boilers Кстати, в моей прошивке по ссылке выше, есть ещё 2 регулятора (кривые).
Я переехал с самодельной автоматики на HA (он модбас тоже поддерживает) - вещь оказалось собственно. А там написать автоматизацию которая с датчиков будет что то усредненное считать по температуре и слать через MQTT dв бакси - это как два байта об асфальт.
У меня так и было сделано, температура усреднялась от 4-х датчиков, в операторской панели Weintek MT6070iH, 7″ был сделан термостат, он всё вычислял и рулил Слимом в режиме вкл/выкл 3 года. Захотелось ОТ...
OldNavi, из строки 98 unsigned int val_in_hex = (val * 256 * 16); уберите деление на 16, лишнее...
По ссылке выше добавил модифицированную библиотеку.
float getDHWTemp() { unsigned long request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0); unsigned long respons26 = sendRequest(request26); return ot.temperatureToData(respons26); } float getOutsideTemp() { unsigned long request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0); return ot.getTemperature(sendRequest(request27)); } float setDHWTemp(float val) { unsigned long request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, ot.temperatureToData(val)); return ot.getTemperature(sendRequest(request56)); }Полагаю, так проще:
float getDHWTemp() { unsigned long response = sendRequest(ot.buildGetDHWTemperatureRequest()); return ot.getTemperature(response); } float getOutsideTemp() { unsigned long response = sendRequest(ot.buildGetOutsideTemperatureRequest()); return ot.getTemperature(response); } float setDHWTemp(float val) { unsigned long response = sendRequest(ot.buildSetDHWTemperatureSetpointRequest(val)); return ot.isValidResponse(response); }Это все я уже поправил, все теперь работает... Одну загадку разгадать не могу.
Если например котел уходит в ошибку - то температуру ГВС показывает как 133 градуса, че хрень пока не пойму
PS: Кстати мой PR в opentherm library - смерджили - можно обновить библиотеку.
Хм, это не температура, похоже на код ошибки, типа Е 133, но из ID-5 читаться должен (расшифровка = по моему блокировка газа).
Полагаю, ошибка здесь кроется:
if (responseStatus == OpenThermResponseStatus::SUCCESS && vars.isFault.value) { vars.dhw_temp.setValue(getFaultCode()); }Блин! Спасибо - совсем copy-past проглядел. Кстати, табличке http://otgw.tclcode.com/matrix.cgi#boilers
не доверяю, бакси отвечает на большее количество кодов - сейчас дамп пишу - попробую собрать статистику
Блин! Спасибо - совсем copy-past проглядел. Кстати, табличке http://otgw.tclcode.com/matrix.cgi#boilers
не доверяю, бакси отвечает на большее количество кодов - сейчас дамп пишу - попробую собрать статистику
Слим-Слимом, но пихают туда платы разных производителей...
Нее, не отвечает засранец :-) Ну и ладно - и так обойдемся
Добавил обработку флагов ошибки (а не только кода OEM) -
----------- Конфигурация ---------------
ID 5 Request - 50000 Response - C0050000 Status: SUCCESS
ID 9 Request - 90000 Response - F0090000 Status: INVALID
ID 10 Request - A0000 Response - C00A3900 Status: SUCCESS
ID 11 Request - 800B0000 Response - C00B001A Status: SUCCESS
ID 12 Request - C0000 Response - F00C0000 Status: INVALID
ID 13 Request - 800D0000 Response - 700D0000 Status: INVALID
ID 15 Request - F0000 Response - F00F0000 Status: INVALID
ID 16 Request - 90101500 Response - 70100000 Status: INVALID
ID 17 Request - 110000 Response - 60110000 Status: INVALID
ID 18 Request - 120000 Response - F0120000 Status: INVALID
ID 19 Request - 80130000 Response - 70130000 Status: INVALID
ID 20 Request - 140000 Response - F0140000 Status: INVALID
ID 21 Request - 80150000 Response - 70150000 Status: INVALID
ID 22 Request - 80160000 Response - 70160000 Status: INVALID
ID 23 Request - 10171500 Response - F0170000 Status: INVALID
ID 24 Request - 10181500 Response - F0180000 Status: INVALID
ID 28 Request - 801C0000 Response - E01C0000 Status: INVALID
ID 33 Request - 210000 Response - F0210000 Status: INVALID
ID 58 Request - 3A0000 Response - F03A0000 Status: INVALID
ID 100 Request - 80640000 Response - 70640000 Status: INVALID
ID 115 Request - 80730000 Response - 70730000 Status: INVALID
ID 116 Request - 740000 Response - F0740000 Status: INVALID
ID 117 Request - 80750000 Response - 70750000 Status: INVALID
ID 118 Request - 80760000 Response - 70760000 Status: INVALID
ID 119 Request - 770000 Response - F0770000 Status: INVALID
ID 120 Request - 780000 Response - F0780000 Status: INVALID
ID 121 Request - 80790000 Response - 70790000 Status: INVALID
ID 122 Request - 807A0000 Response - 707A0000 Status: INVALID
ID 123 Request - 7B0000 Response - F07B0000 Status: INVALID
Зарефакторил код. Сделал универсальную обработку json - одну на mqtt и web и пришил кривые из твоего кода (не уверен что правильно) . Осталось нарисовать вед морду с javascript и положить в SPIFFS и будет готово.
Надо глянуть. Бегло пробежался, не вижу сетпоинта комнатной температуры...
Здесь ошибки нет? .value не хватает.
Точно, поправил. Хотя странно что компилятор не ругался
По поводу ПИД и кривых. Путанница какая то, ваша переменная heat_temp_set это уставка для ID-1 котла, она же в моём коде OP, вычисляемая ПИД и кривыми. И не вижу уставки целевой комнатной температуры (в моём коде SP), участвует в вычислении и ПИД, и кривых. PV понятно, прилетает из вне и становится house_temp.
А уровень модуляции ID-17? В моём Бакси её нет. Я её вычисляю АЦП ЕСПешки из ШИМ с катушки модуляции котла.
heat_temp_set - имеет разное значение в зависимости от house_temp_compsenation
Если house_temp_compsenation = false - то это прямое значение для уставки ID-1 если true - то это уставка комнатной температуры.
house_temp - это переменная куда будет прилетать актуальная температура в комнате с сенсоров
ID-17 у меня используется толька для в функии дампа - проверить какие ID поддерживаются.
Теперь понятно. А уличный датчик штатный в котле или, так же с сенсоров прилетает?
Который в котле.
float temp = getOutsideTemp(); responseStatus = ot.getLastResponseStatus(); if (responseStatus == OpenThermResponseStatus::SUCCESS) { vars.outside_temp.setValue(temp); }enableCentralHeating управляется? Когда house_temp_compsenation = true и температура уставки в помещении heat_temp_set меньше чем текущая температура в помещении house_temp. В данном условии, false.
enableCentralHeating управляется - но я не хочу его вводить в ПИД или куда еще - отключая котел. Ведь в этом случае выключается циркуляция. У меня идея в том что при TCHset меньше чем нижняя граница - котел сам отключит пламя.
Да, постциркуляция вечная получается, но если enableCentralHeating = false котёл не отключается он в ждущем режиме, и постциркуляция в этом случае или 3 мин, или 4 часа как дип в плате управления выбран.