Use platformio and more updates

This commit is contained in:
Yurii
2023-09-16 22:00:07 +03:00
parent 493490895c
commit 4f305a6459
19 changed files with 1479 additions and 1267 deletions

5
.gitignore vendored
View File

@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

BIN
assets/BOM.xlsx Normal file

Binary file not shown.

BIN
assets/equitherm calc.xlsx Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,106 @@
#include <Arduino.h>
#include <OpenTherm.h>
extern SchedulerClass Scheduler;
class CustomOpenTherm : public OpenTherm {
private:
unsigned long send_ts = millis();
void(*handleSendRequestCallback)(unsigned long, unsigned long, OpenThermResponseStatus status, byte attempt);
public:
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false) : OpenTherm(inPin, outPin, isSlave) {}
void setHandleSendRequestCallback(void(*handleSendRequestCallback)(unsigned long, unsigned long, OpenThermResponseStatus status, byte attempt)) {
this->handleSendRequestCallback = handleSendRequestCallback;
}
unsigned long sendRequest(unsigned long request, byte attempts = 5, byte _attempt = 0) {
_attempt++;
while (send_ts > 0 && millis() - send_ts < 200) {
Scheduler.yield();
}
unsigned long _response;
if (!sendRequestAync(request)) {
_response = 0;
} else {
while (!isReady()) {
Scheduler.yield();
process();
}
_response = getLastResponse();
}
if (handleSendRequestCallback != NULL) {
handleSendRequestCallback(request, _response, getLastResponseStatus(), _attempt);
}
send_ts = millis();
if (getLastResponseStatus() == OpenThermResponseStatus::SUCCESS || _attempt >= attempts) {
return _response;
} else {
return sendRequest(request, attempts, _attempt);
}
}
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool summerWinterMode, bool dhwBlocking) {
return sendRequest(buildSetBoilerStatusRequest(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2, summerWinterMode, dhwBlocking));
}
unsigned long buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool summerWinterMode, bool dhwBlocking) {
unsigned int data = enableCentralHeating | (enableHotWater << 1) | (enableCooling << 2) | (enableOutsideTemperatureCompensation << 3) | (enableCentralHeating2 << 4) | (summerWinterMode << 5) | (dhwBlocking << 6);
data <<= 8;
return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Status, data);
}
bool setBoilerTemperature(float temperature) {
unsigned int data = temperatureToData(temperature);
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TSet, data));
return isValidResponse(response);
}
bool setBoilerTemperature2(float temperature) {
unsigned int data = temperatureToData(temperature);
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TsetCH2, data));
return isValidResponse(response);
}
bool sendBoilerReset() {
unsigned int data = 1;
data <<= 8;
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::Command, data));
return isValidResponse(response);
}
bool sendServiceReset() {
unsigned int data = 10;
data <<= 8;
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::Command, data));
return isValidResponse(response);
}
bool sendWaterFilling() {
unsigned int data = 2;
data <<= 8;
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::Command, data));
return isValidResponse(response);
}
// converters
float f88(unsigned long response) {
const byte valueLB = response & 0xFF;
const byte valueHB = (response >> 8) & 0xFF;
float value = (int8_t) valueHB;
return value + (float)valueLB / 256.0;
}
int16_t s16(unsigned long response) {
const byte valueLB = response & 0xFF;
const byte valueHB = (response >> 8) & 0xFF;
int16_t value = valueHB;
return ((value << 8) + valueLB);
}
};

View File

@@ -1,4 +1,3 @@
#pragma once
#include <Arduino.h> #include <Arduino.h>
#if defined(EQUITHERM_INTEGER) #if defined(EQUITHERM_INTEGER)
@@ -11,6 +10,13 @@ typedef float datatype;
class Equitherm { class Equitherm {
public: public:
datatype targetTemp = 0;
datatype indoorTemp = 0;
datatype outdoorTemp = 0;
float Kn = 0.0;
float Kk = 0.0;
float Kt = 0.0;
Equitherm() {} Equitherm() {}
// kn, kk, kt // kn, kk, kt
@@ -26,13 +32,6 @@ public:
_maxOut = max_output; _maxOut = max_output;
} }
datatype targetTemp = 0;
datatype indoorTemp = 0;
datatype outdoorTemp = 0;
float Kn = 0.0;
float Kk = 0.0;
float Kt = 0.0;
// возвращает новое значение при вызове // возвращает новое значение при вызове
datatype getResult() { datatype getResult() {
datatype output = getResultN() + getResultK() + getResultT(); datatype output = getResultN() + getResultK() + getResultT();
@@ -40,6 +39,9 @@ public:
return output; return output;
} }
private:
int _minOut = 20, _maxOut = 90;
// температура контура отопления в зависимости от наружной температуры // температура контура отопления в зависимости от наружной температуры
datatype getResultN() { datatype getResultN() {
float a = (-0.21 * Kn) - 0.06; // a = -0,21k — 0,06 float a = (-0.21 * Kn) - 0.06; // a = -0,21k — 0,06
@@ -56,9 +58,6 @@ public:
// Расчет поправки (ошибки) термостата // Расчет поправки (ошибки) термостата
datatype getResultT() { datatype getResultT() {
return (targetTemp - indoorTemp) * Kt; return constrain((targetTemp - indoorTemp), -2, 2) * Kt;
} }
private:
int _minOut = 20, _maxOut = 90;
}; };

View File

@@ -1,4 +0,0 @@
/*
This file is needed by the Arduino IDE because the ino file needs to be named as the directory name.
Don't worry, the Arduino compiler will "merge" all files, including src/main.cpp
*/

28
platformio.ini Normal file
View File

@@ -0,0 +1,28 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:d1_mini_pro]
platform = espressif8266
board = d1_mini_pro
framework = arduino
lib_deps =
nrwiersma/ESP8266Scheduler@^1.0
arduino-libraries/NTPClient@^3.2.1
bblanchon/ArduinoJson@^6.20.0
ihormelnyk/OpenTherm Library@^1.1.4
knolleary/PubSubClient@^2.8
jandrassy/TelnetStream@^1.2.4
gyverlibs/EEManager@^2.0
gyverlibs/GyverPID@^3.3
gyverlibs/microDS18B20@^3.10
https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2
build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
upload_speed = 921600
monitor_speed = 115200

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,22 @@
extern MqttTask* tMqtt; extern MqttTask* tMqtt;
extern SensorsTask* tSensors; extern SensorsTask* tSensors;
extern OpenThermTask* tOt;
class MainTask: public Task { class MainTask: public Task {
public: public:
MainTask(bool _enabled = false, unsigned long _interval = 0): Task(_enabled, _interval) {} MainTask(bool _enabled = false, unsigned long _interval = 0): Task(_enabled, _interval) {}
protected: protected:
unsigned long lastHeapInfo = 0;
unsigned long firstFailConnect = 0;
unsigned short minFreeHeapSize = 65535;
void setup() {
pinMode(LED_STATUS_PIN, OUTPUT);
//pinMode(LED_OT_RX_PIN, OUTPUT);
}
void loop() { void loop() {
static unsigned long lastHeapInfo = 0;
static unsigned short minFreeHeapSize = 65535;
if (eeSettings.tick()) { if (eeSettings.tick()) {
INFO("Settings updated (EEPROM)"); INFO("Settings updated (EEPROM)");
} }
@@ -20,12 +26,27 @@ protected:
tMqtt->enable(); tMqtt->enable();
} }
if ( firstFailConnect != 0 ) {
firstFailConnect = 0;
}
vars.states.rssi = WiFi.RSSI();
} else { } else {
if (tMqtt->isEnabled()) { if (tMqtt->isEnabled()) {
tMqtt->disable(); tMqtt->disable();
} }
if (settings.emergency.enable && !vars.states.emergency) {
if (firstFailConnect == 0) {
firstFailConnect = millis();
}
if (millis() - firstFailConnect > EMERGENCY_TIME_TRESHOLD) {
vars.states.emergency = true; vars.states.emergency = true;
INFO("Emergency mode enabled");
}
}
} }
if (!tSensors->isEnabled() && settings.outdoorTempSource == 2) { if (!tSensors->isEnabled() && settings.outdoorTempSource == 2) {
@@ -34,6 +55,12 @@ protected:
tSensors->disable(); tSensors->disable();
} }
if (!tOt->isEnabled() && settings.opentherm.inPin > 0 && settings.opentherm.outPin > 0 && settings.opentherm.inPin != settings.opentherm.outPin) {
tOt->enable();
}
ledStatus();
#ifdef USE_TELNET #ifdef USE_TELNET
yield(); yield();
@@ -58,4 +85,57 @@ protected:
} }
} }
} }
void ledStatus() {
static byte blinkLeft = 0;
static bool ledOn = false;
static unsigned long changeTime = 0;
byte errNo = 0;
if (!vars.states.otStatus) {
errNo = 1;
} else if (vars.states.fault) {
errNo = 2;
} else if (vars.states.emergency) {
errNo = 3;
}
if (errNo == 0) {
if (!ledOn) {
digitalWrite(LED_STATUS_PIN, true);
ledOn = true;
}
if (blinkLeft > 0) {
blinkLeft = 0;
}
} else {
if (blinkLeft == 0) {
if (ledOn) {
digitalWrite(LED_STATUS_PIN, false);
ledOn = false;
changeTime = millis();
}
if (millis() - changeTime >= 3000) {
blinkLeft = errNo;
}
}
if (blinkLeft > 0 && millis() - changeTime >= 500) {
if (ledOn) {
digitalWrite(LED_STATUS_PIN, false);
ledOn = false;
blinkLeft--;
} else {
digitalWrite(LED_STATUS_PIN, true);
ledOn = true;
}
changeTime = millis();
}
}
}
}; };

View File

@@ -14,9 +14,11 @@ public:
protected: protected:
unsigned long lastReconnectAttempt = 0; unsigned long lastReconnectAttempt = 0;
unsigned short int reconnectAttempts = 0; unsigned long firstFailConnect = 0;
void setup() { void setup() {
DEBUG("[MQTT] Started");
client.setServer(settings.mqtt.server, settings.mqtt.port); client.setServer(settings.mqtt.server, settings.mqtt.port);
client.setCallback(__callback); client.setCallback(__callback);
haHelper.setPrefix(settings.mqtt.prefix); haHelper.setPrefix(settings.mqtt.prefix);
@@ -38,16 +40,22 @@ protected:
publishHaEntities(); publishHaEntities();
publishNonStaticHaEntities(true); publishNonStaticHaEntities(true);
reconnectAttempts = 0; firstFailConnect = 0;
lastReconnectAttempt = 0; lastReconnectAttempt = 0;
} else { } else {
INFO("Failed to connect to MQTT server\n"); INFO("Failed to connect to MQTT server\n");
if (!vars.states.emergency && ++reconnectAttempts >= EMERGENCY_TRESHOLD) { if (settings.emergency.enable && !vars.states.emergency) {
if (firstFailConnect == 0) {
firstFailConnect = millis();
}
if (millis() - firstFailConnect > EMERGENCY_TIME_TRESHOLD) {
vars.states.emergency = true; vars.states.emergency = true;
INFO("Emergency mode enabled"); INFO("Emergency mode enabled");
} }
}
forceARP(); forceARP();
lastReconnectAttempt = millis(); lastReconnectAttempt = millis();
@@ -117,6 +125,11 @@ protected:
flag = true; flag = true;
} }
if (!doc["heating"]["turbo"].isNull() && doc["heating"]["turbo"].is<bool>()) {
settings.heating.turbo = doc["heating"]["turbo"].as<bool>();
flag = true;
}
if (!doc["heating"]["target"].isNull() && (doc["heating"]["target"].is<float>() || doc["heating"]["target"].is<int>())) { if (!doc["heating"]["target"].isNull() && (doc["heating"]["target"].is<float>() || doc["heating"]["target"].is<int>())) {
settings.heating.target = round(doc["heating"]["target"].as<float>() * 10) / 10; settings.heating.target = round(doc["heating"]["target"].as<float>() * 10) / 10;
flag = true; flag = true;
@@ -272,6 +285,7 @@ protected:
// heating // heating
haHelper.publishSwitchHeating(false); haHelper.publishSwitchHeating(false);
haHelper.publishSwitchHeatingTurbo();
//haHelper.publishNumberHeatingTarget(false); //haHelper.publishNumberHeatingTarget(false);
haHelper.publishNumberHeatingHysteresis(); haHelper.publishNumberHeatingHysteresis();
haHelper.publishSensorHeatingSetpoint(false); haHelper.publishSensorHeatingSetpoint(false);
@@ -305,6 +319,7 @@ protected:
haHelper.publishBinSensorFault(); haHelper.publishBinSensorFault();
haHelper.publishBinSensorDiagnostic(); haHelper.publishBinSensorDiagnostic();
haHelper.publishSensorFaultCode(); haHelper.publishSensorFaultCode();
haHelper.publishSensorRssi();
// sensors // sensors
haHelper.publishSensorModulation(false); haHelper.publishSensorModulation(false);
@@ -383,6 +398,7 @@ protected:
doc["emergency"]["useEquitherm"] = settings.emergency.useEquitherm; doc["emergency"]["useEquitherm"] = settings.emergency.useEquitherm;
doc["heating"]["enable"] = settings.heating.enable; doc["heating"]["enable"] = settings.heating.enable;
doc["heating"]["turbo"] = settings.heating.turbo;
doc["heating"]["target"] = settings.heating.target; doc["heating"]["target"] = settings.heating.target;
doc["heating"]["hysteresis"] = settings.heating.hysteresis; doc["heating"]["hysteresis"] = settings.heating.hysteresis;
@@ -420,6 +436,7 @@ protected:
doc["states"]["fault"] = vars.states.fault; doc["states"]["fault"] = vars.states.fault;
doc["states"]["diagnostic"] = vars.states.diagnostic; doc["states"]["diagnostic"] = vars.states.diagnostic;
doc["states"]["faultCode"] = vars.states.faultCode; doc["states"]["faultCode"] = vars.states.faultCode;
doc["states"]["rssi"] = vars.states.rssi;
doc["sensors"]["modulation"] = vars.sensors.modulation; doc["sensors"]["modulation"] = vars.sensors.modulation;
doc["sensors"]["pressure"] = vars.sensors.pressure; doc["sensors"]["pressure"] = vars.sensors.pressure;
@@ -454,7 +471,7 @@ protected:
if (settings.debug) { if (settings.debug) {
DEBUG_F("MQTT received message\n\r Topic: %s\n\r Data: ", topic); DEBUG_F("MQTT received message\n\r Topic: %s\n\r Data: ", topic);
for (int i = 0; i < length; i++) { for (unsigned int i = 0; i < length; i++) {
DEBUG_STREAM.print((char)payload[i]); DEBUG_STREAM.print((char)payload[i]);
} }
DEBUG_STREAM.print("\n"); DEBUG_STREAM.print("\n");

View File

@@ -1,6 +1,7 @@
#include "lib/CustomOpenTherm.h" #include <new>
#include <CustomOpenTherm.h>
CustomOpenTherm ot(OPENTHERM_IN_PIN, OPENTHERM_OUT_PIN); CustomOpenTherm* ot;
class OpenThermTask: public Task { class OpenThermTask: public Task {
public: public:
@@ -8,100 +9,125 @@ public:
protected: protected:
void setup() { void setup() {
ot.begin(handleInterrupt, responseCallback); ot = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin);
ot.setHandleSendRequestCallback(sendRequestCallback);
ot->begin(handleInterrupt, responseCallback);
ot->setHandleSendRequestCallback(sendRequestCallback);
#ifdef LED_OT_RX_PIN
pinMode(LED_OT_RX_PIN, OUTPUT);
#endif
} }
void loop() { void loop() {
static byte currentHeatingTemp, currentDHWTemp = 0; static byte currentHeatingTemp, currentDHWTemp = 0;
byte newHeatingTemp, newDHWTemp = 0;
unsigned long localResponse; unsigned long localResponse;
setMasterMemberIdCode(); if ( setMasterMemberIdCode() ) {
DEBUG_F("Slave member id code: %u \n", vars.parameters.slaveMemberIdCode); DEBUG_F("Slave member id code: %u\r\n", vars.parameters.slaveMemberIdCode);
DEBUG_F("Master member id code: %u\r\n", settings.opentherm.memberIdCode > 0 ? settings.opentherm.memberIdCode : vars.parameters.slaveMemberIdCode);
localResponse = ot.setBoilerStatus( } else {
settings.heating.enable && pump, WARN("Slave member id failed");
settings.dhw.enable }
bool heatingEnable = (vars.states.emergency || settings.heating.enable) && pump && isReady();
localResponse = ot->setBoilerStatus(
heatingEnable,
settings.dhw.enable,
false, false, true, false, false
); );
if (!ot->isValidResponse(localResponse)) {
if (!ot.isValidResponse(localResponse)) { WARN_F("Invalid response after setBoilerStatus: %s\r\n", ot->statusToString(ot->getLastResponseStatus()));
return; return;
} }
vars.states.heating = ot.isCentralHeatingActive(localResponse); INFO_F("Heating enabled: %d\r\n", heatingEnable);
vars.states.dhw = ot.isHotWaterActive(localResponse); setMaxModulationLevel(heatingEnable ? 100 : 0);
vars.states.flame = ot.isFlameOn(localResponse);
vars.states.fault = ot.isFault(localResponse);
vars.states.diagnostic = ot.isDiagnostic(localResponse);
/*if (vars.dump_request.value) vars.states.heating = ot->isCentralHeatingActive(localResponse);
{ vars.states.dhw = ot->isHotWaterActive(localResponse);
testSupportedIDs(); vars.states.flame = ot->isFlameOn(localResponse);
vars.dump_request.value = false; vars.states.fault = ot->isFault(localResponse);
}*/ vars.states.diagnostic = ot->isDiagnostic(localResponse);
yield();
/*if ( ot.isValidResponse(localResponse) ) {
vars.SlaveMemberIDcode.value = localResponse >> 0 & 0xFF;
uint8_t flags = (localResponse & 0xFFFF) >> 8 & 0xFF;
vars.dhw_present.value = flags & 0x01;
vars.control_type.value = flags & 0x02;
vars.cooling_present.value = flags & 0x04;
vars.dhw_tank_present.value = flags & 0x08;
vars.pump_control_present.value = flags & 0x10;
vars.ch2_present.value = flags & 0x20;
}*/
// Команды чтения данных котла // Команды чтения данных котла
if (millis() - prevUpdateNonEssentialVars > 30000) { if (millis() - prevUpdateNonEssentialVars > 60000) {
updateSlaveParameters(); updateSlaveParameters();
updateMasterParameters(); updateMasterParameters();
// crash?
DEBUG_F("Master type: %u, version: %u \n", vars.parameters.masterType, vars.parameters.masterVersion); DEBUG_F("Master type: %u, version: %u\r\n", vars.parameters.masterType, vars.parameters.masterVersion);
DEBUG_F("Slave type: %u, version: %u \n", vars.parameters.slaveType, vars.parameters.slaveVersion); DEBUG_F("Slave type: %u, version: %u\r\n", vars.parameters.slaveType, vars.parameters.slaveVersion);
updateMinMaxDhwTemp(); updateMinMaxDhwTemp();
updateMinMaxHeatingTemp(); updateMinMaxHeatingTemp();
if (settings.outdoorTempSource == 0) { if (settings.outdoorTempSource == 0) {
updateOutsideTemp(); updateOutsideTemp();
} }
if (vars.states.fault) { if (vars.states.fault) {
updateFaultCode(); updateFaultCode();
ot->sendBoilerReset();
}
if ( vars.states.diagnostic ) {
ot->sendServiceReset();
} }
updatePressure();
prevUpdateNonEssentialVars = millis(); prevUpdateNonEssentialVars = millis();
yield();
} }
updateHeatingTemp();
updateDHWTemp(); updatePressure();
if ( settings.dhw.enable || settings.heating.enable || heatingEnable ) {
updateModulationLevel(); updateModulationLevel();
}
if ( settings.dhw.enable ) {
updateDHWTemp();
} else {
vars.temperatures.dhw = 0;
}
if ( settings.heating.enable || heatingEnable ) {
updateHeatingTemp();
} else {
vars.temperatures.heating = 0;
}
yield();
// //
// Температура ГВС // Температура ГВС
newDHWTemp = settings.dhw.target; byte newDHWTemp = settings.dhw.target;
if (newDHWTemp != currentDHWTemp) { if (settings.dhw.enable && newDHWTemp != currentDHWTemp) {
if (newDHWTemp < vars.parameters.dhwMinTemp || newDHWTemp > vars.parameters.dhwMaxTemp) { if (newDHWTemp < vars.parameters.dhwMinTemp || newDHWTemp > vars.parameters.dhwMaxTemp) {
newDHWTemp = constrain(newDHWTemp, vars.parameters.dhwMinTemp, vars.parameters.dhwMaxTemp); newDHWTemp = constrain(newDHWTemp, vars.parameters.dhwMinTemp, vars.parameters.dhwMaxTemp);
} }
INFO_F("Set DHW temp = %u \n", newDHWTemp); INFO_F("Set DHW temp = %u\r\n", newDHWTemp);
// Записываем заданную температуру ГВС // Записываем заданную температуру ГВС
if (ot.setDHWSetpoint(newDHWTemp)) { if (ot->setDHWSetpoint(newDHWTemp)) {
currentDHWTemp = newDHWTemp; currentDHWTemp = newDHWTemp;
} else {
WARN("Failed set DHW temp");
} }
} }
// //
// Температура отопления // Температура отопления
if (fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001) { if (heatingEnable && fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001) {
INFO_F("Set heating temp = %u \n", vars.parameters.heatingSetpoint); INFO_F("Setting heating temp = %u \n", vars.parameters.heatingSetpoint);
// Записываем заданную температуру // Записываем заданную температуру
if (ot.setBoilerTemperature(vars.parameters.heatingSetpoint)) { if (ot->setBoilerTemperature(vars.parameters.heatingSetpoint)) {
currentHeatingTemp = vars.parameters.heatingSetpoint; currentHeatingTemp = vars.parameters.heatingSetpoint;
} else {
WARN("Failed set heating temp");
} }
} }
@@ -111,35 +137,51 @@ protected:
float halfHyst = settings.heating.hysteresis / 2; float halfHyst = settings.heating.hysteresis / 2;
if (pump && vars.temperatures.indoor - settings.heating.target + 0.0001 >= halfHyst) { if (pump && vars.temperatures.indoor - settings.heating.target + 0.0001 >= halfHyst) {
pump = false; pump = false;
} else if (!pump && vars.temperatures.indoor - settings.heating.target - 0.0001 <= -(halfHyst)) { } else if (!pump && vars.temperatures.indoor - settings.heating.target - 0.0001 <= -(halfHyst)) {
pump = true; pump = true;
} }
} else if (!pump) { } else if (!pump) {
pump = true; pump = true;
} }
} }
void static IRAM_ATTR handleInterrupt() { void static IRAM_ATTR handleInterrupt() {
ot.handleInterrupt(); ot->handleInterrupt();
} }
void static sendRequestCallback(unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) { void static sendRequestCallback(unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) {
printRequestDetail(ot.getDataID(request), status, request, response, attempt); printRequestDetail(ot->getDataID(request), status, request, response, attempt);
} }
void static responseCallback(unsigned long result, OpenThermResponseStatus status) { void static responseCallback(unsigned long result, OpenThermResponseStatus status) {
static byte attempt = 0; static byte attempt = 0;
switch (status) { switch (status) {
case OpenThermResponseStatus::TIMEOUT: case OpenThermResponseStatus::TIMEOUT:
if (++attempt > OPENTHERM_OFFLINE_TRESHOLD) { if (vars.states.otStatus && ++attempt > OPENTHERM_OFFLINE_TRESHOLD) {
vars.states.otStatus = false; vars.states.otStatus = false;
attempt = OPENTHERM_OFFLINE_TRESHOLD; attempt = OPENTHERM_OFFLINE_TRESHOLD;
} }
break; break;
case OpenThermResponseStatus::SUCCESS: case OpenThermResponseStatus::SUCCESS:
attempt = 0; attempt = 0;
if (!vars.states.otStatus) {
vars.states.otStatus = true; vars.states.otStatus = true;
}
#ifdef LED_OT_RX_PIN
{
digitalWrite(LED_OT_RX_PIN, true);
unsigned long ts = millis();
while (millis() - ts < 2) {}
digitalWrite(LED_OT_RX_PIN, false);
}
#endif
break; break;
default: default:
break; break;
} }
@@ -148,9 +190,15 @@ protected:
protected: protected:
bool pump = true; bool pump = true;
unsigned long prevUpdateNonEssentialVars = 0; unsigned long prevUpdateNonEssentialVars = 0;
unsigned long startupTime = millis();
bool isReady() {
return millis() - startupTime > 60000;
}
void static printRequestDetail(OpenThermMessageID id, OpenThermResponseStatus status, unsigned long request, unsigned long response, byte attempt) { void static printRequestDetail(OpenThermMessageID id, OpenThermResponseStatus status, unsigned long request, unsigned long response, byte attempt) {
sprintf(buffer, "OT REQUEST ID: %4d Request: %8x Response: %8x Attempt: %2d Status: %s", id, request, response, attempt, ot.statusToString(status)); sprintf(buffer, "OT REQUEST ID: %4d Request: %8lx Response: %8lx Attempt: %2d Status: %s", id, request, response, attempt, ot->statusToString(status));
if (status != OpenThermResponseStatus::SUCCESS) { if (status != OpenThermResponseStatus::SUCCESS) {
//WARN(buffer); //WARN(buffer);
DEBUG(buffer); DEBUG(buffer);
@@ -159,267 +207,7 @@ protected:
} }
} }
/* bool setMasterMemberIdCode() {
bool getBoilerTemp()
{
unsigned long response;
return sendRequest(ot.buildGetBoilerTemperatureRequest(),response);
}
bool getDHWTemp()
{
unsigned long response;
unsigned long request = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0);
return sendRequest(request,response);
}
bool getOutsideTemp()
{
unsigned long response;
unsigned long request = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0);
return sendRequest(request,response);
}
bool setDHWTemp(float val)
{
unsigned long request = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, ot.temperatureToData(val));
unsigned long response;
return sendRequest(request,response);
}
bool getFaultCode()
{
unsigned long response;
unsigned long request = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0);
return sendRequest(request,response);
}
bool getModulationLevel() {
unsigned long response;
unsigned long request = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::RelModLevel, 0);
return sendRequest(request,response);
}
bool getPressure() {
unsigned long response;
unsigned long request = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::CHPressure, 0);
return sendRequest(request,response);
}
bool sendRequest(unsigned long request, unsigned long& response)
{
send_newts = millis();
if (send_newts - send_ts < 200) {
// Преждем чем слать что то - надо подождать 100ms согласно специфиикации протокола ОТ
delay(200 - (send_newts - send_ts));
}
bool result = ot.sendRequestAync(request);
if(!result) {
WARN("Не могу отправить запрос");
WARN("Шина " + ot.isReady() ? "готова" : "не готова");
return false;
}
while (!ot.isReady())
{
ot.process();
yield(); // This is local Task yield() call which allow us to switch to another task in scheduler
}
send_ts = millis();
response = ot_response;
//printRequestDetail(ot.getDataID(request), request, response);
return true; // Response is global variable
}
void testSupportedIDs()
{
// Basic
unsigned long request;
unsigned long response;
OpenThermMessageID id;
//Command
id = OpenThermMessageID::Command;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//ASFlags
id = OpenThermMessageID::ASFflags;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//TrOverride
id = OpenThermMessageID::TrOverride;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//TSP
id = OpenThermMessageID::TSP;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//TSPindexTSPvalue
id = OpenThermMessageID::TSPindexTSPvalue;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//FHBsize
id = OpenThermMessageID::FHBsize;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//FHBindexFHBvalue
id = OpenThermMessageID::FHBindexFHBvalue;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//MaxCapacityMinModLevel
id = OpenThermMessageID::MaxCapacityMinModLevel;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//TrSet
id = OpenThermMessageID::TrSet;
request = ot.buildRequest(OpenThermRequestType::WRITE, id, ot.temperatureToData(21));
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//RelModLevel
id = OpenThermMessageID::RelModLevel;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//CHPressure
id = OpenThermMessageID::CHPressure;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//DHWFlowRate
id = OpenThermMessageID::DHWFlowRate;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//DayTime
id = OpenThermMessageID::DayTime;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//Date
id = OpenThermMessageID::Date;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//Year
id = OpenThermMessageID::Year;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//TrSetCH2
id = OpenThermMessageID::TrSetCH2;
request = ot.buildRequest(OpenThermRequestType::WRITE, id, ot.temperatureToData(21));
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//Tr
id = OpenThermMessageID::Tr;
request = ot.buildRequest(OpenThermRequestType::WRITE, id, ot.temperatureToData(21));
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//Tret
id = OpenThermMessageID::Tret;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//Texhaust
id = OpenThermMessageID::Texhaust;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//Hcratio
id = OpenThermMessageID::Hcratio;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//RemoteOverrideFunction
id = OpenThermMessageID::RemoteOverrideFunction;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//OEMDiagnosticCode
id = OpenThermMessageID::OEMDiagnosticCode;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//BurnerStarts
id = OpenThermMessageID::BurnerStarts;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//CHPumpStarts
id = OpenThermMessageID::CHPumpStarts;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//DHWPumpValveStarts
id = OpenThermMessageID::DHWPumpValveStarts;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//DHWBurnerStarts
id = OpenThermMessageID::DHWBurnerStarts;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//BurnerOperationHours
id = OpenThermMessageID::BurnerOperationHours;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//CHPumpOperationHours
id = OpenThermMessageID::CHPumpOperationHours;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//DHWPumpValveOperationHours
id = OpenThermMessageID::DHWPumpValveOperationHours;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
//DHWBurnerOperationHours
id = OpenThermMessageID::DHWBurnerOperationHours;
request = ot.buildRequest(OpenThermRequestType::READ, id, 0);
if(sendRequest(request,response))
printRequestDetail(id, ot.getLastResponseStatus(), request, response);
}
*/
void setMasterMemberIdCode() {
//======================================================================================= //=======================================================================================
// Эта группа элементов данных определяет информацию о конфигурации как на ведомых, так // Эта группа элементов данных определяет информацию о конфигурации как на ведомых, так
// и на главных сторонах. Каждый из них имеет группу флагов конфигурации (8 бит) // и на главных сторонах. Каждый из них имеет группу флагов конфигурации (8 бит)
@@ -430,40 +218,88 @@ protected:
// с "кодом идентификатора участника", который идентифицирует производителя устройства. // с "кодом идентификатора участника", который идентифицирует производителя устройства.
//======================================================================================= //=======================================================================================
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SConfigSMemberIDcode, 0)); // 0xFFFF unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SConfigSMemberIDcode, 0)); // 0xFFFF
if (!ot.isValidResponse(response)) { /*uint8_t flags = (response & 0xFFFF) >> 8;
return; DEBUG_F(
"MasterMemberIdCode:\r\n DHW present: %u\r\n Control type: %u\r\n Cooling configuration: %u\r\n DHW configuration: %u\r\n Pump control: %u\r\n CH2 present: %u\r\n Remote water filling function: %u\r\n Heat/cool mode control: %u\r\n Slave MemberID Code: %u\r\n",
flags & 0x01,
flags & 0x02,
flags & 0x04,
flags & 0x08,
flags & 0x10,
flags & 0x20,
flags & 0x40,
flags & 0x80,
response & 0xFF
);*/
if (ot->isValidResponse(response)) {
vars.parameters.slaveMemberIdCode = response & 0xFF;
} else if ( settings.opentherm.memberIdCode <= 0 ) {
return false;
} }
vars.parameters.slaveMemberIdCode = response >> 0 & 0xFF; response = ot->sendRequest(ot->buildRequest(
ot.sendRequest(ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, vars.parameters.slaveMemberIdCode)); OpenThermRequestType::WRITE,
OpenThermMessageID::MConfigMMemberIDcode,
settings.opentherm.memberIdCode > 0 ? settings.opentherm.memberIdCode : vars.parameters.slaveMemberIdCode
));
return ot->isValidResponse(response);
} }
void updateMasterParameters() { bool setMaxModulationLevel(byte value) {
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, 0x013F)); unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MaxRelModLevelSetting, (unsigned int)(value * 256)));
if (!ot.isValidResponse(response)) {
return; return ot->isValidResponse(response);
}
bool setOpenThermVersionMaster() {
unsigned long response;
response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::OpenThermVersionSlave, 0));
if (!ot->isValidResponse(response)) {
return false;
}
// INFO_F("Opentherm version slave: %f\n", ot->getFloat(response));
response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::WRITE_DATA, OpenThermMessageID::OpenThermVersionMaster, response));
if (!ot->isValidResponse(response)) {
return false;
}
// INFO_F("Opentherm version master: %f\n", ot->getFloat(response));
return true;
}
bool updateMasterParameters() {
unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, 0x013F));
if (!ot->isValidResponse(response)) {
return false;
} }
vars.parameters.masterType = (response & 0xFFFF) >> 8; vars.parameters.masterType = (response & 0xFFFF) >> 8;
vars.parameters.masterVersion = response & 0xFF; vars.parameters.masterVersion = response & 0xFF;
return true;
} }
void updateSlaveParameters() { bool updateSlaveParameters() {
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0)); unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0));
if (!ot.isValidResponse(response)) { if (!ot->isValidResponse(response)) {
return; return false;
} }
vars.parameters.slaveType = (response & 0xFFFF) >> 8; vars.parameters.slaveType = (response & 0xFFFF) >> 8;
vars.parameters.slaveVersion = response & 0xFF; vars.parameters.slaveVersion = response & 0xFF;
return true;
} }
void updateMinMaxDhwTemp() { bool updateMinMaxDhwTemp() {
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::TdhwSetUBTdhwSetLB, 0)); unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::TdhwSetUBTdhwSetLB, 0));
if (!ot->isValidResponse(response)) {
if (!ot.isValidResponse(response)) { return false;
return;
} }
byte minTemp = response & 0xFF; byte minTemp = response & 0xFF;
@@ -472,14 +308,17 @@ protected:
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
vars.parameters.dhwMinTemp = minTemp; vars.parameters.dhwMinTemp = minTemp;
vars.parameters.dhwMaxTemp = maxTemp; vars.parameters.dhwMaxTemp = maxTemp;
}
return true;
} }
void updateMinMaxHeatingTemp() { return false;
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::MaxTSetUBMaxTSetLB, 0)); }
if (!ot.isValidResponse(response)) { bool updateMinMaxHeatingTemp() {
return; unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::MaxTSetUBMaxTSetLB, 0));
if (!ot->isValidResponse(response)) {
return false;
} }
byte minTemp = response & 0xFF; byte minTemp = response & 0xFF;
@@ -488,55 +327,80 @@ protected:
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
vars.parameters.heatingMinTemp = minTemp; vars.parameters.heatingMinTemp = minTemp;
vars.parameters.heatingMaxTemp = maxTemp; vars.parameters.heatingMaxTemp = maxTemp;
}
return true;
} }
void updateOutsideTemp() { return false;
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0));
if (ot.isValidResponse(response)) {
vars.temperatures.outdoor = ot.getFloat(response);
}
} }
void updateHeatingTemp() { bool updateOutsideTemp() {
unsigned long response = ot.sendRequest(ot.buildGetBoilerTemperatureRequest()); unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0));
if (!ot->isValidResponse(response)) {
if (ot.isValidResponse(response)) { return false;
vars.temperatures.heating = ot.getFloat(response);
} }
vars.temperatures.outdoor = ot->getFloat(response);
return true;
}
bool updateHeatingTemp() {
unsigned long response = ot->sendRequest(ot->buildGetBoilerTemperatureRequest());
if (!ot->isValidResponse(response)) {
return false;
}
vars.temperatures.heating = ot->getFloat(response);
return true;
} }
void updateDHWTemp() { bool updateDHWTemp() {
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tdhw, 0)); unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermMessageType::READ, OpenThermMessageID::Tdhw, 0));
if (!ot->isValidResponse(response)) {
if (ot.isValidResponse(response)) { return false;
vars.temperatures.dhw = ot.getFloat(response);
}
} }
void updateFaultCode() { vars.temperatures.dhw = ot->getFloat(response);
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0)); return true;
}
bool updateFaultCode() {
unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0));
if (!ot->isValidResponse(response)) {
return false;
}
if (ot.isValidResponse(response)) {
vars.states.faultCode = response & 0xFF; vars.states.faultCode = response & 0xFF;
} return true;
} }
void updateModulationLevel() { bool updateModulationLevel() {
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::RelModLevel, 0)); unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::RelModLevel, 0));
if (ot.isValidResponse(response)) { if (!ot->isValidResponse(response)) {
vars.sensors.modulation = ot.getFloat(response); return false;
}
} }
void updatePressure() { float modulation = ot->f88(response);
unsigned long response = ot.sendRequest(ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::CHPressure, 0)); if (!vars.states.flame) {
vars.sensors.modulation = 0;
if (ot.isValidResponse(response)) { } else {
vars.sensors.pressure = ot.getFloat(response); vars.sensors.modulation = modulation;
} }
return true;
}
bool updatePressure() {
unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::CHPressure, 0));
if (!ot->isValidResponse(response)) {
return false;
}
vars.sensors.pressure = ot->getFloat(response);
return true;
} }
}; };

View File

@@ -1,4 +1,4 @@
#include "lib/Equitherm.h" #include <Equitherm.h>
#include <GyverPID.h> #include <GyverPID.h>
#include <PIDtuner.h> #include <PIDtuner.h>
@@ -21,13 +21,25 @@ protected:
void setup() {} void setup() {}
void loop() { void loop() {
byte newTemp; byte newTemp = vars.parameters.heatingSetpoint;
if (vars.states.emergency) { if (vars.states.emergency) {
if (settings.heating.turbo) {
settings.heating.turbo = false;
INFO("[REGULATOR] Turbo mode auto disabled");
}
newTemp = getEmergencyModeTemp(); newTemp = getEmergencyModeTemp();
} else { } else {
if (vars.tuning.enable || tunerInit) { if (vars.tuning.enable || tunerInit) {
if (settings.heating.turbo) {
settings.heating.turbo = false;
INFO("[REGULATOR] Turbo mode auto disabled");
}
newTemp = getTuningModeTemp(); newTemp = getTuningModeTemp();
if (newTemp == 0) { if (newTemp == 0) {
@@ -36,6 +48,12 @@ protected:
} }
if (!vars.tuning.enable) { if (!vars.tuning.enable) {
if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || (settings.equitherm.enable && settings.pid.enable))) {
settings.heating.turbo = false;
INFO("[REGULATOR] Turbo mode auto disabled");
}
newTemp = getNormalModeTemp(); newTemp = getNormalModeTemp();
} }
} }
@@ -56,13 +74,13 @@ protected:
// if use equitherm // if use equitherm
if (settings.emergency.useEquitherm && settings.outdoorTempSource != 1) { if (settings.emergency.useEquitherm && settings.outdoorTempSource != 1) {
float etResult = getEquithermTemp(); float etResult = getEquithermTemp(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp);
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) {
prevEtResult = etResult; prevEtResult = etResult;
newTemp += etResult; newTemp += etResult;
INFO_F("[REGULATOR][EQUITHERM] New emergency result: %u (%f) \n", (byte) round(etResult), etResult); INFO_F("[REGULATOR][EQUITHERM] New emergency result: %u (%f) \n", (int)round(etResult), etResult);
} else { } else {
newTemp += prevEtResult; newTemp += prevEtResult;
@@ -81,19 +99,23 @@ protected:
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001) { if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001) {
prevHeatingTarget = settings.heating.target; prevHeatingTarget = settings.heating.target;
INFO_F("[REGULATOR] New target: %f \n", settings.heating.target); INFO_F("[REGULATOR] New target: %f \n", settings.heating.target);
if (settings.equitherm.enable && settings.pid.enable) {
pidRegulator.integral = 0;
INFO_F("[REGULATOR][PID] Integral sum has been reset");
}
} }
// if use equitherm // if use equitherm
if (settings.equitherm.enable) { if (settings.equitherm.enable) {
float etResult = getEquithermTemp(); float etResult = getEquithermTemp(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp);
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) {
prevEtResult = etResult; prevEtResult = etResult;
newTemp += etResult; newTemp += etResult;
INFO_F("[REGULATOR][EQUITHERM] New result: %u (%f) \n", (byte) round(etResult), etResult); INFO_F("[REGULATOR][EQUITHERM] New result: %u (%f) \n", (int)round(etResult), etResult);
} else { } else {
newTemp += prevEtResult; newTemp += prevEtResult;
@@ -102,13 +124,16 @@ protected:
// if use pid // if use pid
if (settings.pid.enable) { if (settings.pid.enable) {
float pidResult = getPidTemp(); float pidResult = getPidTemp(
settings.equitherm.enable ? -30 : vars.parameters.heatingMinTemp,
settings.equitherm.enable ? 30 : vars.parameters.heatingMaxTemp
);
if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) {
prevPidResult = pidResult; prevPidResult = pidResult;
newTemp += pidResult; newTemp += pidResult;
INFO_F("[REGULATOR][PID] New result: %u (%f) \n", (byte) round(pidResult), pidResult); INFO_F("[REGULATOR][PID] New result: %u (%f) \n", (int)round(pidResult), pidResult);
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
@@ -147,7 +172,9 @@ protected:
} else if (vars.tuning.regulator == 1) { } else if (vars.tuning.regulator == 1) {
// PID tuner // PID tuner
float defaultTemp = settings.equitherm.enable ? getEquithermTemp() : settings.heating.target; float defaultTemp = settings.equitherm.enable
? getEquithermTemp(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp)
: settings.heating.target;
if (tunerInit && pidTuner.getState() == 3) { if (tunerInit && pidTuner.getState() == 3) {
INFO(F("[REGULATOR][TUNING][PID] Finished")); INFO(F("[REGULATOR][TUNING][PID] Finished"));
@@ -203,7 +230,7 @@ protected:
} }
} }
float getEquithermTemp() { float getEquithermTemp(int minTemp, int maxTemp) {
if (vars.states.emergency) { if (vars.states.emergency) {
etRegulator.Kt = 0; etRegulator.Kt = 0;
etRegulator.indoorTemp = 0; etRegulator.indoorTemp = 0;
@@ -214,13 +241,17 @@ protected:
etRegulator.indoorTemp = round(vars.temperatures.indoor); etRegulator.indoorTemp = round(vars.temperatures.indoor);
etRegulator.outdoorTemp = round(vars.temperatures.outdoor); etRegulator.outdoorTemp = round(vars.temperatures.outdoor);
} else {
if (settings.heating.turbo) {
etRegulator.Kt = 10;
} else { } else {
etRegulator.Kt = settings.equitherm.t_factor; etRegulator.Kt = settings.equitherm.t_factor;
}
etRegulator.indoorTemp = vars.temperatures.indoor; etRegulator.indoorTemp = vars.temperatures.indoor;
etRegulator.outdoorTemp = vars.temperatures.outdoor; etRegulator.outdoorTemp = vars.temperatures.outdoor;
} }
etRegulator.setLimits(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp); etRegulator.setLimits(minTemp, maxTemp);
etRegulator.Kn = settings.equitherm.n_factor; etRegulator.Kn = settings.equitherm.n_factor;
// etRegulator.Kn = tuneEquithermN(etRegulator.Kn, vars.temperatures.indoor, settings.heating.target, 300, 1800, 0.01, 1); // etRegulator.Kn = tuneEquithermN(etRegulator.Kn, vars.temperatures.indoor, settings.heating.target, 300, 1800, 0.01, 1);
etRegulator.Kk = settings.equitherm.k_factor; etRegulator.Kk = settings.equitherm.k_factor;
@@ -229,12 +260,12 @@ protected:
return etRegulator.getResult(); return etRegulator.getResult();
} }
float getPidTemp() { float getPidTemp(int minTemp, int maxTemp) {
pidRegulator.Kp = settings.pid.p_factor; pidRegulator.Kp = settings.pid.p_factor;
pidRegulator.Ki = settings.pid.i_factor; pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.Kd = settings.pid.d_factor; pidRegulator.Kd = settings.pid.d_factor;
pidRegulator.setLimits(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp); pidRegulator.setLimits(minTemp, maxTemp);
pidRegulator.input = vars.temperatures.indoor; pidRegulator.input = vars.temperatures.indoor;
pidRegulator.setpoint = settings.heating.target; pidRegulator.setpoint = settings.heating.target;

View File

@@ -16,7 +16,7 @@ protected:
if (outdoorSensor.online()) { if (outdoorSensor.online()) {
if (outdoorSensor.readTemp()) { if (outdoorSensor.readTemp()) {
float rawTemp = outdoorSensor.getTemp(); float rawTemp = outdoorSensor.getTemp();
INFO_F("[SENSORS][DS18B20] Raw temp: %f \n", rawTemp); DEBUG_F("[SENSORS][DS18B20] Raw temp: %f \n", rawTemp);
if (emptyOutdoorTemp) { if (emptyOutdoorTemp) {
filteredOutdoorTemp = rawTemp; filteredOutdoorTemp = rawTemp;

View File

@@ -4,6 +4,12 @@ struct Settings {
byte outdoorTempSource = 0; byte outdoorTempSource = 0;
char hostname[80] = "opentherm"; char hostname[80] = "opentherm";
struct {
byte inPin = 5;
byte outPin = 4;
unsigned int memberIdCode = 4;
} opentherm;
struct { struct {
char server[80]; char server[80];
int port = 1883; int port = 1883;
@@ -21,6 +27,7 @@ struct Settings {
struct { struct {
bool enable = true; bool enable = true;
bool turbo = false;
float target = 40.0f; float target = 40.0f;
float hysteresis = 0.5f; float hysteresis = 0.5f;
} heating; } heating;
@@ -39,9 +46,9 @@ struct Settings {
struct { struct {
bool enable = false; bool enable = false;
float n_factor = 0.67f; float n_factor = 0.7f;
float k_factor = 1.0f; float k_factor = 3.0f;
float t_factor = 5.0f; float t_factor = 2.0f;
} equitherm; } equitherm;
} settings; } settings;
@@ -61,6 +68,7 @@ struct Variables {
bool fault = false; bool fault = false;
bool diagnostic = false; bool diagnostic = false;
byte faultCode = 0; byte faultCode = 0;
int8_t rssi = 0;
} states; } states;
struct { struct {

View File

@@ -1,12 +1,11 @@
// #include <WiFiClient.h>
#define WM_MDNS
#include <WiFiManager.h> #include <WiFiManager.h>
//#include <ESP8266mDNS.h>
//#include <WiFiUdp.h>
// Wifimanager // Wifimanager
WiFiManager wm; WiFiManager wm;
WiFiManagerParameter* wmHostname; WiFiManagerParameter* wmHostname;
WiFiManagerParameter* wmOtInPin;
WiFiManagerParameter* wmOtOutPin;
WiFiManagerParameter* wmOtMemberIdCode;
WiFiManagerParameter* wmMqttServer; WiFiManagerParameter* wmMqttServer;
WiFiManagerParameter* wmMqttPort; WiFiManagerParameter* wmMqttPort;
WiFiManagerParameter* wmMqttUser; WiFiManagerParameter* wmMqttUser;
@@ -25,6 +24,18 @@ protected:
wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80); wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80);
wm.addParameter(wmHostname); wm.addParameter(wmHostname);
sprintf(buffer, "%d", settings.opentherm.inPin);
wmOtInPin = new WiFiManagerParameter("ot_in_pin", "Opentherm pin IN", buffer, 1);
wm.addParameter(wmOtInPin);
sprintf(buffer, "%d", settings.opentherm.outPin);
wmOtOutPin = new WiFiManagerParameter("ot_out_pin", "Opentherm pin OUT", buffer, 1);
wm.addParameter(wmOtOutPin);
sprintf(buffer, "%d", settings.opentherm.memberIdCode);
wmOtMemberIdCode = new WiFiManagerParameter("ot_member_id_code", "Opentherm member id code", buffer, 5);
wm.addParameter(wmOtMemberIdCode);
wmMqttServer = new WiFiManagerParameter("mqtt_server", "MQTT server", settings.mqtt.server, 80); wmMqttServer = new WiFiManagerParameter("mqtt_server", "MQTT server", settings.mqtt.server, 80);
wm.addParameter(wmMqttServer); wm.addParameter(wmMqttServer);
@@ -46,15 +57,22 @@ protected:
wm.setHostname(settings.hostname); wm.setHostname(settings.hostname);
wm.setWiFiAutoReconnect(true); wm.setWiFiAutoReconnect(true);
wm.setAPClientCheck(true);
wm.setConfigPortalBlocking(false); wm.setConfigPortalBlocking(false);
wm.setSaveParamsCallback(saveParamsCallback); wm.setSaveParamsCallback(saveParamsCallback);
wm.setConfigPortalTimeout(300); wm.setConfigPortalTimeout(180);
wm.setDisableConfigPortal(false); //wm.setDisableConfigPortal(false);
wm.autoConnect(AP_SSID); wm.autoConnect(AP_SSID, AP_PASSWORD);
} }
void loop() { void loop() {
static unsigned short prevCounterLiters = 0;
/*if (WiFi.status() != WL_CONNECTED && !wm.getWebPortalActive() && !wm.getConfigPortalActive()) {
wm.autoConnect(AP_SSID);
}*/
if (connected && WiFi.status() != WL_CONNECTED) { if (connected && WiFi.status() != WL_CONNECTED) {
connected = false; connected = false;
INFO("[wifi] Disconnected"); INFO("[wifi] Disconnected");
@@ -62,6 +80,10 @@ protected:
} else if (!connected && WiFi.status() == WL_CONNECTED) { } else if (!connected && WiFi.status() == WL_CONNECTED) {
connected = true; connected = true;
if (wm.getConfigPortalActive()) {
wm.stopConfigPortal();
}
INFO_F("[wifi] Connected. IP address: %s, RSSI: %d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI()); INFO_F("[wifi] Connected. IP address: %s, RSSI: %d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
} }
@@ -73,14 +95,34 @@ protected:
} }
void static saveParamsCallback() { void static saveParamsCallback() {
strcpy(settings.hostname, (*wmHostname).getValue()); strcpy(settings.hostname, wmHostname->getValue());
strcpy(settings.mqtt.server, (*wmMqttServer).getValue()); settings.opentherm.inPin = atoi(wmOtInPin->getValue());
settings.mqtt.port = atoi((*wmMqttPort).getValue()); settings.opentherm.outPin = atoi(wmOtOutPin->getValue());
strcpy(settings.mqtt.user, (*wmMqttUser).getValue()); settings.opentherm.memberIdCode = atoi(wmOtMemberIdCode->getValue());
strcpy(settings.mqtt.password, (*wmMqttPassword).getValue()); strcpy(settings.mqtt.server, wmMqttServer->getValue());
strcpy(settings.mqtt.prefix, (*wmMqttPrefix).getValue()); settings.mqtt.port = atoi(wmMqttPort->getValue());
strcpy(settings.mqtt.user, wmMqttUser->getValue());
strcpy(settings.mqtt.password, wmMqttPassword->getValue());
strcpy(settings.mqtt.prefix, wmMqttPrefix->getValue());
INFO_F("Settings\nHostname: %s, Server: %s, port: %d, user: %s, pass: %s\n", settings.hostname, settings.mqtt.server, settings.mqtt.port, settings.mqtt.user, settings.mqtt.password); INFO_F(
"New settings:\r\n"
" Hostname: %s\r\n"
" OT in pin: %d"
" OT out pin: %d"
" OT member id code: %d"
" Mqtt server: %s:%d\r\n"
" Mqtt user: %s\r\n"
" Mqtt pass: %s\r\n",
settings.hostname,
settings.opentherm.inPin,
settings.opentherm.outPin,
settings.opentherm.memberIdCode,
settings.mqtt.server,
settings.mqtt.port,
settings.mqtt.user,
settings.mqtt.password
);
eeSettings.updateNow(); eeSettings.updateNow();
INFO(F("Settings saved")); INFO(F("Settings saved"));
} }

View File

@@ -1,13 +1,12 @@
#define OT_GATEWAY_VERSION "1.0.7" #define OT_GATEWAY_VERSION "1.2.1"
#define AP_SSID "OpenTherm Gateway" #define AP_SSID "OpenTherm Gateway"
#define AP_PASSWORD "otgateway123456"
#define USE_TELNET #define USE_TELNET
#define EMERGENCY_TRESHOLD 10 #define EMERGENCY_TIME_TRESHOLD 120000
#define MQTT_RECONNECT_INTERVAL 5000 #define MQTT_RECONNECT_INTERVAL 5000
#define MQTT_KEEPALIVE 30 #define MQTT_KEEPALIVE 30
#define OPENTHERM_IN_PIN 4
#define OPENTHERM_OUT_PIN 5
#define OPENTHERM_OFFLINE_TRESHOLD 10 #define OPENTHERM_OFFLINE_TRESHOLD 10
#define DS18B20_PIN 2 #define DS18B20_PIN 2
@@ -16,6 +15,9 @@
#define DS_CHECK_CRC true #define DS_CHECK_CRC true
#define DS_CRC_USE_TABLE true #define DS_CRC_USE_TABLE true
#define LED_STATUS_PIN 13
#define LED_OT_RX_PIN 15
#define CONFIG_URL "http://%s/" #define CONFIG_URL "http://%s/"

View File

@@ -1,47 +0,0 @@
#pragma once
#include <Arduino.h>
#include <OpenTherm.h>
extern SchedulerClass Scheduler;
class CustomOpenTherm : public OpenTherm {
private:
unsigned long send_ts = millis();
void(*handleSendRequestCallback)(unsigned long, unsigned long, OpenThermResponseStatus status, byte attempt);
public:
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false) : OpenTherm(inPin, outPin, isSlave) {}
void setHandleSendRequestCallback(void(*handleSendRequestCallback)(unsigned long, unsigned long, OpenThermResponseStatus status, byte attempt)) {
this->handleSendRequestCallback = handleSendRequestCallback;
}
unsigned long sendRequest(unsigned long request, byte attempts = 5, byte _attempt = 0) {
_attempt++;
while (send_ts > 0 && millis() - send_ts < 200) {
Scheduler.yield();
}
unsigned long _response;
if (!sendRequestAync(request)) {
_response = 0;
} else {
while (!isReady()) {
Scheduler.yield();
process();
}
_response = getLastResponse();
}
if (handleSendRequestCallback != NULL) {
handleSendRequestCallback(request, _response, getLastResponseStatus(), _attempt);
}
send_ts = millis();
if (getLastResponseStatus() == OpenThermResponseStatus::SUCCESS || _attempt >= attempts) {
return _response;
} else {
return sendRequest(request, attempts, _attempt);
}
}
};

View File

@@ -1,3 +1,4 @@
#include <Arduino.h>
#include "defines.h" #include "defines.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <TelnetStream.h> #include <TelnetStream.h>
@@ -52,7 +53,7 @@ void setup() {
tMqtt = new MqttTask(false); tMqtt = new MqttTask(false);
Scheduler.start(tMqtt); Scheduler.start(tMqtt);
tOt = new OpenThermTask(true); tOt = new OpenThermTask(false);
Scheduler.start(tOt); Scheduler.start(tOt);
tSensors = new SensorsTask(false, DS18B20_INTERVAL); tSensors = new SensorsTask(false, DS18B20_INTERVAL);