mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-26 14:12:16 +03:00
Модуль Benchmark
This commit is contained in:
@@ -7,3 +7,4 @@ extern std::list<IoTItem*> IoTItems; // вектор ссылок базово
|
|||||||
extern void configure(String path);
|
extern void configure(String path);
|
||||||
void clearConfigure();
|
void clearConfigure();
|
||||||
extern IoTItem* myIoTItem;
|
extern IoTItem* myIoTItem;
|
||||||
|
extern IoTBench* myIoTBernch;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#include "utils/StringUtils.h"
|
#include "utils/StringUtils.h"
|
||||||
#include "PeriodicTasks.h"
|
#include "PeriodicTasks.h"
|
||||||
#include "classes/IoTGpio.h"
|
#include "classes/IoTGpio.h"
|
||||||
|
#include "classes/IoTBench.h"
|
||||||
|
|
||||||
/*********************************************************************************************************************
|
/*********************************************************************************************************************
|
||||||
*****************************************глобальные объекты классов***************************************************
|
*****************************************глобальные объекты классов***************************************************
|
||||||
@@ -58,6 +59,8 @@ extern IoTGpio IoTgpio;
|
|||||||
extern IoTItem* rtcItem;
|
extern IoTItem* rtcItem;
|
||||||
//extern IoTItem* camItem;
|
//extern IoTItem* camItem;
|
||||||
extern IoTItem* tlgrmItem;
|
extern IoTItem* tlgrmItem;
|
||||||
|
extern IoTBench* benchLoadItem;
|
||||||
|
extern IoTBench* benchTaskItem;
|
||||||
|
|
||||||
extern TickerScheduler ts;
|
extern TickerScheduler ts;
|
||||||
extern WiFiClient espClient;
|
extern WiFiClient espClient;
|
||||||
|
|||||||
30
include/classes/IoTBench.h
Normal file
30
include/classes/IoTBench.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Global.h"
|
||||||
|
#include "classes/IoTItem.h"
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
struct ItemBench
|
||||||
|
{
|
||||||
|
uint32_t sumloopTime = 0;
|
||||||
|
uint32_t loopTime = 0;
|
||||||
|
uint32_t loopTimeMax_p = 0;
|
||||||
|
uint32_t loopTimeMax_glob = 0;
|
||||||
|
uint32_t count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class IoTBench : public IoTItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IoTBench(const String ¶meters);
|
||||||
|
~IoTBench();
|
||||||
|
|
||||||
|
virtual void preLoadFunction();
|
||||||
|
virtual void postLoadFunction();
|
||||||
|
virtual void preTaskFunction(const String &id);
|
||||||
|
virtual void postTaskFunction(const String &id);
|
||||||
|
protected:
|
||||||
|
std::map<String, ItemBench *> banchItems;
|
||||||
|
};
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "classes/IoTGpio.h"
|
#include "classes/IoTGpio.h"
|
||||||
|
//#include "classes/IoTBench.h"
|
||||||
|
|
||||||
|
class IoTBench;
|
||||||
|
|
||||||
struct IoTValue {
|
struct IoTValue {
|
||||||
float valD = 0;
|
float valD = 0;
|
||||||
@@ -53,6 +56,9 @@ class IoTItem {
|
|||||||
virtual IoTItem* getRtcDriver();
|
virtual IoTItem* getRtcDriver();
|
||||||
//virtual IoTItem* getCAMDriver();
|
//virtual IoTItem* getCAMDriver();
|
||||||
virtual IoTItem* getTlgrmDriver();
|
virtual IoTItem* getTlgrmDriver();
|
||||||
|
//virtual IoTBench* getBenchmark();
|
||||||
|
virtual IoTBench*getBenchmarkTask();
|
||||||
|
virtual IoTBench*getBenchmarkLoad();
|
||||||
virtual unsigned long getRtcUnixTime();
|
virtual unsigned long getRtcUnixTime();
|
||||||
|
|
||||||
// делаем доступным модулям отправку сообщений в телеграм
|
// делаем доступным модулям отправку сообщений в телеграм
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ void configure(String path) {
|
|||||||
if (driver = myIoTItem->getRtcDriver()) rtcItem = (IoTItem*)driver;
|
if (driver = myIoTItem->getRtcDriver()) rtcItem = (IoTItem*)driver;
|
||||||
// пробуем спросить драйвер CAM
|
// пробуем спросить драйвер CAM
|
||||||
//if (driver = myIoTItem->getCAMDriver()) camItem = (IoTItem*)driver;
|
//if (driver = myIoTItem->getCAMDriver()) camItem = (IoTItem*)driver;
|
||||||
|
// пробуем спросить драйвер Benchmark
|
||||||
|
if (driver = myIoTItem->getBenchmarkTask()) benchTaskItem = ((IoTBench*)driver);
|
||||||
|
if (driver = myIoTItem->getBenchmarkLoad()) benchLoadItem = ((IoTBench*)driver);
|
||||||
// пробуем спросить драйвер Telegram_v2
|
// пробуем спросить драйвер Telegram_v2
|
||||||
if (driver = myIoTItem->getTlgrmDriver()) tlgrmItem = (IoTItem*)driver;
|
if (driver = myIoTItem->getTlgrmDriver()) tlgrmItem = (IoTItem*)driver;
|
||||||
IoTItems.push_back(myIoTItem);
|
IoTItems.push_back(myIoTItem);
|
||||||
@@ -48,7 +51,7 @@ void clearConfigure() {
|
|||||||
Serial.printf("Start clearing config\n");
|
Serial.printf("Start clearing config\n");
|
||||||
rtcItem = nullptr;
|
rtcItem = nullptr;
|
||||||
//camItem = nullptr;
|
//camItem = nullptr;
|
||||||
tlgrmItem = nullptr;
|
tlgrmItem = nullptr;
|
||||||
IoTgpio.clearDrivers();
|
IoTgpio.clearDrivers();
|
||||||
|
|
||||||
for (std::list<IoTItem*>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) {
|
for (std::list<IoTItem*>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) {
|
||||||
@@ -58,4 +61,6 @@ void clearConfigure() {
|
|||||||
IoTItems.clear();
|
IoTItems.clear();
|
||||||
|
|
||||||
valuesFlashJson.clear();
|
valuesFlashJson.clear();
|
||||||
|
benchTaskItem = nullptr;
|
||||||
|
benchLoadItem = nullptr;
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,8 @@ IoTGpio IoTgpio(0);
|
|||||||
IoTItem* rtcItem = nullptr;
|
IoTItem* rtcItem = nullptr;
|
||||||
//IoTItem* camItem = nullptr;
|
//IoTItem* camItem = nullptr;
|
||||||
IoTItem* tlgrmItem = nullptr;
|
IoTItem* tlgrmItem = nullptr;
|
||||||
|
IoTBench* benchTaskItem = nullptr;
|
||||||
|
IoTBench* benchLoadItem = nullptr;
|
||||||
String settingsFlashJson = "{}"; // переменная в которой хранятся все настройки, находится в оперативной памяти и синхронизированна с flash памятью
|
String settingsFlashJson = "{}"; // переменная в которой хранятся все настройки, находится в оперативной памяти и синхронизированна с flash памятью
|
||||||
String valuesFlashJson = "{}"; // переменная в которой хранятся все значения элементов, которые необходимо сохранить на flash. Находится в оперативной памяти и синхронизированна с flash памятью
|
String valuesFlashJson = "{}"; // переменная в которой хранятся все значения элементов, которые необходимо сохранить на flash. Находится в оперативной памяти и синхронизированна с flash памятью
|
||||||
String errorsHeapJson = "{}"; // переменная в которой хранятся все ошибки, находится в оперативной памяти только
|
String errorsHeapJson = "{}"; // переменная в которой хранятся все ошибки, находится в оперативной памяти только
|
||||||
|
|||||||
24
src/Main.cpp
24
src/Main.cpp
@@ -16,8 +16,9 @@ String volStrForSave = "";
|
|||||||
void elementsLoop() {
|
void elementsLoop() {
|
||||||
// передаем управление каждому элементу конфигурации для выполнения своих функций
|
// передаем управление каждому элементу конфигурации для выполнения своих функций
|
||||||
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) {
|
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) {
|
||||||
|
if (benchTaskItem) benchTaskItem->preTaskFunction((*it)->getID());
|
||||||
(*it)->loop();
|
(*it)->loop();
|
||||||
|
if (benchTaskItem) benchTaskItem->postTaskFunction((*it)->getID());
|
||||||
// if ((*it)->iAmDead) {
|
// if ((*it)->iAmDead) {
|
||||||
if (!((*it)->iAmLocal) && (*it)->getIntFromNet() == -1) {
|
if (!((*it)->iAmLocal) && (*it)->getIntFromNet() == -1) {
|
||||||
delete *it;
|
delete *it;
|
||||||
@@ -150,13 +151,13 @@ void setup() {
|
|||||||
iotScen.loadScenario("/scenario.txt");
|
iotScen.loadScenario("/scenario.txt");
|
||||||
// создаем событие завершения инициализации основных моментов для возможности выполнения блока кода при загрузке
|
// создаем событие завершения инициализации основных моментов для возможности выполнения блока кода при загрузке
|
||||||
createItemFromNet("onInit", "1", 1);
|
createItemFromNet("onInit", "1", 1);
|
||||||
elementsLoop();
|
// elementsLoop(); //Для работы MQTT Брокера перенес ниже, иначе брокер падает если вызван до routerConnect();
|
||||||
|
|
||||||
stopErrorMarker(SETUPSCEN_ERRORMARKER);
|
stopErrorMarker(SETUPSCEN_ERRORMARKER);
|
||||||
|
|
||||||
initErrorMarker(SETUPINET_ERRORMARKER);
|
initErrorMarker(SETUPINET_ERRORMARKER);
|
||||||
|
|
||||||
// подключаемся к роутеру
|
// подключаемся к роутеру
|
||||||
routerConnect();
|
routerConnect();
|
||||||
|
|
||||||
// инициализация асинхронного веб сервера и веб сокетов
|
// инициализация асинхронного веб сервера и веб сокетов
|
||||||
@@ -179,6 +180,7 @@ void setup() {
|
|||||||
|
|
||||||
initErrorMarker(SETUPLAST_ERRORMARKER);
|
initErrorMarker(SETUPLAST_ERRORMARKER);
|
||||||
|
|
||||||
|
elementsLoop();
|
||||||
// NTP
|
// NTP
|
||||||
ntpInit();
|
ntpInit();
|
||||||
|
|
||||||
@@ -224,31 +226,35 @@ void loop() {
|
|||||||
#ifdef LOOP_DEBUG
|
#ifdef LOOP_DEBUG
|
||||||
unsigned long st = millis();
|
unsigned long st = millis();
|
||||||
#endif
|
#endif
|
||||||
|
if (benchLoadItem) benchLoadItem->preLoadFunction();
|
||||||
|
if (benchTaskItem) benchTaskItem->preTaskFunction("TickerScheduler");
|
||||||
initErrorMarker(TICKER_ERRORMARKER);
|
initErrorMarker(TICKER_ERRORMARKER);
|
||||||
ts.update();
|
ts.update();
|
||||||
stopErrorMarker(TICKER_ERRORMARKER);
|
stopErrorMarker(TICKER_ERRORMARKER);
|
||||||
|
if (benchTaskItem) benchTaskItem->postTaskFunction("TickerScheduler");
|
||||||
|
if (benchTaskItem) benchTaskItem->preTaskFunction("webServer");
|
||||||
#ifdef STANDARD_WEB_SERVER
|
#ifdef STANDARD_WEB_SERVER
|
||||||
initErrorMarker(HTTP_ERRORMARKER);
|
initErrorMarker(HTTP_ERRORMARKER);
|
||||||
HTTP.handleClient();
|
HTTP.handleClient();
|
||||||
stopErrorMarker(HTTP_ERRORMARKER);
|
stopErrorMarker(HTTP_ERRORMARKER);
|
||||||
#endif
|
#endif
|
||||||
|
if (benchTaskItem) benchTaskItem->postTaskFunction("webServer");
|
||||||
|
if (benchTaskItem) benchTaskItem->preTaskFunction("webSocket");
|
||||||
#ifdef STANDARD_WEB_SOCKETS
|
#ifdef STANDARD_WEB_SOCKETS
|
||||||
initErrorMarker(SOCKETS_ERRORMARKER);
|
initErrorMarker(SOCKETS_ERRORMARKER);
|
||||||
standWebSocket.loop();
|
standWebSocket.loop();
|
||||||
stopErrorMarker(SOCKETS_ERRORMARKER);
|
stopErrorMarker(SOCKETS_ERRORMARKER);
|
||||||
#endif
|
#endif
|
||||||
|
if (benchTaskItem) benchTaskItem->postTaskFunction("webSocket");
|
||||||
|
if (benchTaskItem) benchTaskItem->preTaskFunction("mqtt");
|
||||||
initErrorMarker(MQTT_ERRORMARKER);
|
initErrorMarker(MQTT_ERRORMARKER);
|
||||||
mqttLoop();
|
mqttLoop();
|
||||||
stopErrorMarker(MQTT_ERRORMARKER);
|
stopErrorMarker(MQTT_ERRORMARKER);
|
||||||
|
if (benchTaskItem) benchTaskItem->postTaskFunction("mqtt");
|
||||||
initErrorMarker(MODULES_ERRORMARKER);
|
initErrorMarker(MODULES_ERRORMARKER);
|
||||||
elementsLoop();
|
elementsLoop();
|
||||||
stopErrorMarker(MODULES_ERRORMARKER);
|
stopErrorMarker(MODULES_ERRORMARKER);
|
||||||
|
if (benchLoadItem) benchLoadItem->postLoadFunction();
|
||||||
// #ifdef LOOP_DEBUG
|
// #ifdef LOOP_DEBUG
|
||||||
// loopPeriod = millis() - st;
|
// loopPeriod = millis() - st;
|
||||||
// if (loopPeriod > 2) Serial.println(loopPeriod);
|
// if (loopPeriod > 2) Serial.println(loopPeriod);
|
||||||
|
|||||||
@@ -1,812 +0,0 @@
|
|||||||
/*
|
|
||||||
OpenTherm.cpp - OpenTherm Communication Library For Arduino, ESP8266
|
|
||||||
Copyright 2018, Ihor Melnyk
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "OpenTherm.h"
|
|
||||||
|
|
||||||
OpenTherm::OpenTherm(int inPin, int outPin, bool isSlave) : status(OpenThermStatus::NOT_INITIALIZED),
|
|
||||||
inPin(inPin),
|
|
||||||
outPin(outPin),
|
|
||||||
isSlave(isSlave),
|
|
||||||
response(0),
|
|
||||||
responseStatus(OpenThermResponseStatus::NONE),
|
|
||||||
responseTimestamp(0),
|
|
||||||
handleInterruptCallback(NULL),
|
|
||||||
processResponseCallback(NULL)
|
|
||||||
{
|
|
||||||
imitFlag = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenTherm::begin(void (*handleInterruptCallback)(void), void (*processResponseCallback)(unsigned long, OpenThermResponseStatus))
|
|
||||||
{
|
|
||||||
pinMode(inPin, INPUT);
|
|
||||||
pinMode(outPin, OUTPUT);
|
|
||||||
if (handleInterruptCallback != NULL)
|
|
||||||
{
|
|
||||||
this->handleInterruptCallback = handleInterruptCallback;
|
|
||||||
attachInterrupt(digitalPinToInterrupt(inPin), handleInterruptCallback, CHANGE);
|
|
||||||
}
|
|
||||||
activateBoiler();
|
|
||||||
status = OpenThermStatus::READY;
|
|
||||||
this->processResponseCallback = processResponseCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenTherm::begin(void (*handleInterruptCallback)(void))
|
|
||||||
{
|
|
||||||
begin(handleInterruptCallback, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IRAM_ATTR OpenTherm::isReady()
|
|
||||||
{
|
|
||||||
return status == OpenThermStatus::READY;
|
|
||||||
}
|
|
||||||
|
|
||||||
int IRAM_ATTR OpenTherm::readState()
|
|
||||||
{
|
|
||||||
return digitalRead(inPin);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenTherm::setActiveState()
|
|
||||||
{
|
|
||||||
digitalWrite(outPin, LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenTherm::setIdleState()
|
|
||||||
{
|
|
||||||
digitalWrite(outPin, HIGH);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenTherm::activateBoiler()
|
|
||||||
{
|
|
||||||
setIdleState();
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenTherm::sendBit(bool high)
|
|
||||||
{
|
|
||||||
if (high)
|
|
||||||
setActiveState();
|
|
||||||
else
|
|
||||||
setIdleState();
|
|
||||||
delayMicroseconds(500);
|
|
||||||
if (high)
|
|
||||||
setIdleState();
|
|
||||||
else
|
|
||||||
setActiveState();
|
|
||||||
delayMicroseconds(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::sendRequestAync(unsigned long request)
|
|
||||||
{
|
|
||||||
// Serial.println("Request: " + String(request, HEX));
|
|
||||||
noInterrupts();
|
|
||||||
const bool ready = isReady();
|
|
||||||
interrupts();
|
|
||||||
|
|
||||||
if (!ready)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
status = OpenThermStatus::REQUEST_SENDING;
|
|
||||||
response = 0;
|
|
||||||
responseStatus = OpenThermResponseStatus::NONE;
|
|
||||||
// Prevent switching to other tasks as there is a delay within sendBit
|
|
||||||
#ifdef ESP32
|
|
||||||
// vTaskSuspendAll();
|
|
||||||
#endif
|
|
||||||
sendBit(HIGH); // start bit
|
|
||||||
for (int i = 31; i >= 0; i--)
|
|
||||||
{
|
|
||||||
sendBit(bitRead(request, i));
|
|
||||||
}
|
|
||||||
sendBit(HIGH); // stop bit
|
|
||||||
setIdleState();
|
|
||||||
#ifdef ESP32
|
|
||||||
// xTaskResumeAll();
|
|
||||||
#endif
|
|
||||||
status = OpenThermStatus::RESPONSE_WAITING;
|
|
||||||
responseTimestamp = micros();
|
|
||||||
if (imitFlag)
|
|
||||||
ImitationResponse(request);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long OpenTherm::sendRequest(unsigned long request)
|
|
||||||
{
|
|
||||||
if (!sendRequestAync(request))
|
|
||||||
return 0;
|
|
||||||
while (!isReady())
|
|
||||||
{
|
|
||||||
process();
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::sendResponse(unsigned long request)
|
|
||||||
{
|
|
||||||
status = OpenThermStatus::REQUEST_SENDING;
|
|
||||||
response = 0;
|
|
||||||
responseStatus = OpenThermResponseStatus::NONE;
|
|
||||||
// Prevent switching to other tasks as there is a delay within sendBit
|
|
||||||
#ifdef ESP32
|
|
||||||
// vTaskSuspendAll();
|
|
||||||
#endif
|
|
||||||
sendBit(HIGH); // start bit
|
|
||||||
for (int i = 31; i >= 0; i--)
|
|
||||||
{
|
|
||||||
sendBit(bitRead(request, i));
|
|
||||||
}
|
|
||||||
sendBit(HIGH); // stop bit
|
|
||||||
setIdleState();
|
|
||||||
#ifdef ESP32
|
|
||||||
// xTaskResumeAll();
|
|
||||||
#endif
|
|
||||||
status = OpenThermStatus::READY;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long OpenTherm::getLastResponse()
|
|
||||||
{
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenThermResponseStatus OpenTherm::getLastResponseStatus()
|
|
||||||
{
|
|
||||||
return responseStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR OpenTherm::handleInterrupt()
|
|
||||||
{
|
|
||||||
if (isReady())
|
|
||||||
{
|
|
||||||
if (isSlave && readState() == HIGH)
|
|
||||||
{
|
|
||||||
status = OpenThermStatus::RESPONSE_WAITING;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long newTs = micros();
|
|
||||||
if (status == OpenThermStatus::RESPONSE_WAITING)
|
|
||||||
{
|
|
||||||
if (readState() == HIGH)
|
|
||||||
{
|
|
||||||
status = OpenThermStatus::RESPONSE_START_BIT;
|
|
||||||
responseTimestamp = newTs;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Error start bit / Ошибка стартового бита
|
|
||||||
status = OpenThermStatus::RESPONSE_INVALID;
|
|
||||||
responseTimestamp = newTs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (status == OpenThermStatus::RESPONSE_START_BIT)
|
|
||||||
{
|
|
||||||
if ((newTs - responseTimestamp < 750) && readState() == LOW)
|
|
||||||
{
|
|
||||||
status = OpenThermStatus::RESPONSE_RECEIVING;
|
|
||||||
responseTimestamp = newTs;
|
|
||||||
responseBitIndex = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Error Start_bit LOW 750mks / Ошибка стартового бита по тылу (нет LOW через 750мкс)
|
|
||||||
status = OpenThermStatus::RESPONSE_INVALID;
|
|
||||||
responseTimestamp = newTs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (status == OpenThermStatus::RESPONSE_RECEIVING)
|
|
||||||
{
|
|
||||||
// unsigned long bitDuration = newTs - responseTimestamp;
|
|
||||||
// В новой спецификации стоповый бит не обязателен. Если не дождались, всё равно попробуем разобрать
|
|
||||||
if ((newTs - responseTimestamp) > 750 && (newTs - responseTimestamp) < 1300)
|
|
||||||
{
|
|
||||||
if (responseBitIndex < 32)
|
|
||||||
{
|
|
||||||
response = (response << 1) | !readState();
|
|
||||||
responseTimestamp = newTs;
|
|
||||||
responseBitIndex++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // stop bit
|
|
||||||
status = OpenThermStatus::RESPONSE_READY;
|
|
||||||
responseTimestamp = newTs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenTherm::process()
|
|
||||||
{
|
|
||||||
noInterrupts();
|
|
||||||
OpenThermStatus st = status;
|
|
||||||
unsigned long ts = responseTimestamp;
|
|
||||||
interrupts();
|
|
||||||
|
|
||||||
if (st == OpenThermStatus::READY)
|
|
||||||
return;
|
|
||||||
unsigned long newTs = micros();
|
|
||||||
if (st != OpenThermStatus::NOT_INITIALIZED && st != OpenThermStatus::DELAY && (newTs - ts) > 1000000)
|
|
||||||
{
|
|
||||||
status = OpenThermStatus::READY;
|
|
||||||
responseStatus = OpenThermResponseStatus::TIMEOUT;
|
|
||||||
if (processResponseCallback != NULL)
|
|
||||||
{
|
|
||||||
processResponseCallback(response, responseStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (st == OpenThermStatus::RESPONSE_INVALID)
|
|
||||||
{
|
|
||||||
status = OpenThermStatus::DELAY;
|
|
||||||
responseStatus = OpenThermResponseStatus::INVALID;
|
|
||||||
if (processResponseCallback != NULL)
|
|
||||||
{
|
|
||||||
processResponseCallback(response, responseStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (st == OpenThermStatus::RESPONSE_READY)
|
|
||||||
{
|
|
||||||
status = OpenThermStatus::DELAY;
|
|
||||||
responseStatus = (isSlave ? isValidRequest(response) : isValidResponse(response)) ? OpenThermResponseStatus::SUCCESS : OpenThermResponseStatus::INVALID;
|
|
||||||
// Error msgType (READ_ACK | WRITE_ACK) is Header
|
|
||||||
if (processResponseCallback != NULL)
|
|
||||||
{
|
|
||||||
processResponseCallback(response, responseStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (st == OpenThermStatus::DELAY)
|
|
||||||
{
|
|
||||||
if ((newTs - ts) > 100000)
|
|
||||||
{
|
|
||||||
status = OpenThermStatus::READY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::parity(unsigned long frame) // odd parity
|
|
||||||
{
|
|
||||||
byte p = 0;
|
|
||||||
while (frame > 0)
|
|
||||||
{
|
|
||||||
if (frame & 1)
|
|
||||||
p++;
|
|
||||||
frame = frame >> 1;
|
|
||||||
}
|
|
||||||
return (p & 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenThermMessageType OpenTherm::getMessageType(unsigned long message)
|
|
||||||
{
|
|
||||||
OpenThermMessageType msg_type = static_cast<OpenThermMessageType>((message >> 28) & 7);
|
|
||||||
return msg_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenThermMessageID OpenTherm::getDataID(unsigned long frame)
|
|
||||||
{
|
|
||||||
return (OpenThermMessageID)((frame >> 16) & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long OpenTherm::buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data)
|
|
||||||
{
|
|
||||||
unsigned long request = data;
|
|
||||||
if (type == OpenThermMessageType::WRITE_DATA)
|
|
||||||
{
|
|
||||||
request |= 1ul << 28;
|
|
||||||
}
|
|
||||||
request |= ((unsigned long)id) << 16;
|
|
||||||
if (parity(request))
|
|
||||||
request |= (1ul << 31);
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
unsigned long OpenTherm::buildRequestID(OpenThermMessageType type, unsigned int id, unsigned int data)
|
|
||||||
{
|
|
||||||
unsigned long request = data;
|
|
||||||
if (type == OpenThermMessageType::WRITE_DATA)
|
|
||||||
{
|
|
||||||
request |= 1ul << 28;
|
|
||||||
}
|
|
||||||
request |= ((unsigned long)id) << 16;
|
|
||||||
if (parity(request))
|
|
||||||
request |= (1ul << 31);
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long OpenTherm::buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data)
|
|
||||||
{
|
|
||||||
unsigned long response = data;
|
|
||||||
response |= ((unsigned long)type) << 28;
|
|
||||||
response |= ((unsigned long)id) << 16;
|
|
||||||
if (parity(response))
|
|
||||||
response |= (1ul << 31);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::isValidResponse(unsigned long response)
|
|
||||||
{
|
|
||||||
if (parity(response))
|
|
||||||
return false;
|
|
||||||
byte msgType = (response << 1) >> 29;
|
|
||||||
return msgType == READ_ACK || msgType == WRITE_ACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::isValidRequest(unsigned long request)
|
|
||||||
{
|
|
||||||
if (parity(request))
|
|
||||||
return false;
|
|
||||||
byte msgType = (request << 1) >> 29;
|
|
||||||
return msgType == READ_DATA || msgType == WRITE_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenTherm::end()
|
|
||||||
{
|
|
||||||
if (this->handleInterruptCallback != NULL)
|
|
||||||
{
|
|
||||||
detachInterrupt(digitalPinToInterrupt(inPin));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *OpenTherm::statusToString(OpenThermResponseStatus status)
|
|
||||||
{
|
|
||||||
switch (status)
|
|
||||||
{
|
|
||||||
case NONE:
|
|
||||||
return "NONE";
|
|
||||||
case SUCCESS:
|
|
||||||
return "SUCCESS";
|
|
||||||
case INVALID:
|
|
||||||
return "INVALID";
|
|
||||||
case TIMEOUT:
|
|
||||||
return "TIMEOUT";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *OpenTherm::messageTypeToString(OpenThermMessageType message_type)
|
|
||||||
{
|
|
||||||
switch (message_type)
|
|
||||||
{
|
|
||||||
case READ_DATA:
|
|
||||||
return "READ_DATA";
|
|
||||||
case WRITE_DATA:
|
|
||||||
return "WRITE_DATA";
|
|
||||||
case INVALID_DATA:
|
|
||||||
return "INVALID_DATA";
|
|
||||||
case RESERVED:
|
|
||||||
return "RESERVED";
|
|
||||||
case READ_ACK:
|
|
||||||
return "READ_ACK";
|
|
||||||
case WRITE_ACK:
|
|
||||||
return "WRITE_ACK";
|
|
||||||
case DATA_INVALID:
|
|
||||||
return "DATA_INVALID";
|
|
||||||
case UNKNOWN_DATA_ID:
|
|
||||||
return "UNKNOWN_DATA_ID";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// building requests
|
|
||||||
|
|
||||||
unsigned long OpenTherm::buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool enableSummerMode, bool dhwBlock)
|
|
||||||
{
|
|
||||||
unsigned int data = enableCentralHeating | (enableHotWater << 1) | (enableCooling << 2) | (enableOutsideTemperatureCompensation << 3) | (enableCentralHeating2 << 4) | (enableSummerMode << 5) | (dhwBlock << 6);
|
|
||||||
data <<= 8;
|
|
||||||
return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Status, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long OpenTherm::buildSetBoilerTemperatureRequest(float temperature)
|
|
||||||
{
|
|
||||||
unsigned int data = temperatureToData(temperature);
|
|
||||||
return buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TSet, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long OpenTherm::buildGetBoilerTemperatureRequest()
|
|
||||||
{
|
|
||||||
return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tboiler, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsing responses
|
|
||||||
bool OpenTherm::isFault(unsigned long response)
|
|
||||||
{
|
|
||||||
return response & 0x1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::isCentralHeatingActive(unsigned long response)
|
|
||||||
{
|
|
||||||
return response & 0x2;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::isHotWaterActive(unsigned long response)
|
|
||||||
{
|
|
||||||
return response & 0x4;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::isFlameOn(unsigned long response)
|
|
||||||
{
|
|
||||||
return response & 0x8;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::isCoolingActive(unsigned long response)
|
|
||||||
{
|
|
||||||
return response & 0x10;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::isDiagnostic(unsigned long response)
|
|
||||||
{
|
|
||||||
return response & 0x40;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t OpenTherm::getUInt(const unsigned long response) const
|
|
||||||
{
|
|
||||||
const uint16_t u88 = response & 0xffff;
|
|
||||||
return u88;
|
|
||||||
}
|
|
||||||
|
|
||||||
float OpenTherm::getFloat(const unsigned long response) const
|
|
||||||
{
|
|
||||||
const uint16_t u88 = getUInt(response);
|
|
||||||
const float f = (u88 & 0x8000) ? -(0x10000L - u88) / 256.0f : u88 / 256.0f;
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int OpenTherm::temperatureToData(float temperature)
|
|
||||||
{
|
|
||||||
if (temperature < 0)
|
|
||||||
temperature = 0;
|
|
||||||
if (temperature > 100)
|
|
||||||
temperature = 100;
|
|
||||||
unsigned int data = (unsigned int)(temperature * 256);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// basic requests
|
|
||||||
|
|
||||||
unsigned long OpenTherm::setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool enableSummerMode, bool dhwBlock)
|
|
||||||
{
|
|
||||||
return sendRequest(buildSetBoilerStatusRequest(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2, enableSummerMode, dhwBlock));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::setBoilerTemperature(float temperature)
|
|
||||||
{
|
|
||||||
unsigned long response = sendRequest(buildSetBoilerTemperatureRequest(temperature));
|
|
||||||
return isValidResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
float OpenTherm::getBoilerTemperature()
|
|
||||||
{
|
|
||||||
unsigned long response = sendRequest(buildGetBoilerTemperatureRequest());
|
|
||||||
return isValidResponse(response) ? getFloat(response) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
float OpenTherm::getReturnTemperature()
|
|
||||||
{
|
|
||||||
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tret, 0));
|
|
||||||
return isValidResponse(response) ? getFloat(response) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenTherm::setDHWSetpoint(float temperature)
|
|
||||||
{
|
|
||||||
unsigned int data = temperatureToData(temperature);
|
|
||||||
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TdhwSet, data));
|
|
||||||
return isValidResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
float OpenTherm::getDHWTemperature()
|
|
||||||
{
|
|
||||||
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tdhw, 0));
|
|
||||||
return isValidResponse(response) ? getFloat(response) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
float OpenTherm::getModulation()
|
|
||||||
{
|
|
||||||
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::RelModLevel, 0));
|
|
||||||
return isValidResponse(response) ? getFloat(response) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
float OpenTherm::getPressure()
|
|
||||||
{
|
|
||||||
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::CHPressure, 0));
|
|
||||||
return isValidResponse(response) ? getFloat(response) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char OpenTherm::getFault()
|
|
||||||
{
|
|
||||||
return ((sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0)) >> 8) & 0xff);
|
|
||||||
}
|
|
||||||
int8_t flame_timer = 0;
|
|
||||||
void OpenTherm::ImitationResponse(unsigned long request)
|
|
||||||
{
|
|
||||||
|
|
||||||
// unsigned long response;
|
|
||||||
unsigned int data = getUInt(request);
|
|
||||||
OpenThermMessageType msgType;
|
|
||||||
byte ID;
|
|
||||||
OpenThermMessageID id = getDataID(request);
|
|
||||||
uint8_t flags;
|
|
||||||
|
|
||||||
switch (id)
|
|
||||||
{
|
|
||||||
case OpenThermMessageID::Status:
|
|
||||||
// Статус котла получен
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
static int8_t flame = 0;
|
|
||||||
flame_timer++;
|
|
||||||
if (flame_timer > 10)
|
|
||||||
flame = 1;
|
|
||||||
if (flame_timer > 20)
|
|
||||||
{
|
|
||||||
flame_timer = 0;
|
|
||||||
flame = 0;
|
|
||||||
}
|
|
||||||
static int8_t fault = 0;
|
|
||||||
// fault = 1 - fault;
|
|
||||||
data = (bool)fault | (true << 1) | (true << 2) | ((bool)flame << 3) | (false << 4);
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::SConfigSMemberIDcode:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::SlaveVersion:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::MasterVersion:
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::RelModLevel:
|
|
||||||
static float RelModLevel = 10;
|
|
||||||
// RelModLevel = RelModLevel > 100 ? 10 : RelModLevel + 1;
|
|
||||||
if (flame_timer < 11)
|
|
||||||
{
|
|
||||||
RelModLevel = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RelModLevel = RelModLevel == 0 ? 10 : RelModLevel + 1;
|
|
||||||
}
|
|
||||||
// data = RelModLevel;
|
|
||||||
data = temperatureToData(RelModLevel);
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Tboiler:
|
|
||||||
// Получили температуру котла
|
|
||||||
static float Tboiler = 40;
|
|
||||||
Tboiler = Tboiler > 60 ? 40 : Tboiler + 1;
|
|
||||||
data = temperatureToData(Tboiler);
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Tdhw:
|
|
||||||
// Получили температуру ГВС
|
|
||||||
static float Tdhw = 60;
|
|
||||||
Tdhw = Tdhw > 80 ? 60 : Tdhw + 1;
|
|
||||||
data = temperatureToData(Tdhw);
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Toutside:
|
|
||||||
// Получили внешнюю температуру
|
|
||||||
static float Toutside = -10;
|
|
||||||
Toutside = Toutside > 10 ? -10 : Toutside + 1;
|
|
||||||
data = temperatureToData(Toutside);
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::ASFflags:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OpenThermMessageID::TdhwSetUBTdhwSetLB:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::MaxTSetUBMaxTSetLB:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OpenThermMessageID::OEMDiagnosticCode:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OpenThermMessageID::OpenThermVersionSlave:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OpenThermMessageID::CHPressure:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::DHWFlowRate:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::DayTime:
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Date:
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Year:
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OpenThermMessageID::Tret:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Tstorage:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Tcollector:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::TflowCH2:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Tdhw2:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Texhaust:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::TheatExchanger:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::BoilerFanSpeed:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::ElectricBurnerFlame:
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::BurnerStarts:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::CHPumpStarts:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::DHWPumpValveStarts:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::DHWBurnerStarts:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::BurnerOperationHours:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::CHPumpOperationHours:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::DHWPumpValveOperationHours:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::DHWBurnerOperationHours:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::RBPflags:
|
|
||||||
//
|
|
||||||
// Pre-Defined Remote Boiler Parameters
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::TdhwSet:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::TSet:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::MaxTSet:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::Hcratio:
|
|
||||||
//
|
|
||||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
else
|
|
||||||
msgType = OpenThermMessageType::WRITE_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::TSP:
|
|
||||||
//
|
|
||||||
// Transparent Slave Parameters
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::FHBsize:
|
|
||||||
//
|
|
||||||
// Fault History Data
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::MaxCapacityMinModLevel:
|
|
||||||
//
|
|
||||||
// Boiler Sequencer Control
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::TrOverride:
|
|
||||||
//
|
|
||||||
// Remote override room setpoint
|
|
||||||
//
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
case OpenThermMessageID::RemoteOverrideFunction:
|
|
||||||
msgType = OpenThermMessageType::READ_ACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
msgType = OpenThermMessageType::UNKNOWN_DATA_ID;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
response = buildResponse(msgType, id, data);
|
|
||||||
status = OpenThermStatus::RESPONSE_READY;
|
|
||||||
responseStatus = OpenThermResponseStatus::SUCCESS;
|
|
||||||
/*
|
|
||||||
if (processResponseCallback != NULL)
|
|
||||||
{
|
|
||||||
processResponseCallback(response, OpenThermResponseStatus::SUCCESS);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
/*
|
|
||||||
OpenTherm.h - OpenTherm Library for the ESP8266/Arduino platform
|
|
||||||
https://github.com/ihormelnyk/OpenTherm
|
|
||||||
http://ihormelnyk.com/pages/OpenTherm
|
|
||||||
Licensed under MIT license
|
|
||||||
Copyright 2018, Ihor Melnyk
|
|
||||||
|
|
||||||
Frame Structure:
|
|
||||||
P MGS-TYPE SPARE DATA-ID DATA-VALUE
|
|
||||||
0 000 0000 00000000 00000000 00000000
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef OpenTherm_h
|
|
||||||
#define OpenTherm_h
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
enum OpenThermResponseStatus : uint8_t
|
|
||||||
{
|
|
||||||
NONE,
|
|
||||||
SUCCESS,
|
|
||||||
INVALID,
|
|
||||||
TIMEOUT
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OpenThermMessageType : uint8_t
|
|
||||||
{
|
|
||||||
/* Master to Slave */
|
|
||||||
READ_DATA = B000,
|
|
||||||
READ = READ_DATA, // for backwared compatibility
|
|
||||||
WRITE_DATA = B001,
|
|
||||||
WRITE = WRITE_DATA, // for backwared compatibility
|
|
||||||
INVALID_DATA = B010,
|
|
||||||
RESERVED = B011,
|
|
||||||
/* Slave to Master */
|
|
||||||
READ_ACK = B100,
|
|
||||||
WRITE_ACK = B101,
|
|
||||||
DATA_INVALID = B110,
|
|
||||||
UNKNOWN_DATA_ID = B111
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef OpenThermMessageType OpenThermRequestType; // for backwared compatibility
|
|
||||||
|
|
||||||
enum OpenThermMessageID : uint8_t
|
|
||||||
{
|
|
||||||
Status, // flag8 / flag8 Master and Slave Status flags.
|
|
||||||
TSet, // f8.8 Control setpoint ie CH water temperature setpoint (°C)
|
|
||||||
MConfigMMemberIDcode, // flag8 / u8 Master Configuration Flags / Master MemberID Code
|
|
||||||
SConfigSMemberIDcode, // flag8 / u8 Slave Configuration Flags / Slave MemberID Code
|
|
||||||
Command, // u8 / u8 Remote Command
|
|
||||||
ASFflags, // / OEM-fault-code flag8 / u8 Application-specific fault flags and OEM fault code
|
|
||||||
RBPflags, // flag8 / flag8 Remote boiler parameter transfer-enable & read/write flags
|
|
||||||
CoolingControl, // f8.8 Cooling control signal (%)
|
|
||||||
TsetCH2, // f8.8 Control setpoint for 2e CH circuit (°C)
|
|
||||||
TrOverride, // f8.8 Remote override room setpoint
|
|
||||||
TSP, // u8 / u8 Number of Transparent-Slave-Parameters supported by slave
|
|
||||||
TSPindexTSPvalue, // u8 / u8 Index number / Value of referred-to transparent slave parameter.
|
|
||||||
FHBsize, // u8 / u8 Size of Fault-History-Buffer supported by slave
|
|
||||||
FHBindexFHBvalue, // u8 / u8 Index number / Value of referred-to fault-history buffer entry.
|
|
||||||
MaxRelModLevelSetting, // f8.8 Maximum relative modulation level setting (%)
|
|
||||||
MaxCapacityMinModLevel, // u8 / u8 Maximum boiler capacity (kW) / Minimum boiler modulation level(%)
|
|
||||||
TrSet, // f8.8 Room Setpoint (°C)
|
|
||||||
RelModLevel, // f8.8 Relative Modulation Level (%)
|
|
||||||
CHPressure, // f8.8 Water pressure in CH circuit (bar)
|
|
||||||
DHWFlowRate, // f8.8 Water flow rate in DHW circuit. (litres/minute)
|
|
||||||
DayTime, // special / u8 Day of Week and Time of Day
|
|
||||||
Date, // u8 / u8 Calendar date
|
|
||||||
Year, // u16 Calendar year
|
|
||||||
TrSetCH2, // f8.8 Room Setpoint for 2nd CH circuit (°C)
|
|
||||||
Tr, // f8.8 Room temperature (°C)
|
|
||||||
Tboiler, // f8.8 Boiler flow water temperature (°C)
|
|
||||||
Tdhw, // f8.8 DHW temperature (°C)
|
|
||||||
Toutside, // f8.8 Outside temperature (°C)
|
|
||||||
Tret, // f8.8 Return water temperature (°C)
|
|
||||||
Tstorage, // f8.8 Solar storage temperature (°C)
|
|
||||||
Tcollector, // f8.8 Solar collector temperature (°C)
|
|
||||||
TflowCH2, // f8.8 Flow water temperature CH2 circuit (°C)
|
|
||||||
Tdhw2, // f8.8 Domestic hot water temperature 2 (°C)
|
|
||||||
Texhaust, // s16 Boiler exhaust temperature (°C)
|
|
||||||
TheatExchanger, // f8.8 Boiler heat exchanger temperature (°C)
|
|
||||||
BoilerFanSpeed, // u16 Boiler fan speed Setpiont and actual value
|
|
||||||
ElectricBurnerFlame, // f8.8?? Electric current through burner flame (mюA)
|
|
||||||
TdhwSetUBTdhwSetLB = 48, // s8 / s8 DHW setpoint upper & lower bounds for adjustment (°C)
|
|
||||||
MaxTSetUBMaxTSetLB, // s8 / s8 Max CH water setpoint upper & lower bounds for adjustment (°C)
|
|
||||||
HcratioUBHcratioLB, // s8 / s8 OTC heat curve ratio upper & lower bounds for adjustment
|
|
||||||
TdhwSet = 56, // f8.8 DHW setpoint (°C) (Remote parameter 1)
|
|
||||||
MaxTSet, // f8.8 Max CH water setpoint (°C) (Remote parameters 2)
|
|
||||||
Hcratio, // f8.8 OTC heat curve ratio (°C) (Remote parameter 3)
|
|
||||||
RemoteOverrideFunction = 100, // flag8 / - Function of manual and program changes in master and remote room setpoint.
|
|
||||||
OEMDiagnosticCode = 115, // u16 OEM-specific diagnostic/service code
|
|
||||||
BurnerStarts, // u16 Number of starts burner
|
|
||||||
CHPumpStarts, // u16 Number of starts CH pump
|
|
||||||
DHWPumpValveStarts, // u16 Number of starts DHW pump/valve
|
|
||||||
DHWBurnerStarts, // u16 Number of starts burner during DHW mode
|
|
||||||
BurnerOperationHours, // u16 Number of hours that burner is in operation (i.e. flame on)
|
|
||||||
CHPumpOperationHours, // u16 Number of hours that CH pump has been running
|
|
||||||
DHWPumpValveOperationHours, // u16 Number of hours that DHW pump has been running or DHW valve has been opened
|
|
||||||
DHWBurnerOperationHours, // u16 Number of hours that burner is in operation during DHW mode
|
|
||||||
OpenThermVersionMaster, // f8.8 The implemented version of the OpenTherm Protocol Specification in the master.
|
|
||||||
OpenThermVersionSlave, // f8.8 The implemented version of the OpenTherm Protocol Specification in the slave.
|
|
||||||
MasterVersion, // u8 / u8 Master product version number and type
|
|
||||||
SlaveVersion, // u8 / u8 Slave product version number and type
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OpenThermStatus : uint8_t
|
|
||||||
{
|
|
||||||
NOT_INITIALIZED,
|
|
||||||
READY,
|
|
||||||
DELAY,
|
|
||||||
REQUEST_SENDING,
|
|
||||||
RESPONSE_WAITING,
|
|
||||||
RESPONSE_START_BIT,
|
|
||||||
RESPONSE_RECEIVING,
|
|
||||||
RESPONSE_READY,
|
|
||||||
RESPONSE_INVALID
|
|
||||||
};
|
|
||||||
|
|
||||||
class OpenTherm
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
OpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false);
|
|
||||||
volatile OpenThermStatus status;
|
|
||||||
void begin(void (*handleInterruptCallback)(void));
|
|
||||||
void begin(void (*handleInterruptCallback)(void), void (*processResponseCallback)(unsigned long, OpenThermResponseStatus));
|
|
||||||
bool isReady();
|
|
||||||
unsigned long sendRequest(unsigned long request);
|
|
||||||
bool sendResponse(unsigned long request);
|
|
||||||
bool sendRequestAync(unsigned long request);
|
|
||||||
unsigned long buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data);
|
|
||||||
unsigned long buildRequestID(OpenThermMessageType type, unsigned int id, unsigned int data);
|
|
||||||
unsigned long buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data);
|
|
||||||
unsigned long getLastResponse();
|
|
||||||
OpenThermResponseStatus getLastResponseStatus();
|
|
||||||
const char *statusToString(OpenThermResponseStatus status);
|
|
||||||
void handleInterrupt();
|
|
||||||
void process();
|
|
||||||
void end();
|
|
||||||
|
|
||||||
bool parity(unsigned long frame);
|
|
||||||
OpenThermMessageType getMessageType(unsigned long message);
|
|
||||||
OpenThermMessageID getDataID(unsigned long frame);
|
|
||||||
const char *messageTypeToString(OpenThermMessageType message_type);
|
|
||||||
bool isValidRequest(unsigned long request);
|
|
||||||
bool isValidResponse(unsigned long response);
|
|
||||||
|
|
||||||
// requests
|
|
||||||
unsigned long buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false, bool enableSummerMode = false, bool dhwBlock = false);
|
|
||||||
unsigned long buildSetBoilerTemperatureRequest(float temperature);
|
|
||||||
unsigned long buildGetBoilerTemperatureRequest();
|
|
||||||
|
|
||||||
// responses
|
|
||||||
bool isFault(unsigned long response);
|
|
||||||
bool isCentralHeatingActive(unsigned long response);
|
|
||||||
bool isHotWaterActive(unsigned long response);
|
|
||||||
bool isFlameOn(unsigned long response);
|
|
||||||
bool isCoolingActive(unsigned long response);
|
|
||||||
bool isDiagnostic(unsigned long response);
|
|
||||||
uint16_t getUInt(const unsigned long response) const;
|
|
||||||
float getFloat(const unsigned long response) const;
|
|
||||||
unsigned int temperatureToData(float temperature);
|
|
||||||
|
|
||||||
// basic requests
|
|
||||||
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false, bool enableSummerMode = false, bool dhwBlock = false);
|
|
||||||
bool setBoilerTemperature(float temperature);
|
|
||||||
float getBoilerTemperature();
|
|
||||||
float getReturnTemperature();
|
|
||||||
bool setDHWSetpoint(float temperature);
|
|
||||||
float getDHWTemperature();
|
|
||||||
float getModulation();
|
|
||||||
float getPressure();
|
|
||||||
unsigned char getFault();
|
|
||||||
|
|
||||||
//Имитация ответов от котла, TRUE - идет имитация ответов котла, в котел так же шлется (лучше его отключить), FALSE - штатная работа
|
|
||||||
void imitation(bool fl) {imitFlag = fl;}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool imitFlag;
|
|
||||||
void ImitationResponse(unsigned long request);
|
|
||||||
|
|
||||||
const int inPin;
|
|
||||||
const int outPin;
|
|
||||||
const bool isSlave;
|
|
||||||
|
|
||||||
volatile unsigned long response;
|
|
||||||
volatile OpenThermResponseStatus responseStatus;
|
|
||||||
volatile unsigned long responseTimestamp;
|
|
||||||
volatile byte responseBitIndex;
|
|
||||||
|
|
||||||
int readState();
|
|
||||||
void setActiveState();
|
|
||||||
void setIdleState();
|
|
||||||
void activateBoiler();
|
|
||||||
|
|
||||||
void sendBit(bool high);
|
|
||||||
void (*handleInterruptCallback)();
|
|
||||||
void (*processResponseCallback)(unsigned long, OpenThermResponseStatus);
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef ICACHE_RAM_ATTR
|
|
||||||
#define ICACHE_RAM_ATTR
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef IRAM_ATTR
|
|
||||||
#define IRAM_ATTR ICACHE_RAM_ATTR
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // OpenTherm_h
|
|
||||||
@@ -1,589 +0,0 @@
|
|||||||
#include "Global.h"
|
|
||||||
#include "classes/IoTItem.h"
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "OpenTherm.h"
|
|
||||||
|
|
||||||
#define SLAVE true
|
|
||||||
#define TIMEOUT_TRESHOLD 5
|
|
||||||
|
|
||||||
namespace _OpenThermSlave
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// команды/установки от термостата
|
|
||||||
struct SetpointBoiler
|
|
||||||
{
|
|
||||||
uint8_t cmd_chEnable = 0;
|
|
||||||
uint8_t cmd_dhwEnable = 0;
|
|
||||||
float TSetCH = 0;
|
|
||||||
float TSetDhw = 0;
|
|
||||||
} set;
|
|
||||||
|
|
||||||
struct failCode
|
|
||||||
{
|
|
||||||
bool service_required = 0;
|
|
||||||
bool lockout_reset = 0;
|
|
||||||
bool low_water_pressure = 0;
|
|
||||||
bool gas_fault = 0;
|
|
||||||
bool air_fault = 0;
|
|
||||||
bool water_overtemp = 0;
|
|
||||||
uint8_t fault_code = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// текущее реальное состояние котла
|
|
||||||
struct StateBoiler
|
|
||||||
{
|
|
||||||
uint8_t stateCH = 0;
|
|
||||||
uint8_t stateDHW = 0;
|
|
||||||
uint8_t fl_flame = 0;
|
|
||||||
uint8_t fl_fail = 0;
|
|
||||||
failCode fCode;
|
|
||||||
float RelModLevel = 0;
|
|
||||||
float Tboiler = 0;
|
|
||||||
float Tret = 0;
|
|
||||||
float Tdhw = 0;
|
|
||||||
float Toutside = 0;
|
|
||||||
} state;
|
|
||||||
|
|
||||||
// конфигурация котла
|
|
||||||
struct ConfigBoiler
|
|
||||||
{
|
|
||||||
bool dhw = false; // 1- есть реле(трехходовой) ГВС
|
|
||||||
bool ctrlType = false; // 0 - модуляция, 1- вкл/выкл
|
|
||||||
bool confDhw = true; // 1 - бак, 0 - проточная //TODO ПОКА НЕ ЗНАЮ ЧТО ДЕЛАТЬ
|
|
||||||
bool pumpControlMaster = false; // в протоколе ОТ: мастер управляет насосом ????????????????????? //TODO Команды кправления насосом от мастера не помню
|
|
||||||
|
|
||||||
int minDhw;
|
|
||||||
int maxDhw;
|
|
||||||
int minCH;
|
|
||||||
int maxCH;
|
|
||||||
|
|
||||||
} conf;
|
|
||||||
|
|
||||||
// DynamicJsonDocument OpenThemData(JSON_BUFFER_SIZE / 2);
|
|
||||||
|
|
||||||
IoTItem *tmp;
|
|
||||||
|
|
||||||
IoTItem *_idTboiler = nullptr;
|
|
||||||
IoTItem *_idTret = nullptr;
|
|
||||||
IoTItem *_idToutside = nullptr;
|
|
||||||
IoTItem *_idStateCH = nullptr;
|
|
||||||
IoTItem *_idStateDHW = nullptr;
|
|
||||||
IoTItem *_idStateFlame = nullptr;
|
|
||||||
IoTItem *_idModLevel = nullptr;
|
|
||||||
IoTItem *_idTDhw = nullptr;
|
|
||||||
IoTItem *_idCmdCH = nullptr;
|
|
||||||
IoTItem *_idCmdDHW = nullptr;
|
|
||||||
IoTItem *_idSetCH = nullptr;
|
|
||||||
IoTItem *_idSetDHW = nullptr;
|
|
||||||
IoTItem *_idCtrlType = nullptr;
|
|
||||||
|
|
||||||
unsigned long timeout_count = 0;
|
|
||||||
|
|
||||||
uint8_t _debug = 0;
|
|
||||||
bool _telegram = false;
|
|
||||||
unsigned long ot_response = 0;
|
|
||||||
uint8_t SlaveMemberIDcode = 0;
|
|
||||||
|
|
||||||
|
|
||||||
void publishNew(String widget, String value)
|
|
||||||
{
|
|
||||||
tmp = findIoTItem(widget);
|
|
||||||
if (tmp)
|
|
||||||
{
|
|
||||||
tmp->setValue(value, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_debug > 0)
|
|
||||||
SerialPrint("new", "SmartBoiler", widget + " = " + value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendTelegramm(String msg)
|
|
||||||
{
|
|
||||||
if (_telegram == 1)
|
|
||||||
{
|
|
||||||
if (tlgrmItem)
|
|
||||||
tlgrmItem->sendTelegramMsg(false, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* =========================================================================================
|
|
||||||
* КЛАСС РАБОТЫ ПО ПРОТОКОЛУ OPENTHERM
|
|
||||||
* =========================================================================================
|
|
||||||
*/
|
|
||||||
class OpenThermSlave : public IoTItem
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
// unsigned long ts = 0;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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", (int &)SlaveMemberIDcode);
|
|
||||||
|
|
||||||
jsonRead(parameters, "LogLevel", (int &)_debug);
|
|
||||||
jsonRead(parameters, "telegram", _telegram);
|
|
||||||
|
|
||||||
String tmpID;
|
|
||||||
jsonRead(parameters, "idTboiler", tmpID);
|
|
||||||
_idTboiler = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idTret", tmpID);
|
|
||||||
_idTret = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idToutside", tmpID);
|
|
||||||
_idToutside = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idStateCH", tmpID);
|
|
||||||
_idStateCH = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idStateDHW", tmpID);
|
|
||||||
_idStateDHW = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idStateFlame", tmpID);
|
|
||||||
_idStateFlame = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idModLevel", tmpID);
|
|
||||||
_idModLevel = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idTDhw", tmpID);
|
|
||||||
_idTDhw = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idCmdCH", tmpID);
|
|
||||||
_idCmdCH = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idCmdDHW", tmpID);
|
|
||||||
_idCmdDHW = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idCtrlType", tmpID);
|
|
||||||
_idCtrlType = findIoTItem(tmpID);
|
|
||||||
|
|
||||||
jsonRead(parameters, "idSetCH", tmpID);
|
|
||||||
_idSetCH = findIoTItem(tmpID);
|
|
||||||
jsonRead(parameters, "idSetDHW", tmpID);
|
|
||||||
_idSetDHW = findIoTItem(tmpID);
|
|
||||||
|
|
||||||
jsonRead(parameters, "minCH", conf.minCH);
|
|
||||||
jsonRead(parameters, "maxCH", conf.maxCH);
|
|
||||||
jsonRead(parameters, "minDhw", conf.minDhw);
|
|
||||||
jsonRead(parameters, "maxDhw", conf.maxDhw);
|
|
||||||
|
|
||||||
instance_OTdriver(_RX_pin, _TX_pin);
|
|
||||||
ot_driver->begin(handleInterruptSlave, processRequest); // responseCallback
|
|
||||||
// ot_boiler = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void doByInterval()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Основной цикл программы
|
|
||||||
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;
|
|
||||||
_idCmdCH->setValue(val, true);
|
|
||||||
val.valD = set.cmd_dhwEnable;
|
|
||||||
_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", "не подключен");
|
|
||||||
timeout_count = TIMEOUT_TRESHOLD;
|
|
||||||
sendTelegramm(("OpenTherm: потеря связи с управляющим устройством (термостатом) ❌"));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OpenThermResponseStatus::SUCCESS:
|
|
||||||
timeout_count = 0;
|
|
||||||
publishNew("boilerslave", "✅");
|
|
||||||
// publishNew("status", "подключен");
|
|
||||||
// sendTelegramm(("OpenTherm: котёл подключен ✅"));
|
|
||||||
// respondense_flag = true;
|
|
||||||
// ts_ = new_ts_;
|
|
||||||
HandleRequest(request);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Парсинг запросов
|
|
||||||
static void HandleRequest(unsigned long request)
|
|
||||||
{
|
|
||||||
if (_idCtrlType)
|
|
||||||
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 > 2)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
_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;
|
|
||||||
_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 > 2)
|
|
||||||
{
|
|
||||||
SerialPrint("i", "OpenThermSlave ->", String(millis()) + " ID: " + String(id) + " / responseHEX: " + String(ot_response, HEX) + " / response: " + String(ot_response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~OpenThermSlave()
|
|
||||||
{
|
|
||||||
delete ot_driver;
|
|
||||||
ot_driver = nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void *getAPI_OpenThermSlave(String subtype, String param)
|
|
||||||
{
|
|
||||||
if (subtype == F("OpenThermSlave"))
|
|
||||||
{
|
|
||||||
return new _OpenThermSlave::OpenThermSlave(param);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
{
|
|
||||||
"menuSection": "executive_devices",
|
|
||||||
"configItem": [
|
|
||||||
{
|
|
||||||
"global": 0,
|
|
||||||
"name": "OpenThermSlave",
|
|
||||||
"type": "Reading",
|
|
||||||
"subtype": "OpenThermSlave",
|
|
||||||
"id": "otslave",
|
|
||||||
"widget": "",
|
|
||||||
"page": "Boiler",
|
|
||||||
"descr": "Котёл",
|
|
||||||
"int": 60,
|
|
||||||
"value": "...",
|
|
||||||
"RX_pin": 13,
|
|
||||||
"TX_pin": 15,
|
|
||||||
"LogLevel": 0,
|
|
||||||
"telegram": 1,
|
|
||||||
"MemberID": 0,
|
|
||||||
"confDhw":0,
|
|
||||||
"minCH": 35,
|
|
||||||
"maxCH": 85,
|
|
||||||
"minDhw": 30,
|
|
||||||
"maxDhw": 60,
|
|
||||||
"idTboiler": "Tboiler",
|
|
||||||
"idTret": "Tret",
|
|
||||||
"idToutside": "Toutside",
|
|
||||||
"idTDhw":"TDhw",
|
|
||||||
"idStateCH":"StateCH",
|
|
||||||
"idStateDHW":"StateDHW",
|
|
||||||
"idStateFlame":"StateFlame",
|
|
||||||
"idModLevel":"ModLevel",
|
|
||||||
"idCmdCH":"CmdCH",
|
|
||||||
"idCmdDHW":"CmdDHW",
|
|
||||||
"idSetCH":"SetCH",
|
|
||||||
"idSetDHW":"SetDHW",
|
|
||||||
"idCtrlType":"CtrlType"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"about": {
|
|
||||||
"authorName": "Mikhail Bubnov",
|
|
||||||
"authorContact": "https://t.me/Mit4bmw",
|
|
||||||
"authorGit": "https://github.com/Mit4el",
|
|
||||||
"specialThanks": "",
|
|
||||||
"moduleName": "OpenThermSlave",
|
|
||||||
"moduleVersion": "0.1",
|
|
||||||
"usedRam": {
|
|
||||||
"esp32_4mb": 15,
|
|
||||||
"esp8266_4mb": 15
|
|
||||||
},
|
|
||||||
"title": "OpenThermSlave",
|
|
||||||
"moduleDesc": "Модуль для автоматизации электрического котла. Мозги котла с внешним протоколом opentherm",
|
|
||||||
"propInfo": {
|
|
||||||
"int": "Интервал отправки данных в MQTT и web интерфейс",
|
|
||||||
"telegram": "1- Будет отправлять в телеграмм оповещения при ошибках котла и пропаже сигнала от котла, остальные необходимо реализовывать через сценарий",
|
|
||||||
"MemberID": "SlaveMemberIDcode - код производителя котла, кем притворится котёл;) Менять в большинстве случаев не надо",
|
|
||||||
"idPID":"ID модуля ПИД регулятора, для расчета модуляции и включения тэнов в зависимости от температуры теплоносителя, в модуле TCHSet будет уставка СО, создать TCHSet и указать его в модуле ПИД",
|
|
||||||
"idTboiler": "ID датчика температуры подачи котла",
|
|
||||||
"idTret": "ID датчика температуры обратки котла",
|
|
||||||
"idToutside": "ID датчика уличной температуры",
|
|
||||||
"rele1_Pwr": "Мощность тэна на первом реле, ID реле должно называться rele1",
|
|
||||||
"rele2_Pwr": "Мощность тэна на первом реле, ID реле должно называться rele2, если нет, то 0 (ноль)",
|
|
||||||
"rele3_Pwr": "Мощность тэна на первом реле, ID реле должно называться rele3, если нет, то 0 (ноль)",
|
|
||||||
"Pupm": "1-есть реле насоса (ID реле должно называться relePump), 0-нет реле насоса, насос управляется котлом без нас",
|
|
||||||
"minCH": "Граница установки температуры СО",
|
|
||||||
"maxCH": "Граница установки температуры СО",
|
|
||||||
"gistCH": "Гистерезис СО - нагрев СО включится если температура теплоносителя ниже уставки на указанные градусы (CHSet = 45гр, gistCH = 5гр, нагрев включится когда idTboiler = 40гр)",
|
|
||||||
"idTdhw": "ID датчика температуры ГВС, например в датчик в БКН",
|
|
||||||
"idReleDhw":"ID реле трехходового крана ГВС",
|
|
||||||
"gistDhw": "Гистерезис ГВС - нагрев ГВС включится если температура воды ниже уставки на указанные градусы",
|
|
||||||
"minDhw": "Граница установки температуры ГВС",
|
|
||||||
"maxDhw": "Граница установки температуры ГВС"
|
|
||||||
},
|
|
||||||
"funcInfo": [
|
|
||||||
{
|
|
||||||
"name": "CHSet",
|
|
||||||
"descr": "Установить целевую температуру СО",
|
|
||||||
"params": [
|
|
||||||
"тепмература СО (подачи) - bolier.CHSet(60)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CHEnable",
|
|
||||||
"descr": "включить / выключить отопление",
|
|
||||||
"params": [
|
|
||||||
"bolier.CHEnable(1) - вкл, bolier.CHEnable(0) - выкл, "
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DHWSet",
|
|
||||||
"descr": "Установить целевую температуру ГВС",
|
|
||||||
"params": [
|
|
||||||
"тепмература ГВС - dhw.DHWSet(40)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DHWEnable",
|
|
||||||
"descr": "включить / выключить ГВС",
|
|
||||||
"params": [
|
|
||||||
"dhw.DHWEnable(1) - вкл, dhw.DHWEnable(0) - выкл, "
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"defActive": true,
|
|
||||||
"usedLibs": {
|
|
||||||
"esp32_4mb3f": [],
|
|
||||||
"esp32*": [],
|
|
||||||
"esp82*": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
165
src/modules/virtual/Benchmark/Benchmark.cpp
Normal file
165
src/modules/virtual/Benchmark/Benchmark.cpp
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#include "Global.h"
|
||||||
|
#include "classes/IoTBench.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
// #include <map>
|
||||||
|
|
||||||
|
class BenchmarkLoad : public IoTBench
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
bool _log = false;
|
||||||
|
uint32_t _loadP = 1; // период подсчета загруженности процессора
|
||||||
|
|
||||||
|
uint32_t startLoad = 0; // время начало цикла loop
|
||||||
|
uint32_t loadPrev = 0; // время предыдущего подсчета benchmark
|
||||||
|
uint32_t loadSum = 0; // время выполнния всех циклов loop за период _loadP
|
||||||
|
float load = 0; // загруженность процессора в процентах за период _loadP (loadSum / 1000) / _loadP * 100
|
||||||
|
uint32_t count = 0; // количестов циклов loop в сек в среднем за период _loadP
|
||||||
|
|
||||||
|
public:
|
||||||
|
BenchmarkLoad(String parameters) : IoTBench(parameters)
|
||||||
|
{
|
||||||
|
// jsonRead(parameters, "log", _log);
|
||||||
|
// jsonRead(parameters, "int", _loadP); // в минутах
|
||||||
|
_loadP = _interval ; //* 1000
|
||||||
|
// SerialPrint("i", "Benchmark",
|
||||||
|
// "_interval: " + String(_interval) + " _loadP: " + String(_loadP));
|
||||||
|
if (_loadP < 10000)
|
||||||
|
_loadP = 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doByInterval()
|
||||||
|
{
|
||||||
|
printBenchmarkLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
IoTItem::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void preLoadFunction()
|
||||||
|
{
|
||||||
|
startLoad = micros(); // время начала выполнения одного цикла
|
||||||
|
}
|
||||||
|
void postLoadFunction()
|
||||||
|
{
|
||||||
|
loadSum += (micros() - startLoad); // высчитываем время выполнения одного цикла (после нагрузки) и прибавляем к сумме за вреям контроля _loadP
|
||||||
|
}
|
||||||
|
|
||||||
|
void printBenchmarkLoad()
|
||||||
|
{
|
||||||
|
load = (loadSum / 10ul) / _loadP; // (loadSum / 1000) / _loadP * 100
|
||||||
|
|
||||||
|
SerialPrint("i", "Benchmark",
|
||||||
|
"CPU load time: " + String(loadSum) + "us, in RealTime: " + String((micros() - loadPrev)) + "us");
|
||||||
|
SerialPrint("i", "Benchmark",
|
||||||
|
"CPU load in " + String(_loadP) + "ms :" + String((load)) + "%" +
|
||||||
|
" loop/sec: " + String(count / (_loadP / 1000)));
|
||||||
|
loadPrev = micros(); //+= _loadP;
|
||||||
|
loadSum = 0;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
IoTBench *getBenchmarkLoad()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
~BenchmarkLoad(){
|
||||||
|
// clearBenchConfig();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class BenchmarkTask : public IoTBench
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
uint32_t _loadP = 1;
|
||||||
|
bool _log = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BenchmarkTask(String parameters) : IoTBench(parameters)
|
||||||
|
{
|
||||||
|
// jsonRead(parameters, "log", _log);
|
||||||
|
// jsonRead(parameters, "int", _loadP); // в минутах
|
||||||
|
_loadP = _interval;// * 1000;
|
||||||
|
if (_loadP < 10000)
|
||||||
|
_loadP = 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doByInterval()
|
||||||
|
{
|
||||||
|
printBenchmarkTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
void preTaskFunction(const String &id)
|
||||||
|
{
|
||||||
|
if (banchItems.find(id) != banchItems.end())
|
||||||
|
{
|
||||||
|
banchItems[id]->loopTime = micros(); // micros();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
banchItems[id] = new ItemBench;
|
||||||
|
banchItems[id]->loopTime = micros(); // micros();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void postTaskFunction(const String &id)
|
||||||
|
{
|
||||||
|
if (banchItems.find(id) != banchItems.end())
|
||||||
|
{
|
||||||
|
banchItems[id]->loopTime = micros() - banchItems[id]->loopTime;
|
||||||
|
banchItems[id]->sumloopTime += banchItems[id]->loopTime;
|
||||||
|
if (banchItems[id]->loopTime > banchItems[id]->loopTimeMax_glob)
|
||||||
|
banchItems[id]->loopTimeMax_glob = banchItems[id]->loopTime;
|
||||||
|
if (banchItems[id]->loopTime > banchItems[id]->loopTimeMax_p)
|
||||||
|
banchItems[id]->loopTimeMax_p = banchItems[id]->loopTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printBenchmarkTask()
|
||||||
|
{
|
||||||
|
for (auto it = banchItems.begin(); it != banchItems.end(); it++)
|
||||||
|
{
|
||||||
|
SerialPrint(
|
||||||
|
"i", "Benchmark",
|
||||||
|
" load (" + String((float)(it->second)->sumloopTime / 10ul / _loadP) + "%) " +
|
||||||
|
" max: per (" + String((it->second)->loopTimeMax_p) + "us)" +
|
||||||
|
" glob (" + String((it->second)->loopTimeMax_glob) + "us) - " + it->first);
|
||||||
|
(it->second)->sumloopTime = 0;
|
||||||
|
(it->second)->loopTimeMax_p = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearBenchConfig()
|
||||||
|
{
|
||||||
|
for (auto it = banchItems.begin(); it != banchItems.end(); it++)
|
||||||
|
{
|
||||||
|
delete it->second;
|
||||||
|
}
|
||||||
|
banchItems.clear();
|
||||||
|
}
|
||||||
|
IoTBench *getBenchmarkTask()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
~BenchmarkTask()
|
||||||
|
{
|
||||||
|
clearBenchConfig();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
void *getAPI_Benchmark(String subtype, String param)
|
||||||
|
{
|
||||||
|
if (subtype == F("loadBench"))
|
||||||
|
{
|
||||||
|
return new BenchmarkTask(param);
|
||||||
|
}
|
||||||
|
else if (subtype == F("taskBench"))
|
||||||
|
{
|
||||||
|
return new BenchmarkLoad(param);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/modules/virtual/Benchmark/modinfo.json
Normal file
69
src/modules/virtual/Benchmark/modinfo.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"menuSection": "virtual_elments",
|
||||||
|
|
||||||
|
"configItem": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"name": "Load Processor",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "loadBench",
|
||||||
|
"id": "bench",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "Benchmark",
|
||||||
|
"descr": "Загруженность процессора",
|
||||||
|
"int": 10,
|
||||||
|
"log": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"name": "Load Task",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "taskBench",
|
||||||
|
"id": "bench",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "Benchmark",
|
||||||
|
"descr": "Загруженность задач",
|
||||||
|
"int": 10,
|
||||||
|
"log": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"about": {
|
||||||
|
"authorName": "Mikhail Bubnov",
|
||||||
|
"authorContact": "https://t.me/Mit4bmw",
|
||||||
|
"authorGit": "https://github.com/Mit4el",
|
||||||
|
"specialThanks": "",
|
||||||
|
"moduleName": "Benchmark",
|
||||||
|
"moduleVersion": "1.0",
|
||||||
|
"usedRam": {
|
||||||
|
"esp32_4mb": 15,
|
||||||
|
"esp8266_4mb": 15
|
||||||
|
},
|
||||||
|
"title": "Производительонсть системы",
|
||||||
|
"moduleDesc": "Оценочные показатели производительности системы и выполнения модулей",
|
||||||
|
"propInfo": {
|
||||||
|
"int": "Интервал подсчета загруженности процессора в секундах"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"defActive": true,
|
||||||
|
|
||||||
|
"usedLibs": {
|
||||||
|
"esp32_4mb": [],
|
||||||
|
"esp32_4mb3f": [],
|
||||||
|
"esp32s2_4mb": [],
|
||||||
|
"esp32_16mb": [],
|
||||||
|
"esp32s3_16mb": [],
|
||||||
|
"esp32c3m_4mb": [],
|
||||||
|
"esp8266_4mb": [],
|
||||||
|
"esp8266_16mb": [],
|
||||||
|
"esp8266_1mb": [],
|
||||||
|
"esp8266_1mb_ota": [],
|
||||||
|
"esp8285_1mb": [],
|
||||||
|
"esp8285_1mb_ota": [],
|
||||||
|
"esp8266_2mb": [],
|
||||||
|
"esp8266_2mb_ota": []
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user