2024-02-12 20:49:36 +03:00
|
|
|
|
#include "Global.h"
|
|
|
|
|
|
#include "classes/IoTItem.h"
|
|
|
|
|
|
#include <Arduino.h>
|
|
|
|
|
|
#include "BoilerHeader.h"
|
2024-02-26 23:31:12 +03:00
|
|
|
|
#include <map>
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
#include "OpenTherm.h"
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
#define SLAVE true
|
|
|
|
|
|
#define TIMEOUT_TRESHOLD 5
|
|
|
|
|
|
|
|
|
|
|
|
namespace _Boiler_v2
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
struct StepPower
|
|
|
|
|
|
{
|
|
|
|
|
|
float pwr;
|
|
|
|
|
|
std::vector<String> listIDRele;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// std::vector<IoTItem *> vectorRele;
|
|
|
|
|
|
|
|
|
|
|
|
std::map<String, IoTItem *> mapRele;
|
|
|
|
|
|
|
|
|
|
|
|
std::map<int, StepPower> stepMap;
|
|
|
|
|
|
|
|
|
|
|
|
// DynamicJsonDocument OpenThemData(JSON_BUFFER_SIZE / 2);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
|
|
|
|
|
IoTItem *_idPID = nullptr;
|
|
|
|
|
|
IoTItem *_idTboiler = nullptr;
|
|
|
|
|
|
IoTItem *_idTret = nullptr;
|
|
|
|
|
|
IoTItem *_idToutside = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
IoTItem *_idStateCH = nullptr;
|
|
|
|
|
|
IoTItem *_idStateFlame = nullptr;
|
|
|
|
|
|
IoTItem *_idModLevel = nullptr;
|
|
|
|
|
|
IoTItem *_idCmdCH = nullptr;
|
|
|
|
|
|
IoTItem *_idSetCH = nullptr;
|
|
|
|
|
|
IoTItem *_idCtrlType = nullptr;
|
2024-02-26 23:31:12 +03:00
|
|
|
|
IoTItem *_relePump = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
IoTItem *_idStateDHW = nullptr;
|
|
|
|
|
|
IoTItem *_idCmdDHW = nullptr;
|
|
|
|
|
|
IoTItem *_idTDhw = nullptr;
|
|
|
|
|
|
IoTItem *_idSetDHW = nullptr;
|
|
|
|
|
|
IoTItem *_releDhw = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
float pid;
|
|
|
|
|
|
// IoTItem *rele[3];
|
|
|
|
|
|
// IoTItem *_relePump;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
|
|
|
|
|
IoTItem *tmp;
|
|
|
|
|
|
|
|
|
|
|
|
// проверяем если пришедшее значение отличается от предыдущего регистрируем событие
|
|
|
|
|
|
void publishNew(String widget, String value)
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
tmp = findIoTItem(widget);
|
|
|
|
|
|
if (tmp)
|
|
|
|
|
|
{
|
|
|
|
|
|
tmp->setValue(value, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
SerialPrint("new", "SmartBoiler", widget + " = " + value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// Обновит состояние реле, только если оно поменялось, что бы было меньше событий
|
|
|
|
|
|
void updateReleState(IoTItem *rele, int val)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (rele)
|
|
|
|
|
|
if (val != ::atoi(rele->getValue().c_str()))
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
rele->setValue(String(val), true);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void sendTelegramm(String msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_telegram == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (tlgrmItem)
|
|
|
|
|
|
tlgrmItem->sendTelegramMsg(false, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
OpenTherm *ot_driver = nullptr;
|
|
|
|
|
|
OpenTherm *instance_OTdriver(int _RX_pin, int _TX_pin)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!ot_driver)
|
|
|
|
|
|
{
|
|
|
|
|
|
ot_driver = new OpenTherm(_RX_pin, _TX_pin, SLAVE);
|
|
|
|
|
|
// ot_driver->begin();
|
|
|
|
|
|
}
|
|
|
|
|
|
return ot_driver;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Обработчик прерываний от ОТ
|
|
|
|
|
|
void IRAM_ATTR handleInterruptSlave()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ot_driver != nullptr)
|
|
|
|
|
|
ot_driver->handleInterrupt();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// unsigned long ot_response = 0;
|
|
|
|
|
|
// int SlaveMemberIDcode = 0;
|
|
|
|
|
|
|
2024-02-12 20:49:36 +03:00
|
|
|
|
/*
|
|
|
|
|
|
* =========================================================================================
|
2024-02-26 23:31:12 +03:00
|
|
|
|
* КЛАСС РАБОТЫ ПО ПРОТОКОЛУ OPENTHERM
|
2024-02-12 20:49:36 +03:00
|
|
|
|
* =========================================================================================
|
|
|
|
|
|
*/
|
2024-02-26 23:31:12 +03:00
|
|
|
|
class OpenThermSlave : public IoTItem
|
|
|
|
|
|
{
|
|
|
|
|
|
private:
|
|
|
|
|
|
// unsigned long ts = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
public:
|
|
|
|
|
|
OpenThermSlave(String parameters) : IoTItem(parameters)
|
|
|
|
|
|
{
|
|
|
|
|
|
int _RX_pin = 16;
|
|
|
|
|
|
int _TX_pin = 4;
|
|
|
|
|
|
SerialPrint("i", F("OpenThermSlave"), " START... ");
|
|
|
|
|
|
|
|
|
|
|
|
jsonRead(parameters, "RX_pin", _RX_pin);
|
|
|
|
|
|
jsonRead(parameters, "TX_pin", _TX_pin);
|
|
|
|
|
|
jsonRead(parameters, "MemberID", SlaveMemberIDcode);
|
|
|
|
|
|
|
|
|
|
|
|
instance_OTdriver(_RX_pin, _TX_pin);
|
|
|
|
|
|
ot_driver->begin(handleInterruptSlave, processRequest); // responseCallback
|
|
|
|
|
|
// ot_boiler = this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void configured()
|
|
|
|
|
|
{
|
|
|
|
|
|
static bool isFirstOT = true;
|
|
|
|
|
|
if (isFirstOT)
|
|
|
|
|
|
{
|
|
|
|
|
|
isFirstOT = false;
|
|
|
|
|
|
|
|
|
|
|
|
bool haveBoiler = false;
|
|
|
|
|
|
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((*it)->getSubtype() == "BoilerControl")
|
|
|
|
|
|
haveBoiler = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!haveBoiler)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("E", "OpenThermSlave", "Warning: BoilerControl не найден! работаем автономно по ID модулей");
|
|
|
|
|
|
_idTboiler = findIoTItem("idTboiler");
|
|
|
|
|
|
_idTret = findIoTItem("idTret");
|
|
|
|
|
|
_idToutside = findIoTItem("idToutside");
|
|
|
|
|
|
_idStateCH = findIoTItem("idStateCH");
|
|
|
|
|
|
_idStateDHW = findIoTItem("idStateDHW");
|
|
|
|
|
|
_idStateFlame = findIoTItem("idStateFlame");
|
|
|
|
|
|
_idModLevel = findIoTItem("idModLevel");
|
|
|
|
|
|
_idTDhw = findIoTItem("idTDhw");
|
|
|
|
|
|
_idCmdCH = findIoTItem("idCmdCH");
|
|
|
|
|
|
_idCmdDHW = findIoTItem("idCmdDHW");
|
|
|
|
|
|
_idCtrlType = findIoTItem("idCtrlType");
|
|
|
|
|
|
_idSetCH = findIoTItem("idSetCH");
|
|
|
|
|
|
_idSetDHW = findIoTItem("idSetDHW");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void doByInterval()
|
|
|
|
|
|
{
|
|
|
|
|
|
configured();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Основной цикл программы
|
|
|
|
|
|
void loop()
|
|
|
|
|
|
{
|
|
|
|
|
|
ot_driver->process();
|
|
|
|
|
|
IoTItem::loop();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Комманды из сценария
|
|
|
|
|
|
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
|
|
|
|
|
{
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Обработка управления и отправка статуса
|
|
|
|
|
|
static void processStatus(unsigned int &data)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t statusRequest = data >> 8; // забрали старший байт с командами мастера
|
|
|
|
|
|
set.cmd_chEnable = statusRequest & 0x1; // забрали 0 бит из этого байта - включение СО (маска 01)
|
|
|
|
|
|
|
|
|
|
|
|
set.cmd_dhwEnable = statusRequest & 0x2; // забрали 1 бит из этого байта - включение СО (маска 10)
|
|
|
|
|
|
IoTValue val;
|
|
|
|
|
|
val.valD = set.cmd_chEnable;
|
|
|
|
|
|
if (_idCmdCH)
|
|
|
|
|
|
_idCmdCH->setValue(val, true);
|
|
|
|
|
|
val.valD = set.cmd_dhwEnable;
|
|
|
|
|
|
if (_idCmdDHW)
|
|
|
|
|
|
_idCmdDHW->setValue(val, true);
|
|
|
|
|
|
data &= 0xFF00; // старший бит не трогаем, а младший обнулили, что бы его заполнить состоянием котла и вернуть data термостату
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
// if (_idFail)
|
|
|
|
|
|
// state.fl_fail = ::atof(_idFail->getValue().c_str());
|
|
|
|
|
|
if (_idStateCH)
|
|
|
|
|
|
state.stateCH = ::atoi(_idStateCH->getValue().c_str());
|
|
|
|
|
|
if (_idStateDHW)
|
|
|
|
|
|
state.stateDHW = ::atoi(_idStateDHW->getValue().c_str());
|
|
|
|
|
|
if (_idStateFlame)
|
|
|
|
|
|
state.fl_flame = ::atoi(_idStateFlame->getValue().c_str());
|
|
|
|
|
|
*/
|
|
|
|
|
|
if (state.fl_fail)
|
|
|
|
|
|
data |= 0x01; // fault indication
|
|
|
|
|
|
if (state.stateCH)
|
|
|
|
|
|
data |= 0x02; // CH active
|
|
|
|
|
|
if (state.stateDHW)
|
|
|
|
|
|
data |= 0x04; // DHW active
|
|
|
|
|
|
if (state.fl_flame)
|
|
|
|
|
|
data |= 0x08; // flame on
|
|
|
|
|
|
// data |= 0x10; //cooling active
|
|
|
|
|
|
// data |= 0x20; //CH2 active
|
|
|
|
|
|
// data |= 0x40; //diagnostic/service event
|
|
|
|
|
|
// data |= 0x80; //electricity production on
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// обработка сброса ошибок
|
|
|
|
|
|
static void processCommand(unsigned int &data)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint8_t command = data >> 8; // забрали старший байт с командами мастера
|
|
|
|
|
|
if (command == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
state.fl_fail = 0;
|
|
|
|
|
|
data |= 128; // ответ 128-255: команда выполнена
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=================================== Обработка входящих сообщение ОТ ======================================
|
|
|
|
|
|
static void processRequest(unsigned long request, OpenThermResponseStatus status)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (status)
|
|
|
|
|
|
{
|
|
|
|
|
|
case OpenThermResponseStatus::NONE:
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("E", "OpenThermSlave", "Error: OpenTherm не инициализирован");
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermResponseStatus::INVALID:
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("E", "OpenThermSlave", "ID:" + String(ot_driver->getDataID(request)) + " / Error: Ошибка разбора команды: " + String(request, HEX));
|
|
|
|
|
|
// build UNKNOWN-DATAID response
|
|
|
|
|
|
unsigned long response = ot_driver->buildResponse(OpenThermMessageType::DATA_INVALID, ot_driver->getDataID(request), 0);
|
|
|
|
|
|
|
|
|
|
|
|
// send response
|
|
|
|
|
|
ot_driver->sendResponse(response);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermResponseStatus::TIMEOUT:
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("E", "OpenThermSlave", " ID: " + String(ot_driver->getDataID(request)) + " / Error: Таймаут команд от управляющего устройства (термостата)");
|
|
|
|
|
|
}
|
|
|
|
|
|
timeout_count++;
|
|
|
|
|
|
if (timeout_count > TIMEOUT_TRESHOLD)
|
|
|
|
|
|
{
|
|
|
|
|
|
// publishNew("boilerslave", "❌");
|
|
|
|
|
|
// publishNew("status", "не подключен");
|
|
|
|
|
|
// setValue("ОТ не подключен", true);
|
|
|
|
|
|
timeout_count = TIMEOUT_TRESHOLD;
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("E", "OpenThermSlave", "OpenTherm: потеря связи с управляющим устройством (термостатом) ❌");
|
|
|
|
|
|
}
|
|
|
|
|
|
sendTelegramm(("OpenTherm: потеря связи с управляющим устройством (термостатом) ❌"));
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermResponseStatus::SUCCESS:
|
|
|
|
|
|
timeout_count = 0;
|
|
|
|
|
|
// publishNew("boilerslave", "✅");
|
|
|
|
|
|
// publishNew("status", "подключен");
|
|
|
|
|
|
// setValue("ОТ подключен", true);
|
|
|
|
|
|
// sendTelegramm(("OpenTherm: котёл подключен ✅"));
|
|
|
|
|
|
// respondense_flag = true;
|
|
|
|
|
|
// ts_ = new_ts_;
|
|
|
|
|
|
HandleRequest(request);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Парсинг запросов
|
|
|
|
|
|
static void HandleRequest(unsigned long request)
|
|
|
|
|
|
{
|
|
|
|
|
|
/*
|
|
|
|
|
|
if (_idCtrlType)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_idCtrlType->getValue() == "Модуляция")
|
|
|
|
|
|
conf.ctrlType = 0;
|
|
|
|
|
|
else
|
|
|
|
|
|
conf.ctrlType = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
*/
|
|
|
|
|
|
// conf.ctrlType = ::atoi(_idCtrlType->getValue().c_str());
|
|
|
|
|
|
// unsigned long response;
|
|
|
|
|
|
unsigned int data = ot_driver->getUInt(request);
|
|
|
|
|
|
OpenThermMessageType msgType;
|
|
|
|
|
|
byte ID;
|
|
|
|
|
|
OpenThermMessageID id = ot_driver->getDataID(request);
|
|
|
|
|
|
uint8_t flags;
|
|
|
|
|
|
if (_debug > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("i", "OpenThermSlave <-", String(millis()) + " ID: " + String(id) + " / requestHEX: " + String(request, HEX) + " / request: " + String(request));
|
|
|
|
|
|
}
|
|
|
|
|
|
switch (id)
|
|
|
|
|
|
{
|
|
|
|
|
|
/*----------------------------Инициализация и конфигурация----------------------------*/
|
|
|
|
|
|
case OpenThermMessageID::SConfigSMemberIDcode: // запрос Конфигурации Котла и SlaveMemberID
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
data = conf.dhw | (conf.ctrlType << 1) | (false << 2) | (conf.confDhw << 3) | (conf.pumpControlMaster << 4) | (false << 5); // 2-cooling, 5-CH2
|
|
|
|
|
|
data <<= 8;
|
|
|
|
|
|
data |= SlaveMemberIDcode;
|
|
|
|
|
|
// data = (int)SlaveMemberIDcode;
|
|
|
|
|
|
break;
|
|
|
|
|
|
// case OpenThermMessageID::MConfigMMemberIDcode: // Получили Master Member ID
|
|
|
|
|
|
// msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
// break;
|
|
|
|
|
|
// case OpenThermMessageID::SlaveVersion: // TODO вернуть версию модуля
|
|
|
|
|
|
// msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// data = (int)1;
|
|
|
|
|
|
// break;
|
|
|
|
|
|
// case OpenThermMessageID::MasterVersion:
|
|
|
|
|
|
// msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
// break;
|
|
|
|
|
|
// case OpenThermMessageID::OpenThermVersionSlave:
|
|
|
|
|
|
// msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// break;
|
|
|
|
|
|
/*----------------------------Управление (уставки и команды)----------------------------*/
|
|
|
|
|
|
case OpenThermMessageID::TdhwSetUBTdhwSetLB: // границы уставки ГВС
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
data |= (uint8_t)conf.minDhw;
|
|
|
|
|
|
data |= (uint8_t)conf.maxDhw << 8;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::MaxTSetUBMaxTSetLB: // границы уставки СО
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
data |= (uint8_t)conf.minCH;
|
|
|
|
|
|
data |= (uint8_t)conf.maxCH << 8;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::Command: // Сброс ошибок/сброс блокировки котла. Ответ: команды (не)выполнена
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
processCommand(data);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::TdhwSet: // TODO Получили температуру ГВС
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
{
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// if (_idSetDHW)
|
|
|
|
|
|
// set.TSetDhw = ::atof(_idSetDHW->getValue().c_str());
|
|
|
|
|
|
data = ot_driver->temperatureToData(set.TSetDhw);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
// processDHWSet(ot_driver->getFloat(data));
|
|
|
|
|
|
set.TSetDhw = ot_driver->getFloat(data);
|
|
|
|
|
|
set.TSetDhw = constrain(set.TSetDhw, conf.minDhw, conf.maxDhw);
|
|
|
|
|
|
// publishNew("TDHWSet", String(set.TSetDhw));
|
|
|
|
|
|
IoTValue val;
|
|
|
|
|
|
val.valD = set.TSetDhw;
|
|
|
|
|
|
if (_idSetDHW)
|
|
|
|
|
|
_idSetDHW->setValue(val, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::TSet: // TODO Получили температуру СО
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
{
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// if (_idSetCH)
|
|
|
|
|
|
// set.TSetCH = ::atof(_idSetCH->getValue().c_str());
|
|
|
|
|
|
data = ot_driver->temperatureToData(set.TSetCH);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
// processCHSet(ot_driver->getFloat(data));
|
|
|
|
|
|
set.TSetCH = ot_driver->getFloat(data);
|
|
|
|
|
|
set.TSetCH = constrain(set.TSetCH, conf.minCH, conf.maxCH);
|
|
|
|
|
|
// publishNew("TCHSet", String(set.TSetCH));
|
|
|
|
|
|
IoTValue val;
|
|
|
|
|
|
val.valD = set.TSetCH;
|
|
|
|
|
|
if (_idSetCH)
|
|
|
|
|
|
_idSetCH->setValue(val, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
/* case OpenThermMessageID::MaxTSet: // максимальная уставка ГВС ??????
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::Hcratio: // Коэффециент тепловой кривой
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
*/
|
|
|
|
|
|
/*----------------------------Состояние и статусы----------------------------*/
|
|
|
|
|
|
case OpenThermMessageID::Status: // TODO Вернуть Статус котла
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
processStatus(data);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::RelModLevel: // запрос модуляции
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// if (_idModLevel)
|
|
|
|
|
|
// state.RelModLevel = ::atoi(_idModLevel->getValue().c_str());
|
|
|
|
|
|
data = ot_driver->temperatureToData(state.RelModLevel);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::Tboiler: // запрос температуры котла
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// if (_idTboiler)
|
|
|
|
|
|
// state.Tboiler = ::atof(_idTboiler->getValue().c_str());
|
|
|
|
|
|
data = ot_driver->temperatureToData(state.Tboiler);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::Tdhw: // запрос температуры ГВС
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
if (_idTDhw)
|
|
|
|
|
|
{
|
|
|
|
|
|
state.Tdhw = ::atof(_idTDhw->getValue().c_str());
|
|
|
|
|
|
data = ot_driver->temperatureToData(state.Tdhw);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
msgType = OpenThermMessageType::UNKNOWN_DATA_ID;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::Toutside: // запрос внешней температуры
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
if (_idToutside)
|
|
|
|
|
|
{
|
|
|
|
|
|
state.Toutside = ::atof(_idToutside->getValue().c_str());
|
|
|
|
|
|
data = ot_driver->temperatureToData(state.Toutside);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
msgType = OpenThermMessageType::UNKNOWN_DATA_ID;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::ASFflags: // запрос ошибок
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
data = 0;
|
|
|
|
|
|
if (state.fl_fail)
|
|
|
|
|
|
{
|
|
|
|
|
|
data = state.fCode.service_required | (state.fCode.lockout_reset << 1) | (state.fCode.low_water_pressure << 2) | (state.fCode.gas_fault << 3) | (state.fCode.air_fault << 4) | (state.fCode.water_overtemp << 5);
|
|
|
|
|
|
data |= (uint8_t)state.fCode.fault_code << 8;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::Tret: // температура обратки
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
if (_idTret)
|
|
|
|
|
|
{
|
|
|
|
|
|
state.Tret = ::atof(_idTret->getValue().c_str());
|
|
|
|
|
|
data = ot_driver->temperatureToData(state.Tret);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
msgType = OpenThermMessageType::UNKNOWN_DATA_ID;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
// case OpenThermMessageID::OEMDiagnosticCode:
|
|
|
|
|
|
// msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// break;
|
|
|
|
|
|
// case OpenThermMessageID::ElectricBurnerFlame: // Ток работы горелки ?????
|
|
|
|
|
|
// msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// break;
|
|
|
|
|
|
// case OpenThermMessageID::MaxCapacityMinModLevel: // максимальная мощность котла кВт и минимальная модуляция %
|
|
|
|
|
|
// msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
// break;
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------Двусторонние информационные сообщения----------------------------*/
|
|
|
|
|
|
/* case OpenThermMessageID::DayTime:
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::Date:
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::Year:
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
// ========>>>>>>>>>>> СБРОС КОЛИЧЕСТВА 0 от мастера
|
|
|
|
|
|
case OpenThermMessageID::BurnerStarts: // Количество стартов горелки
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::CHPumpStarts: // Количество стартов насоса СО
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::DHWPumpValveStarts: // Количество стартов насоса/клапана ГВС
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::DHWBurnerStarts: // Количество стартов горелки ГВС
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::BurnerOperationHours: // часы работы горелки
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::CHPumpOperationHours: // часы работы горелки СО
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::DHWPumpValveOperationHours: // часы работы насоса/клапана ГВС
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OpenThermMessageID::DHWBurnerOperationHours: // часы работы горелки ГВС
|
|
|
|
|
|
if (ot_driver->getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
|
|
|
|
msgType = OpenThermMessageType::READ_ACK;
|
|
|
|
|
|
else
|
|
|
|
|
|
msgType = OpenThermMessageType::WRITE_ACK;
|
|
|
|
|
|
break;
|
|
|
|
|
|
*/
|
|
|
|
|
|
/*------------------------------------ ВСЁ ------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
msgType = OpenThermMessageType::UNKNOWN_DATA_ID;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
ot_response = ot_driver->buildResponse(msgType, id, data);
|
|
|
|
|
|
ot_driver->sendResponse(ot_response);
|
|
|
|
|
|
if (_debug > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("i", "OpenThermSlave ->", String(millis()) + " ID: " + String(id) + " / responseHEX: " + String(ot_response, HEX) + " / response: " + String(ot_response));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
~OpenThermSlave()
|
|
|
|
|
|
{
|
|
|
|
|
|
delete ot_driver;
|
|
|
|
|
|
ot_driver = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* =========================================================================================
|
|
|
|
|
|
* КЛАСС УПРАВЛЕНИЯ ГВС
|
|
|
|
|
|
* =========================================================================================
|
|
|
|
|
|
*/
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
|
|
|
|
|
class DHWControl : public IoTItem
|
|
|
|
|
|
{
|
|
|
|
|
|
private:
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// unsigned long ts = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
DHWControl(String parameters) : IoTItem(parameters)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("i", F("DHWControl"), " START... ");
|
|
|
|
|
|
|
|
|
|
|
|
jsonRead(parameters, "minDhw", conf.minDhw);
|
|
|
|
|
|
jsonRead(parameters, "maxDhw", conf.maxDhw);
|
|
|
|
|
|
|
|
|
|
|
|
jsonRead(parameters, "gistDhw", conf.gistDhw);
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
jsonRead(parameters, "numStepDhw", conf.numStepDhw);
|
|
|
|
|
|
|
|
|
|
|
|
String tmpID;
|
|
|
|
|
|
if (jsonRead(parameters, "idSetDHW", tmpID))
|
|
|
|
|
|
_idSetDHW = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idStateDHW", tmpID))
|
|
|
|
|
|
_idStateDHW = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idCmdDHW", tmpID))
|
|
|
|
|
|
_idCmdDHW = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idTDhw", tmpID))
|
|
|
|
|
|
_idTDhw = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idReleDhw", tmpID))
|
|
|
|
|
|
_releDhw = findIoTItem(tmpID);
|
|
|
|
|
|
if (_releDhw)
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("i", "DHWControl", "Инициализировано РЕЛЕ ГВС");
|
2024-02-26 23:31:12 +03:00
|
|
|
|
conf.dhw = true;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
/*
|
|
|
|
|
|
void configured()
|
|
|
|
|
|
{
|
|
|
|
|
|
static bool isFirstDhW = true;
|
|
|
|
|
|
if (isFirstDhW)
|
|
|
|
|
|
{
|
|
|
|
|
|
isFirstDhW = false;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
String tmpID;
|
|
|
|
|
|
if (jsonRead(parameters, "idSetDHW", tmpID))
|
|
|
|
|
|
_idSetDHW = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idStateDHW", tmpID))
|
|
|
|
|
|
_idStateDHW = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idCmdDHW", tmpID))
|
|
|
|
|
|
_idCmdDHW = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idTDhw", tmpID))
|
|
|
|
|
|
_idTDhw = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idReleDhw", tmpID))
|
|
|
|
|
|
_releDhw = findIoTItem(tmpID);
|
|
|
|
|
|
if (_releDhw){
|
|
|
|
|
|
SerialPrint("i", "DHWControl", "Инициализировано РЕЛЕ ГВС");
|
|
|
|
|
|
conf.dhw = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
*/
|
2024-02-12 20:49:36 +03:00
|
|
|
|
// ============================== ЛОГИКА РАБОТЫ КОТЛА И ВКЛЮЧЕНИЯ ТЭНОВ ======================================
|
|
|
|
|
|
|
|
|
|
|
|
// Работы котла и включения тэнов
|
|
|
|
|
|
static void logicPowerDhw()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_idCmdDHW)
|
|
|
|
|
|
set.cmd_dhwEnable = ::atoi(_idCmdDHW->getValue().c_str());
|
|
|
|
|
|
|
|
|
|
|
|
if (_idTDhw)
|
|
|
|
|
|
state.Tdhw = ::atof(_idTDhw->getValue().c_str());
|
|
|
|
|
|
|
|
|
|
|
|
if (_idSetDHW)
|
|
|
|
|
|
set.TSetDhw = ::atof(_idSetDHW->getValue().c_str());
|
|
|
|
|
|
|
|
|
|
|
|
if (set.cmd_dhwEnable)
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (_releDhw)
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
|
|
|
|
|
if (_idTDhw)
|
|
|
|
|
|
state.Tdhw = ::atof(_idTDhw->getValue().c_str());
|
|
|
|
|
|
|
|
|
|
|
|
if (set.TSetDhw - state.Tdhw >= conf.gistDhw && state.Tdhw > -5 && state.Tdhw < 120)
|
|
|
|
|
|
{
|
|
|
|
|
|
// включаем ГВС
|
2024-02-26 23:31:12 +03:00
|
|
|
|
//_releDhw->setValue("1", true);
|
|
|
|
|
|
updateReleState(_releDhw, 1);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
state.stateDHW = 1;
|
|
|
|
|
|
state.stateCH = 0;
|
2024-02-26 23:31:12 +03:00
|
|
|
|
state.numStepOn = conf.numStepDhw;
|
|
|
|
|
|
if (stepMap.find(conf.numStepDhw) != stepMap.end())
|
|
|
|
|
|
state.RelModLevel = conf.prcOnekWt * stepMap[conf.numStepDhw].pwr;
|
|
|
|
|
|
else
|
|
|
|
|
|
state.RelModLevel = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
//_releDhw->setValue("0", true);
|
|
|
|
|
|
updateReleState(_releDhw, 0);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
state.stateDHW = 0;
|
|
|
|
|
|
state.RelModLevel = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (_releDhw)
|
|
|
|
|
|
//_releDhw->setValue("0", true);
|
|
|
|
|
|
updateReleState(_releDhw, 0);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
// publishNew("StateDHW", String(state.stateDHW));
|
|
|
|
|
|
IoTValue val;
|
|
|
|
|
|
if (_idStateDHW)
|
|
|
|
|
|
{
|
|
|
|
|
|
val.valD = state.stateDHW;
|
|
|
|
|
|
_idStateDHW->setValue(val, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void doByInterval()
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// configured();
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Комманды из сценария
|
|
|
|
|
|
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
|
|
|
|
|
{
|
|
|
|
|
|
IoTValue val;
|
|
|
|
|
|
if (command == "SetDHW")
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (param.size())
|
|
|
|
|
|
{
|
|
|
|
|
|
set.TSetDhw = param[0].valD;
|
|
|
|
|
|
set.TSetDhw = constrain(set.TSetDhw, conf.minDhw, conf.maxDhw);
|
|
|
|
|
|
// publishNew("SetDHW", String(set.TSetDhw));
|
|
|
|
|
|
val.valD = set.TSetDhw;
|
|
|
|
|
|
if (_idSetDHW)
|
|
|
|
|
|
_idSetDHW->setValue(val, true);
|
|
|
|
|
|
SerialPrint("i", "DHWControl", "Scenario DHWSet ");
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
else if (command == "DHWEnable")
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (param.size())
|
|
|
|
|
|
{
|
|
|
|
|
|
set.cmd_dhwEnable = param[0].valD;
|
|
|
|
|
|
val.valD = set.cmd_dhwEnable;
|
|
|
|
|
|
if (_idCmdDHW)
|
|
|
|
|
|
_idCmdDHW->setValue(val, true);
|
|
|
|
|
|
SerialPrint("i", "DHWControl", "Scenario DHWEnable ");
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
~DHWControl()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* =========================================================================================
|
|
|
|
|
|
* КЛАСС УПРАВЛЕНИЯ КОТЛОМ
|
|
|
|
|
|
* =========================================================================================
|
|
|
|
|
|
*/
|
|
|
|
|
|
class BoilerControl : public IoTItem
|
|
|
|
|
|
{
|
|
|
|
|
|
private:
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// unsigned long ts = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
BoilerControl(String parameters) : IoTItem(parameters)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("i", F("BoilerControl"), " START... ");
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
jsonRead(parameters, "debug", _debug);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
jsonRead(parameters, "telegram", _telegram);
|
2024-02-26 23:31:12 +03:00
|
|
|
|
|
|
|
|
|
|
// jsonRead(parameters, "Pump", conf.pump);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
jsonRead(parameters, "changeRele", conf.changeRele);
|
|
|
|
|
|
|
|
|
|
|
|
jsonRead(parameters, "minCH", conf.minCH);
|
|
|
|
|
|
jsonRead(parameters, "maxCH", conf.maxCH);
|
|
|
|
|
|
jsonRead(parameters, "gistCH", conf.gistCH);
|
|
|
|
|
|
jsonRead(parameters, "antiFreez", conf.antiFreez);
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
jsonRead(parameters, "maxKW", conf.maxKW);
|
|
|
|
|
|
// configuration();
|
|
|
|
|
|
state.fl_flame = state.stateCH = 0;
|
|
|
|
|
|
conf.countRele = conf.prcOnekWt = 0;
|
|
|
|
|
|
|
|
|
|
|
|
String tmpID;
|
|
|
|
|
|
if (jsonRead(parameters, "idPID", tmpID))
|
|
|
|
|
|
_idPID = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idTboiler", tmpID))
|
|
|
|
|
|
_idTboiler = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idTret", tmpID))
|
|
|
|
|
|
_idTret = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idToutside", tmpID))
|
|
|
|
|
|
_idToutside = findIoTItem(tmpID);
|
|
|
|
|
|
|
|
|
|
|
|
if (jsonRead(parameters, "idStateCH", tmpID))
|
|
|
|
|
|
_idStateCH = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idStateFlame", tmpID))
|
|
|
|
|
|
_idStateFlame = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idModLevel", tmpID))
|
|
|
|
|
|
_idModLevel = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idCmdCH", tmpID))
|
|
|
|
|
|
_idCmdCH = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idSetCH", tmpID))
|
|
|
|
|
|
_idSetCH = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idCtrlType", tmpID))
|
|
|
|
|
|
_idCtrlType = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idRelePump", tmpID))
|
|
|
|
|
|
_relePump = findIoTItem(tmpID);
|
|
|
|
|
|
if (_relePump)
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Initialized relay pump");
|
|
|
|
|
|
|
|
|
|
|
|
updateStateboiler();
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
/*
|
|
|
|
|
|
void configured()
|
|
|
|
|
|
{
|
|
|
|
|
|
static bool isFirst = true;
|
|
|
|
|
|
if (isFirst)
|
|
|
|
|
|
{
|
|
|
|
|
|
isFirst = true;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
String tmpID;
|
|
|
|
|
|
if (jsonRead(parameters, "idPID", tmpID))
|
|
|
|
|
|
_idPID = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idTboiler", tmpID))
|
|
|
|
|
|
_idTboiler = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idTret", tmpID))
|
|
|
|
|
|
_idTret = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idToutside", tmpID))
|
|
|
|
|
|
_idToutside = findIoTItem(tmpID);
|
|
|
|
|
|
|
|
|
|
|
|
if (jsonRead(parameters, "idStateCH", tmpID))
|
|
|
|
|
|
_idStateCH = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idStateFlame", tmpID))
|
|
|
|
|
|
_idStateFlame = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idModLevel", tmpID))
|
|
|
|
|
|
_idModLevel = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idCmdCH", tmpID))
|
|
|
|
|
|
_idCmdCH = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idSetCH", tmpID))
|
|
|
|
|
|
_idSetCH = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idCtrlType", tmpID))
|
|
|
|
|
|
_idCtrlType = findIoTItem(tmpID);
|
|
|
|
|
|
if (jsonRead(parameters, "idRelePump", tmpID))
|
|
|
|
|
|
_relePump = findIoTItem(tmpID);
|
|
|
|
|
|
if (_relePump)
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Initialized relay pump");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
*/
|
2024-02-12 20:49:36 +03:00
|
|
|
|
// ============================== ЛОГИКА РАБОТЫ КОТЛА И ВКЛЮЧЕНИЯ ТЭНОВ ======================================
|
|
|
|
|
|
|
|
|
|
|
|
// Работы котла и включения тэнов
|
2024-02-26 23:31:12 +03:00
|
|
|
|
static void
|
|
|
|
|
|
logicPowerOn()
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// TODO ВКЛЮЧЕНИЕ РЕЛЕ ПО ИХ МОЩНОСТИ (СДЕЛАТЬ ШАГИ НАГРЕВА И КОМБИНАЦИИ ВКЛБЧЕНИЯ ТЕНОВ С РАЗНОЙ МОЩНОСТЬЮ
|
|
|
|
|
|
|
|
|
|
|
|
// state.numStepOn = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
state.RelModLevel = 0;
|
|
|
|
|
|
pid = 0;
|
|
|
|
|
|
state.fl_flame = false;
|
|
|
|
|
|
// обнуляем ГВС
|
|
|
|
|
|
state.stateDHW = 0;
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
conf.countRele = mapRele.size(); // vectorRele.size();
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " countRele = " + String(conf.countRele));
|
|
|
|
|
|
if (conf.maxKW)
|
|
|
|
|
|
{
|
|
|
|
|
|
// conf.prcOnekWt /= conf.countRele;
|
|
|
|
|
|
conf.prcOnekWt = 100 / conf.maxKW;
|
|
|
|
|
|
// SerialPrint("i", "BoilerControl", "Процент одного кВт = " + String (conf.prcOnekWt) + "%");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (conf.countRele == 1 || _idPID == nullptr)
|
|
|
|
|
|
conf.ctrlType = true;
|
|
|
|
|
|
|
|
|
|
|
|
// IoTValue val;
|
|
|
|
|
|
// val.valD = conf.ctrlType;
|
|
|
|
|
|
if (_idCtrlType)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (conf.ctrlType == 0)
|
|
|
|
|
|
_idCtrlType->setValue("Модуляция", true);
|
|
|
|
|
|
else
|
|
|
|
|
|
_idCtrlType->setValue("Вкл/Выкл", true);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_idPID)
|
|
|
|
|
|
pid = ::atof(_idPID->getValue().c_str());
|
|
|
|
|
|
|
|
|
|
|
|
if (conf.antiFreez > 0 && state.Tboiler < (conf.antiFreez + 5))
|
|
|
|
|
|
{
|
|
|
|
|
|
state.antiFreezOn = true;
|
|
|
|
|
|
state.numStepOn = 1;
|
|
|
|
|
|
state.RelModLevel = conf.prcOnekWt * stepMap[1].pwr;
|
|
|
|
|
|
publishNew("status", "Анти-заморозка");
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Режим анти-заморозка");
|
|
|
|
|
|
sendTelegramm("Режим анти-заморозка");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (state.antiFreezOn)
|
|
|
|
|
|
{
|
|
|
|
|
|
state.numStepOn = 0;
|
|
|
|
|
|
state.RelModLevel = 0;
|
|
|
|
|
|
state.antiFreezOn = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-12 20:49:36 +03:00
|
|
|
|
if (state.Tboiler < conf.maxCH)
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (!state.antiFreezOn)
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (conf.autoPower)
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// if (dhw_ctrl)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// если есть модуль ГВС, то вызываем его логику включения тэнов
|
|
|
|
|
|
DHWControl::logicPowerDhw();
|
|
|
|
|
|
//}
|
|
|
|
|
|
if (!state.stateDHW) // Если уже включено ГВС, то нечего смотреть на отопление
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (set.cmd_chEnable)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " stepMap.size = " + String(stepMap.size()));
|
|
|
|
|
|
publishNew("status", "Штатный режим");
|
|
|
|
|
|
// включаем отопление
|
|
|
|
|
|
state.stateCH = 1;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
float TMAXcompare;
|
|
|
|
|
|
if (state.numStepOn == 0)
|
|
|
|
|
|
{ // сейчас нагрев выключен
|
|
|
|
|
|
TMAXcompare = set.TSetCH - conf.gistCH;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
else
|
|
|
|
|
|
{ // сейчас нагрев работает
|
|
|
|
|
|
TMAXcompare = set.TSetCH;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
|
|
|
|
|
|
if (state.Tboiler <= TMAXcompare && state.Tboiler > -5 && state.Tboiler < 120)
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (conf.ctrlType == 0)
|
|
|
|
|
|
{ // режим модуляции, это когда есть модуль ПИД и более одного реле
|
|
|
|
|
|
float pidStep = 100 / stepMap.size();
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " pidStep = " + String(pidStep));
|
|
|
|
|
|
for (size_t i = 0; i < stepMap.size(); i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (pid > 1 && pid > i * pidStep && pid <= pidStep * (i + 1))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " Modulation, stepON = " + String(i + 1));
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " Modulation, pid > " + String(i * pidStep) + "; pid <= " + String(pidStep * (i + 1)));
|
|
|
|
|
|
}
|
|
|
|
|
|
state.numStepOn = i + 1;
|
|
|
|
|
|
if (stepMap.find(state.numStepOn) != stepMap.end())
|
|
|
|
|
|
state.RelModLevel = conf.prcOnekWt * stepMap[state.numStepOn].pwr;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
state.RelModLevel = 0;
|
|
|
|
|
|
SerialPrint("E", "SmartBoiler", "НЕТ ТАКОГО Шага: " + String(state.numStepOn));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pid >= 100)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " Modulation, stepON = " + String(stepMap.size()));
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " Modulation, pid 100% ");
|
|
|
|
|
|
}
|
|
|
|
|
|
state.numStepOn = stepMap.size();
|
|
|
|
|
|
if (stepMap.find(state.numStepOn) != stepMap.end())
|
|
|
|
|
|
state.RelModLevel = 100;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
state.RelModLevel = 0;
|
|
|
|
|
|
SerialPrint("E", "SmartBoiler", "НЕТ ТАКОГО Шага: " + String(state.numStepOn));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{ // у нас релейный режим без ПИДа или с одним реле(тэном)
|
|
|
|
|
|
// TODO СЕЙЧАС ВКЛЮЧАЕМ ПЕРВЫЙ ШАГ НАГРЕВА, НАДО ЧТО ТО ПРИДУМАТЬ УМНОЕ
|
|
|
|
|
|
state.numStepOn = 1;
|
|
|
|
|
|
state.RelModLevel = conf.prcOnekWt * stepMap[1].pwr;
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " On|Off, stepON = " + String(state.numStepOn));
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
else
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
state.numStepOn = 0;
|
|
|
|
|
|
state.RelModLevel = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
publishNew("status", "Выкл отопление");
|
|
|
|
|
|
state.numStepOn = 0;
|
|
|
|
|
|
state.RelModLevel = 0;
|
|
|
|
|
|
state.stateCH = 0;
|
|
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " Выкл отопление ");
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
publishNew("status", "Ручной режим");
|
|
|
|
|
|
if (stepMap.find(state.numStepOn) != stepMap.end())
|
|
|
|
|
|
state.RelModLevel = conf.prcOnekWt * stepMap[state.numStepOn].pwr;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
SerialPrint("E", "SmartBoiler", "НЕТ ТАКОГО Шага: " + String(state.numStepOn));
|
|
|
|
|
|
set.cmd_chEnable = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!set.cmd_chEnable)
|
|
|
|
|
|
{
|
|
|
|
|
|
state.numStepOn = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
state.RelModLevel = 0;
|
2024-02-26 23:31:12 +03:00
|
|
|
|
state.stateCH = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (_debug > 0)
|
|
|
|
|
|
SerialPrint("I", "SmartBoiler", " Ручной режим, stepON = " + String(state.numStepOn));
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
else
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
state.numStepOn = 0;
|
|
|
|
|
|
state.RelModLevel = 0;
|
|
|
|
|
|
state.stateCH = 0;
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Выключились по макс. температуре, Tboiler = " + String(state.Tboiler));
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
static bool prev_flame;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (state.numStepOn > 0)
|
|
|
|
|
|
{
|
2024-02-12 20:49:36 +03:00
|
|
|
|
state.fl_flame = true; // если хоть одно реле включено, то выставляем флаг горелки
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// если хоть одно реле включено, то включаем насос
|
|
|
|
|
|
if (_relePump)
|
|
|
|
|
|
//_relePump->setValue("1", true);
|
|
|
|
|
|
updateReleState(_relePump, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// если все реле выключены
|
|
|
|
|
|
if (_relePump)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (state.Tboiler > conf.minCH && set.cmd_chEnable) // НО температура ещё горячая и при этом отопление включено, то включаем насос
|
|
|
|
|
|
//_relePump->setValue("1", true);
|
|
|
|
|
|
updateReleState(_relePump, 1);
|
|
|
|
|
|
else // Если температура в котле уже остыла, или отопление нам не нужно (летом нагрели воду-пусть котел сам остывает без насоса), то выключаем насос
|
|
|
|
|
|
//_relePump->setValue("0", true);
|
|
|
|
|
|
updateReleState(_relePump, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
|
|
|
|
|
if (state.fl_flame && prev_flame != state.fl_flame)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (conf.changeRele)
|
|
|
|
|
|
{
|
|
|
|
|
|
state.currentRele++;
|
|
|
|
|
|
if (state.currentRele >= conf.countRele)
|
|
|
|
|
|
state.currentRele = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
prev_flame = state.fl_flame;
|
|
|
|
|
|
|
|
|
|
|
|
bool fl_pump = false;
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// onStepPower(state.numStepOn);
|
|
|
|
|
|
if (state.numStepOn > 0)
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (stepMap.find(state.numStepOn) == stepMap.end())
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
SerialPrint("E", "SmartBoiler", "НЕТ ТАКОГО Шага: " + String(state.numStepOn));
|
|
|
|
|
|
state.numStepOn = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool releon;
|
|
|
|
|
|
if (conf.changeRele)
|
|
|
|
|
|
{
|
|
|
|
|
|
// количество реле в текущем шаге
|
|
|
|
|
|
int countOn = stepMap[state.numStepOn].listIDRele.size();
|
|
|
|
|
|
bool crOn = false;
|
|
|
|
|
|
// перебираем все реле
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
for (auto it = mapRele.begin(); it != mapRele.end(); it++)
|
|
|
|
|
|
{
|
|
|
|
|
|
crOn = false;
|
|
|
|
|
|
// перебираем сколько реле нам нужно включить в текущем шаге
|
|
|
|
|
|
for (size_t c = 0; c < countOn; c++)
|
|
|
|
|
|
{
|
|
|
|
|
|
// state.currentRele - с какого реле нужно начать включение
|
|
|
|
|
|
int cr = state.currentRele + c;
|
|
|
|
|
|
if (cr >= mapRele.size())
|
|
|
|
|
|
cr = 0;
|
|
|
|
|
|
if (i == cr)
|
|
|
|
|
|
crOn = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (crOn)
|
|
|
|
|
|
{
|
|
|
|
|
|
updateReleState(it->second, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
updateReleState(it->second, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
i++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
for (auto it = mapRele.begin(); it != mapRele.end(); it++)
|
|
|
|
|
|
{
|
|
|
|
|
|
releon = false;
|
|
|
|
|
|
for (size_t r = 0; r < stepMap[state.numStepOn].listIDRele.size(); r++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (stepMap[state.numStepOn].listIDRele[r] == it->first)
|
|
|
|
|
|
releon = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (releon)
|
|
|
|
|
|
updateReleState(it->second, 1);
|
|
|
|
|
|
else
|
|
|
|
|
|
updateReleState(it->second, 0);
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
for (auto it = mapRele.begin(); it != mapRele.end(); it++)
|
|
|
|
|
|
{
|
|
|
|
|
|
updateReleState(it->second, 0);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
|
2024-02-12 20:49:36 +03:00
|
|
|
|
IoTValue val;
|
|
|
|
|
|
if (_idModLevel)
|
|
|
|
|
|
{
|
|
|
|
|
|
val.valD = state.RelModLevel;
|
|
|
|
|
|
_idModLevel->setValue(val, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_idStateCH)
|
|
|
|
|
|
{
|
|
|
|
|
|
val.valD = state.stateCH;
|
|
|
|
|
|
_idStateCH->setValue(val, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_idStateFlame)
|
|
|
|
|
|
{
|
|
|
|
|
|
val.valD = state.fl_flame;
|
|
|
|
|
|
_idStateFlame->setValue(val, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
publishNew("autoPower", String(conf.autoPower));
|
|
|
|
|
|
|
|
|
|
|
|
// setValue(String(stepMap[state.numStepOn].pwr));
|
|
|
|
|
|
// publishNew("ModLevel", String(state.RelModLevel));
|
|
|
|
|
|
// publishNew("StateCH", String(state.stateCH));
|
|
|
|
|
|
// publishNew("controlType", String(conf.ctrlType));
|
|
|
|
|
|
// publishNew("StateFlame", String(state.fl_flame));
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//============================== ОБЕСПЕЧЕНИЕ РАБОТЫ IoTMANAGER =====================================
|
2024-02-26 23:31:12 +03:00
|
|
|
|
/*
|
|
|
|
|
|
// конфигурирование котла в зависимости от настроек
|
|
|
|
|
|
void configuration()
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
state.fl_flame = state.stateCH = 0;
|
|
|
|
|
|
conf.countRele = conf.prcOnekWt = 0;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (conf.pump)
|
|
|
|
|
|
{
|
|
|
|
|
|
_relePump = findIoTItem("_relePump");
|
|
|
|
|
|
if (_relePump)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Initialized relay pump");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
updateStateboiler();
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
*/
|
|
|
|
|
|
void addStepPower(std::vector<IoTValue> ¶m)
|
|
|
|
|
|
{
|
|
|
|
|
|
// int step = param[0].valD;
|
|
|
|
|
|
// float pwr = param[1].valD;
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Add Step Power - " + param[0].valS + " , power - " + param[1].valS);
|
|
|
|
|
|
StepPower step; // = new StepPower;
|
|
|
|
|
|
step.pwr = param[1].valD;
|
|
|
|
|
|
for (size_t i = 2; i < param.size(); i++)
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
step.listIDRele.push_back(param[i].valS);
|
|
|
|
|
|
tmp = findIoTItem(param[i].valS);
|
|
|
|
|
|
if (tmp)
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// vectorRele.push_back(tmp);
|
|
|
|
|
|
mapRele[tmp->getID()] = tmp;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
SerialPrint("i", "BoilerControl", "initialized relay - " + tmp->getID());
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
else
|
2024-02-26 23:31:12 +03:00
|
|
|
|
{
|
|
|
|
|
|
SerialPrint("E", "BoilerControl", "Error initialized relay - " + param[i].valS);
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
stepMap[(int)param[0].valD] = step;
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void doByInterval()
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
// configured();
|
|
|
|
|
|
updateStateboiler();
|
|
|
|
|
|
logicPowerOn();
|
|
|
|
|
|
if (stepMap.find(state.numStepOn) != stepMap.end())
|
|
|
|
|
|
setValue(String(stepMap[state.numStepOn].pwr));
|
2024-02-26 23:47:09 +03:00
|
|
|
|
else
|
|
|
|
|
|
setValue("0");
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-26 23:31:12 +03:00
|
|
|
|
/*
|
|
|
|
|
|
// Основной цикл программы
|
|
|
|
|
|
void loop()
|
2024-02-12 20:49:36 +03:00
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
unsigned long new_ts = millis();
|
|
|
|
|
|
int delay = 1000;
|
|
|
|
|
|
if (new_ts - ts > delay)
|
|
|
|
|
|
{
|
|
|
|
|
|
ts = new_ts;
|
|
|
|
|
|
updateStateboiler();
|
|
|
|
|
|
logicPowerOn();
|
|
|
|
|
|
}
|
|
|
|
|
|
// для новых версий IoTManager
|
|
|
|
|
|
IoTItem::loop();
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
*/
|
2024-02-12 20:49:36 +03:00
|
|
|
|
// Комманды из сценария
|
|
|
|
|
|
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
|
|
|
|
|
{
|
|
|
|
|
|
IoTValue val;
|
|
|
|
|
|
if (command == "CHSet")
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (param.size())
|
|
|
|
|
|
{
|
|
|
|
|
|
set.TSetCH = param[0].valD;
|
|
|
|
|
|
set.TSetCH = constrain(set.TSetCH, conf.minCH, conf.maxCH);
|
|
|
|
|
|
val.valD = set.TSetCH;
|
|
|
|
|
|
if (_idSetCH)
|
|
|
|
|
|
_idSetCH->setValue(val, true);
|
|
|
|
|
|
// publishNew("SetCH", String(set.TSetCH));
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Scenario CHSet ");
|
|
|
|
|
|
}
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
else if (command == "CHEnable")
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
if (param.size())
|
|
|
|
|
|
{
|
|
|
|
|
|
set.cmd_chEnable = param[0].valD;
|
|
|
|
|
|
val.valD = set.cmd_chEnable;
|
|
|
|
|
|
if (_idCmdCH)
|
|
|
|
|
|
_idCmdCH->setValue(val, true);
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Scenario CHEnable ");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (command == "addStepPower")
|
|
|
|
|
|
{
|
|
|
|
|
|
if (param.size() > 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
// addstep(step, power, param);
|
|
|
|
|
|
addStepPower(param);
|
|
|
|
|
|
// SerialPrint("i", "BoilerControl", "Scenario addStep ");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (command == "onStepPower")
|
|
|
|
|
|
{
|
|
|
|
|
|
if (param.size())
|
|
|
|
|
|
{
|
|
|
|
|
|
conf.autoPower = false;
|
|
|
|
|
|
// step[param[0].valD] = true;
|
|
|
|
|
|
// onStepPower(param[0].valD);
|
|
|
|
|
|
state.numStepOn = param[0].valD;
|
|
|
|
|
|
set.cmd_chEnable = true;
|
|
|
|
|
|
val.valD = set.cmd_chEnable;
|
|
|
|
|
|
|
|
|
|
|
|
if (_idCmdCH)
|
|
|
|
|
|
_idCmdCH->setValue(val, true);
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Scenario onStep, Hand Power On ");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (command == "autoPower")
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
conf.autoPower = true;
|
|
|
|
|
|
SerialPrint("i", "BoilerControl", "Auto Power On");
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// обновление данных от датчиков
|
|
|
|
|
|
void updateStateboiler()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_idTboiler)
|
|
|
|
|
|
state.Tboiler = ::atof(_idTboiler->getValue().c_str());
|
|
|
|
|
|
if (_idTret)
|
|
|
|
|
|
state.Tret = ::atof(_idTret->getValue().c_str());
|
|
|
|
|
|
if (_idToutside)
|
|
|
|
|
|
state.Toutside = ::atof(_idToutside->getValue().c_str());
|
|
|
|
|
|
|
|
|
|
|
|
// if (_idStateCH)
|
|
|
|
|
|
// state.stateCH = ::atoi(_idStateCH->getValue().c_str());
|
|
|
|
|
|
// if (_idStateFlame)
|
|
|
|
|
|
// state.fl_flame = ::atoi(_idStateFlame->getValue().c_str());
|
|
|
|
|
|
// if (_idModLevel)
|
|
|
|
|
|
// state.RelModLevel = ::atof(_idModLevel->getValue().c_str());
|
|
|
|
|
|
if (_idCmdCH)
|
|
|
|
|
|
set.cmd_chEnable = ::atoi(_idCmdCH->getValue().c_str());
|
|
|
|
|
|
if (_idSetCH)
|
|
|
|
|
|
set.TSetCH = ::atof(_idSetCH->getValue().c_str());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
~BoilerControl()
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
stepMap.clear();
|
|
|
|
|
|
// vectorRele.clear();
|
|
|
|
|
|
mapRele.clear();
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void *getAPI_SmartBoiler(String subtype, String param)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (subtype == F("BoilerControl"))
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
return new _Boiler_v2::BoilerControl(param);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
else if (subtype == F("DHWControl"))
|
|
|
|
|
|
{
|
2024-02-26 23:31:12 +03:00
|
|
|
|
return new _Boiler_v2::DHWControl(param);
|
2024-02-12 20:49:36 +03:00
|
|
|
|
}
|
2024-02-26 23:31:12 +03:00
|
|
|
|
else if (subtype == F("OpenThermSlave"))
|
|
|
|
|
|
{
|
|
|
|
|
|
return new _Boiler_v2::OpenThermSlave(param);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-12 20:49:36 +03:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|