Термостат OpenTherm на ESP8266

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

Интересно.... Даже очень.

 

А киньте рабочий вариант на почту. Проверю на себе без изменений.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Вот этот работает

#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();
}

 

VOVA_iS
Offline
Зарегистрирован: 09.07.2019
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));

Не работает функция обратного вызова, чтение топиков

tsv_33
Offline
Зарегистрирован: 11.04.2019
Да, эту часть кода я переписал и она рабочая.
Проверил, работает.
 
21:52:45.748 -> Уставка температуры ГВС = 50.00 °C                       была
21:52:46.147 -> Текущая температура контура ГВС = 57.00 °C
21:52:46.551 -> Текущая температура контура СО = 38.00 °C
21:52:46.944 -> Температура на улице = 14.00 °C
21:52:46.944 -> Индикация состояния неисправности: no fault
21:52:48.534 -> MQTT, Current Room Temperature, °C = 21.37
21:52:48.925 -> MQTT, CH Temperature, °C = 38.00
21:52:49.690 -> MQTT, DHW Temperature, °C = 57.00
21:52:50.479 -> MQTT, Outside Temperature, °C = 14.00
21:52:51.264 -> MQTT, Status Central Heating = 0
21:52:52.078 -> MQTT, Status Hot Water = 0
21:52:52.850 -> MQTT, Status Central Heating = 0
21:52:53.251 -> MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 0.00
21:52:53.251 -> MQTT,topic sp = 18.00
21:52:53.251 -> MQTT,topic spdhw = 55.00            изменил
21:52:53.669 -> Заданное значение температуры в помещении = 18.00 °C
21:52:53.669 -> Текущее значение температуры в помещении = 21.31 °C
21:52:53.669 -> Выхов ПИД регулятора = 0.00
21:52:54.643 -> Текущий статус системы отопления: off
21:52:54.643 -> Текущий статус горячей воды: off
21:52:54.643 -> Текущий статус горелки: off
21:52:55.029 -> Уставка температуры ГВС = 55.00 °C     записалась в плату
21:52:55.436 -> Текущая температура контура ГВС = 57.00 °C
21:52:55.811 -> Текущая температура контура СО = 37.00 °C
21:52:56.213 -> Температура на улице = 14.00 °C
 
Уставки делал прямо с брокера CloudMQTT
 
tsv_33
Offline
Зарегистрирован: 11.04.2019

Опять приветствую многоуважаемое сообщество. Возвращаюсь к началу поста и описываемой проблеме. Причина с медлительностью веб сервера в библиотеке

#include <OpenTherm.h>

Точнее в setup

ot.begin(handleInterrupt);

Весь код начинает работать с циклом в 1 сек. Особенность библиотеки такая. Возможно ли решить некую многозадачность?

 

OldNavi
Offline
Зарегистрирован: 22.08.2019

 

306 void loop() {
307   new_ts = millis();
308

  if (new_ts - ts > 1000) {

Думается мне что здесь проблема - вынеси обработку Web/MQTT из этого условия - должно реагировать получше.  Мне приехали мои ESPшки - буду собирать свой ОТ контроллер, на этой же базе - в 2 головы глядишь и интереснее пойдет.

tsv_33
Offline
Зарегистрирован: 11.04.2019

OldNavi, нет, не помогает. Весь код, что в условии, что за его пределми - 1сек. Как только комментирую ot.begin(handleInterrupt); сервера летают, датчик тоже, но ОТ не работает.

OldNavi
Offline
Зарегистрирован: 22.08.2019

обработка прерываний не должна брать много - код под вопросмо  что я вижу в библиотеке 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

 

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

Залетает...и без запросов то же.

OldNavi
Offline
Зарегистрирован: 22.08.2019

Ну и видится проблема в том, что ты заспамил шину ОТ запросами (а запрос ответ может быть до 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 сделано.  Желание знать актуальную температуру каждую секунду похвально - но бессмыслено - она и раз за минуту не изменится. ну и тд и тп.

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

Сейчас на полный цикл обработки данных уходит 10-12 сек. Оригинальный код автора библиотеки http://ihormelnyk.com/mqtt_thermostat кушает на треть меньше. но там и данных меньше. Про код с ID2, что его можно в setup(), не прокатывает, а без него мой котёл только мониторится, а вот отправлять код каждые раз в 50 секунд вполне (тут надо покопаться в инфе, фигурируют цифры 40 и 20 сек.). Если котёл в течении этого времени не получит кода подтверждения из ID3 режим ОТ отключается, термостат только мониторит и то не всё. Другим котлам это по барабану. В любом случае сама библиотека ОТ вызывает вопросы.

OldNavi
Offline
Зарегистрирован: 22.08.2019

Я полазил по коду библиотеки - он впринципе нормально реализован, из минусов он только работает нормально под core 2.3.0 - под 2.5.2 падает.  Но не суть - проблема таже - sendRequest() это синхронный запрос и он может возвращать данные до 1 секунды.  2-я проблема - это спамминг шины ОТ - если читать спецификацию то следующий запрос можно делать не ранее чем через 100 милисекунд после последнего ответа. Думается мне, что я попробую реализацию через шедуллер с 2-мя тасками через https://www.arduinolibraries.info/libraries/esp8266-scheduler - в одном будет коммуникация с ОТ через библиотеку с таймингами, во второй таске уже MQTT и Вебсервак с блекджеком и б..ми. Но это пока не скоро - надо платку согласования еще распаять.

tsv_33
Offline
Зарегистрирован: 11.04.2019
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, решит проюлему спаминга.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Определённо сдвиг в лучшую сторону есть. Нужен совет. Как лучше организовать чтение переменных ОТ в одном case или каждую в отдельном?

OldNavi
Offline
Зарегистрирован: 22.08.2019

ИМХО лучше в отдельном case

tsv_33
Offline
Зарегистрирован: 11.04.2019

Сервер заработал :-)

OldNavi
Offline
Зарегистрирован: 22.08.2019

Выложил бы полный код куданить на Гитхаб например.

OldNavi
Offline
Зарегистрирован: 22.08.2019

Вроде добился, что контекст псевдо 2-х поточного кода заработал.  У меня теперь веб сервер отвечает в течении порядка 100ms - т.е практически сразу.  Заодно пришил и конфигуратор и OTA.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Всё же есть одна особенность. Обработка 3 и 2 ID должны быть в одном case.

OldNavi
Offline
Зарегистрирован: 22.08.2019

Положил свою реализацию на основе async запросов и параллельных тасков на Гитхаб

https://github.com/OldNavi/OpenThermController.git

Пока реализовано

1/ WiFi manager - для конфига WiFI и MQTT

2/ Сохранение конфига при применении команд

3/ MQTT с JSON

4/ HTTP Server пока простой

5/ ОТА поддерживается

6/ Сброс бойлера через MQTT

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

Мой файл на https://yadi.sk/d/aigzaKc_TUQsnQ

OldNavi
Offline
Зарегистрирован: 22.08.2019

О_О.  Поимел сегодня секса с этой библиотекой opentherm_library.   Выяснилась такая бяка - если использовать SPIFFS для сохранения конфига и в это время прилетает прерывание от ОТ адаптера - все падает в корку. После долго дебага и гугления с рашифровкой стек трейса выяснилась собственно простая вещь  - при записи файла SPIFFS отключает флеш от основного адресного пространства и соответственно когда прилетает прерывание у обработчики и вызываевых им функций не стоит атрибута ICACHE_RAM_ATTR и соответственно инструкция пытается выполнится из флеша (а его то уже тютю) и мы падаем в эксепшен.  Соответственно пришлось поправить библиотеку в OpenTherm.cpp добавив необходимые аттрибуты и все перестало падать.  Кому надо берите исправленную библиотеку здесь - https://github.com/OldNavi/opentherm_library.git.  Pull Requst в основной репозиторий я сделал - но хз приет его автор или нет, посмотрим.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Я библиотеку немного модифицировал, добавил к базовым ещё обработку нескольких ID.

OldNavi
Offline
Зарегистрирован: 22.08.2019

Прикрутил EEPROM - но кторый rotating - так глядишь не быстро сдохнет espшка

tsv_33
Offline
Зарегистрирован: 11.04.2019

Я свою год гоняю, до полсотни раз за день шью... Ничего, жива...

tsv_33
Offline
Зарегистрирован: 11.04.2019

Гляжу в коде нет датчика температуры (помещения), с других устройств используете?

OldNavi
Offline
Зарегистрирован: 22.08.2019

Да, эту информацию будет давать Home Assistant. Кстати вопрос такой - а с другими параметрами - например ID=54 Room Temperature - не пробовали ?  Может БАКСИ сам будет все считать, есть у меня подозрение что он внутри себя все может делать и не надо всякие PID прикручивать.

tsv_33
Offline
Зарегистрирован: 11.04.2019

По внешнему подключению температурного датчика думал, у меня вся автоматика на modbus сделана и датчиков в доме штук 5. Пока не стал заморачиваться...

Нет 54-го. К сожалению возможности Слима ограничены, даже модуляцию не выдаёт, по сему АЦП задействовал. Вот можно глянуть какие ID в Бакси Слим рулятся http://otgw.tclcode.com/matrix.cgi#boilers  Кстати, в моей прошивке по ссылке выше, есть ещё 2 регулятора (кривые).

OldNavi
Offline
Зарегистрирован: 22.08.2019

Нда, жалко - ну да пофиг,  и так сделаем хорошо

OldNavi
Offline
Зарегистрирован: 22.08.2019

tsv_33 пишет:

По внешнему подключению температурного датчика думал, у меня вся автоматика на modbus сделана и датчиков в доме штук 5. Пока не стал заморачиваться...

Нет 54-го. К сожалению возможности Слима ограничены, даже модуляцию не выдаёт, по сему АЦП задействовал. Вот можно глянуть какие ID в Бакси Слим рулятся http://otgw.tclcode.com/matrix.cgi#boilers  Кстати, в моей прошивке по ссылке выше, есть ещё 2 регулятора (кривые).

Я переехал с самодельной автоматики на HA (он модбас тоже поддерживает) - вещь оказалось собственно. А там написать автоматизацию которая с датчиков будет что то усредненное считать по температуре и слать через MQTT dв бакси - это как два байта об асфальт.

tsv_33
Offline
Зарегистрирован: 11.04.2019

У меня так и было сделано, температура усреднялась от 4-х датчиков, в операторской панели Weintek MT6070iH, 7″ был сделан термостат, он всё вычислял и рулил Слимом в режиме вкл/выкл 3 года. Захотелось ОТ... 

tsv_33
Offline
Зарегистрирован: 11.04.2019

OldNavi, из строки 98 unsigned int  val_in_hex = (val * 256 * 16); уберите деление на 16, лишнее...

По ссылке выше добавил модифицированную библиотеку.

tsv_33
Offline
Зарегистрирован: 11.04.2019


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);
    }

 

 

OldNavi
Offline
Зарегистрирован: 22.08.2019

Это все я уже поправил, все теперь работает... Одну загадку разгадать не могу.

Если например котел уходит в ошибку - то температуру ГВС показывает как 133 градуса,  че хрень пока не пойму

PS:  Кстати мой PR в opentherm library - смерджили - можно обновить библиотеку.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Хм, это не температура, похоже на код ошибки, типа Е 133, но из ID-5 читаться должен (расшифровка = по моему блокировка газа).

tsv_33
Offline
Зарегистрирован: 11.04.2019

Полагаю, ошибка здесь кроется:

if (responseStatus == OpenThermResponseStatus::SUCCESS && vars.isFault.value) {
   vars.dhw_temp.setValue(getFaultCode());
  }

 

OldNavi
Offline
Зарегистрирован: 22.08.2019

Блин! Спасибо - совсем copy-past проглядел.  Кстати, табличке http://otgw.tclcode.com/matrix.cgi#boilers

не доверяю,  бакси отвечает на большее количество кодов - сейчас дамп пишу - попробую собрать статистику

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

OldNavi пишет:

Блин! Спасибо - совсем copy-past проглядел.  Кстати, табличке http://otgw.tclcode.com/matrix.cgi#boilers

не доверяю,  бакси отвечает на большее количество кодов - сейчас дамп пишу - попробую собрать статистику

Слим-Слимом, но пихают туда платы разных производителей...

OldNavi
Offline
Зарегистрирован: 22.08.2019

Нее,  не отвечает засранец :-) Ну и ладно - и так обойдемся

Добавил обработку флагов ошибки (а не только кода 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

OldNavi
Offline
Зарегистрирован: 22.08.2019

Зарефакторил код. Сделал универсальную обработку json - одну на mqtt и web и пришил кривые из твоего кода (не уверен что правильно) . Осталось нарисовать вед морду с javascript и положить в SPIFFS и будет готово.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Надо глянуть. Бегло пробежался, не вижу сетпоинта комнатной температуры...

tsv_33
Offline
Зарегистрирован: 11.04.2019
vars.isFlameOn = ot.isFlameOn(statusResponse);
vars.isFault = ot.isFault(statusResponse);
vars.isDiagnostic = ot.isDiagnostic(statusResponse);

Здесь ошибки нет? .value не хватает.

OldNavi
Offline
Зарегистрирован: 22.08.2019

Точно, поправил.  Хотя странно что компилятор не ругался

tsv_33
Offline
Зарегистрирован: 11.04.2019

По поводу ПИД и кривых. Путанница какая то, ваша переменная heat_temp_set это уставка для ID-1 котла, она же в моём коде OP, вычисляемая ПИД и кривыми. И не вижу уставки целевой комнатной температуры (в моём коде SP), участвует в вычислении и ПИД, и кривых. PV понятно, прилетает из вне и становится house_temp. 

А уровень модуляции ID-17? В моём Бакси её нет. Я её вычисляю АЦП ЕСПешки из ШИМ с катушки модуляции котла.

OldNavi
Offline
Зарегистрирован: 22.08.2019

heat_temp_set - имеет разное значение в зависимости от house_temp_compsenation

Если house_temp_compsenation = false - то это прямое значение для уставки ID-1 если true - то это уставка комнатной температуры.

house_temp - это переменная куда будет прилетать актуальная температура в комнате с сенсоров

ID-17 у меня используется толька для в функии дампа - проверить какие ID поддерживаются.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Теперь понятно. А уличный датчик штатный в котле или, так же с сенсоров прилетает?

OldNavi
Offline
Зарегистрирован: 22.08.2019

Который в котле.

            float temp = getOutsideTemp();
            responseStatus = ot.getLastResponseStatus();
          if (responseStatus == OpenThermResponseStatus::SUCCESS) {
            vars.outside_temp.setValue(temp);
          }

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

enableCentralHeating управляется? Когда  house_temp_compsenation = true и температура уставки в помещении heat_temp_set меньше чем текущая температура в помещении house_temp. В данном условии, false.

OldNavi
Offline
Зарегистрирован: 22.08.2019

enableCentralHeating управляется - но я не хочу его вводить в ПИД или куда еще - отключая котел. Ведь в этом случае выключается циркуляция.  У меня идея в том что при TCHset меньше чем нижняя граница - котел сам отключит пламя.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Да, постциркуляция вечная получается, но если enableCentralHeating = false котёл не отключается он в ждущем режиме, и постциркуляция в этом случае или 3 мин, или 4 часа как дип в плате управления выбран.