6 Commits

Author SHA1 Message Date
Yurii
a5996cc93d refactor: rounding sensor values 2024-11-15 01:28:28 +03:00
Yurii
9d6b6c18ab fix: pub network.rssi 2024-11-15 01:18:45 +03:00
Yurii
e6119dc7ee refactor: restart action improved 2024-11-15 00:55:38 +03:00
Yurii
19feb85230 refactor: some changes 2024-11-15 00:25:41 +03:00
Yurii
0d71a674b6 feat: added more OT polled IDs & reformat code
* ID79 - exhaust CO2
* ID84 - exhaust fan speed
* ID85 - supply fan speed
* ID29 - solar storage temp
* ID30 - solar collector temp
* ID35 - boiler fan speed setpoint & actual
2024-11-14 23:02:46 +03:00
Yurii
34eabca64a refactor: improved web cache 2024-11-14 22:30:34 +03:00
17 changed files with 711 additions and 289 deletions

View File

@@ -60,6 +60,10 @@ const styles = (cb) => {
const items = paths.styles.bundles[name]; const items = paths.styles.bundles[name];
src(items) src(items)
.pipe(replace(
"{BUILD_TIME}",
Math.floor(Date.now() / 1000)
))
.pipe(postcss([ .pipe(postcss([
cssnano({ preset: 'advanced' }) cssnano({ preset: 'advanced' })
])) ]))
@@ -78,6 +82,10 @@ const scripts = (cb) => {
const items = paths.scripts.bundles[name]; const items = paths.scripts.bundles[name];
src(items) src(items)
.pipe(replace(
"{BUILD_TIME}",
Math.floor(Date.now() / 1000)
))
.pipe(terser().on('error', console.error)) .pipe(terser().on('error', console.error))
.pipe(concat(name)) .pipe(concat(name))
.pipe(gzip({ .pipe(gzip({
@@ -94,6 +102,10 @@ const jsonFiles = (cb) => {
const item = paths.json[i]; const item = paths.json[i];
src(item.src) src(item.src)
.pipe(replace(
"{BUILD_TIME}",
Math.floor(Date.now() / 1000)
))
.pipe(jsonminify()) .pipe(jsonminify())
.pipe(gzip({ .pipe(gzip({
append: true append: true

View File

@@ -218,7 +218,6 @@ protected:
CanHandleCallback canHandleCallback; CanHandleCallback canHandleCallback;
BeforeSendCallback beforeSendCallback; BeforeSendCallback beforeSendCallback;
TemplateCallback templateCallback; TemplateCallback templateCallback;
String eTag;
const char* uri = nullptr; const char* uri = nullptr;
const char* path = nullptr; const char* path = nullptr;
const char* cacheHeader = nullptr; const char* cacheHeader = nullptr;

View File

@@ -1,5 +1,8 @@
#include <FS.h> #include <FS.h>
#include <detail/mimetable.h> #include <detail/mimetable.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <detail/RequestHandlersImpl.h>
#endif
using namespace mime; using namespace mime;
@@ -47,21 +50,25 @@ public:
return true; return true;
} }
#if defined(ARDUINO_ARCH_ESP8266)
if (server._eTagEnabled) { if (server._eTagEnabled) {
if (server._eTagFunction) { if (this->eTag.isEmpty()) {
this->eTag = (server._eTagFunction)(*this->fs, this->path); if (server._eTagFunction) {
this->eTag = (server._eTagFunction)(*this->fs, this->path);
} else if (this->eTag.isEmpty()) { } else {
this->eTag = esp8266webserver::calcETag(*this->fs, this->path); #if defined(ARDUINO_ARCH_ESP8266)
this->eTag = esp8266webserver::calcETag(*this->fs, this->path);
#elif defined(ARDUINO_ARCH_ESP32)
this->eTag = StaticRequestHandler::calcETag(*this->fs, this->path);
#endif
}
} }
if (server.header(F("If-None-Match")).equals(this->eTag.c_str())) { if (!this->eTag.isEmpty() && server.header(F("If-None-Match")).equals(this->eTag.c_str())) {
server.send(304); server.send(304);
return true; return true;
} }
} }
#endif
if (!this->path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !this->fs->exists(path)) { if (!this->path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !this->fs->exists(path)) {
String pathWithGz = this->path + FPSTR(mimeTable[gz].endsWith); String pathWithGz = this->path + FPSTR(mimeTable[gz].endsWith);
@@ -84,11 +91,11 @@ public:
server.sendHeader(F("Cache-Control"), this->cacheHeader); server.sendHeader(F("Cache-Control"), this->cacheHeader);
} }
#if defined(ARDUINO_ARCH_ESP8266) if (server._eTagEnabled && !this->eTag.isEmpty()) {
if (server._eTagEnabled && this->eTag.length() > 0) {
server.sendHeader(F("ETag"), this->eTag); server.sendHeader(F("ETag"), this->eTag);
} }
#if defined(ARDUINO_ARCH_ESP8266)
server.streamFile(file, F("text/html"), method); server.streamFile(file, F("text/html"), method);
#else #else
server.streamFile(file, F("text/html"), 200); server.streamFile(file, F("text/html"), 200);

View File

@@ -67,15 +67,25 @@ public:
break; break;
case Sensors::Purpose::MODULATION_LEVEL: case Sensors::Purpose::MODULATION_LEVEL:
case Sensors::Purpose::POWER_FACTOR:
doc[FPSTR(HA_DEVICE_CLASS)] = F("power_factor"); doc[FPSTR(HA_DEVICE_CLASS)] = F("power_factor");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT); doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT);
break; break;
case Sensors::Purpose::CURRENT_POWER: case Sensors::Purpose::POWER:
doc[FPSTR(HA_DEVICE_CLASS)] = F("power"); doc[FPSTR(HA_DEVICE_CLASS)] = F("power");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("kW"); doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("kW");
break; break;
case Sensors::Purpose::FAN_SPEED:
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("RPM");
break;
case Sensors::Purpose::CO2:
doc[FPSTR(HA_DEVICE_CLASS)] = F("carbon_dioxide");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("ppm");
break;
case Sensors::Purpose::PRESSURE: case Sensors::Purpose::PRESSURE:
doc[FPSTR(HA_DEVICE_CLASS)] = F("pressure"); doc[FPSTR(HA_DEVICE_CLASS)] = F("pressure");
if (unit == UnitSystem::METRIC) { if (unit == UnitSystem::METRIC) {
@@ -137,10 +147,15 @@ public:
doc[FPSTR(HA_ICON)] = F("mdi:fire-circle"); doc[FPSTR(HA_ICON)] = F("mdi:fire-circle");
break; break;
case Sensors::Purpose::CURRENT_POWER: case Sensors::Purpose::POWER_FACTOR:
case Sensors::Purpose::POWER:
doc[FPSTR(HA_ICON)] = F("mdi:chart-bar"); doc[FPSTR(HA_ICON)] = F("mdi:chart-bar");
break; break;
case Sensors::Purpose::FAN_SPEED:
doc[FPSTR(HA_ICON)] = F("mdi:fan");
break;
case Sensors::Purpose::PRESSURE: case Sensors::Purpose::PRESSURE:
doc[FPSTR(HA_ICON)] = F("mdi:gauge"); doc[FPSTR(HA_ICON)] = F("mdi:gauge");
break; break;

View File

@@ -32,7 +32,8 @@ protected:
unsigned long lastHeapInfo = 0; unsigned long lastHeapInfo = 0;
unsigned int minFreeHeap = 0; unsigned int minFreeHeap = 0;
unsigned int minMaxFreeBlockHeap = 0; unsigned int minMaxFreeBlockHeap = 0;
unsigned long restartSignalTime = 0; bool restartSignalReceived = false;
unsigned long restartSignalReceivedTime = 0;
bool heatingEnabled = false; bool heatingEnabled = false;
unsigned long heatingDisabledTime = 0; unsigned long heatingDisabledTime = 0;
PumpStartReason extPumpStartReason = PumpStartReason::NONE; PumpStartReason extPumpStartReason = PumpStartReason::NONE;
@@ -73,10 +74,28 @@ protected:
} }
if (vars.actions.restart) { if (vars.actions.restart) {
this->restartSignalReceivedTime = millis();
this->restartSignalReceived = true;
vars.actions.restart = false; vars.actions.restart = false;
this->restartSignalTime = millis();
Log.sinfoln(FPSTR(L_MAIN), F("Restart signal received. Restart after 10 sec.")); Log.sinfoln(FPSTR(L_MAIN), F("Received restart signal"));
}
if (!vars.states.restarting && this->restartSignalReceived && millis() - this->restartSignalReceivedTime > 5000) {
vars.states.restarting = true;
// save settings
fsSettings.updateNow();
// save sensors settings
fsSensorsSettings.updateNow();
// force save network settings
if (fsNetworkSettings.updateNow() == FD_FILE_ERR && LittleFS.begin()) {
fsNetworkSettings.write();
}
Log.sinfoln(FPSTR(L_MAIN), F("Restart scheduled in 10 sec."));
} }
vars.mqtt.connected = tMqtt->isConnected(); vars.mqtt.connected = tMqtt->isConnected();
@@ -148,20 +167,9 @@ protected:
// restart // restart
if (this->restartSignalTime > 0 && millis() - this->restartSignalTime > 10000) { if (this->restartSignalReceived && millis() - this->restartSignalReceivedTime > 15000) {
// save settings this->restartSignalReceived = false;
fsSettings.updateNow();
// save sensors settings
fsSensorsSettings.updateNow();
// force save network settings
if (fsNetworkSettings.updateNow() == FD_FILE_ERR && LittleFS.begin()) {
fsNetworkSettings.write();
}
this->restartSignalTime = 0;
this->delay(500);
ESP.restart(); ESP.restart();
} }
} }
@@ -170,8 +178,10 @@ protected:
unsigned int freeHeap = getFreeHeap(); unsigned int freeHeap = getFreeHeap();
unsigned int maxFreeBlockHeap = getMaxFreeBlockHeap(); unsigned int maxFreeBlockHeap = getMaxFreeBlockHeap();
if (!this->restartSignalTime && (freeHeap < 2048 || maxFreeBlockHeap < 2048)) { // critical heap
this->restartSignalTime = millis(); if (!vars.states.restarting && (freeHeap < 2048 || maxFreeBlockHeap < 2048)) {
this->restartSignalReceivedTime = millis();
vars.states.restarting = true;
} }
if (settings.system.logLevel < TinyLogger::Level::VERBOSE) { if (settings.system.logLevel < TinyLogger::Level::VERBOSE) {

View File

@@ -184,6 +184,10 @@ protected:
} }
void loop() { void loop() {
if (vars.states.restarting || vars.states.upgrading) {
return;
}
if (this->connected && !this->client->connected()) { if (this->connected && !this->client->connected()) {
this->connected = false; this->connected = false;
this->onDisconnect(); this->onDisconnect();

View File

@@ -106,6 +106,10 @@ protected:
} }
void loop() { void loop() {
if (vars.states.restarting || vars.states.upgrading) {
return;
}
if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) { if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) {
this->setup(); this->setup();
@@ -214,6 +218,13 @@ protected:
Sensors::setConnectionStatusByType(Sensors::Type::OT_PRESSURE, false); Sensors::setConnectionStatusByType(Sensors::Type::OT_PRESSURE, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_MODULATION_LEVEL, false); Sensors::setConnectionStatusByType(Sensors::Type::OT_MODULATION_LEVEL, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_CURRENT_POWER, false); Sensors::setConnectionStatusByType(Sensors::Type::OT_CURRENT_POWER, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_EXHAUST_CO2, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_EXHAUST_FAN_SPEED, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_SUPPLY_FAN_SPEED, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_SOLAR_STORAGE_TEMP, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_SOLAR_COLLECTOR_TEMP, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_FAN_SPEED_SETPOINT, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_FAN_SPEED_CURRENT, false);
this->initialized = false; this->initialized = false;
vars.slave.connected = false; vars.slave.connected = false;
@@ -652,14 +663,14 @@ protected:
if (Sensors::getAmountByType(Sensors::Type::OT_EXHAUST_TEMP)) { if (Sensors::getAmountByType(Sensors::Type::OT_EXHAUST_TEMP)) {
if (this->updateExhaustTemp()) { if (this->updateExhaustTemp()) {
float convertedExhaustTemp = convertTemp( float convertedExhaustTemp = convertTemp(
vars.slave.exhaustTemp, vars.slave.exhaust.temp,
settings.opentherm.unitSystem, settings.opentherm.unitSystem,
settings.system.unitSystem settings.system.unitSystem
); );
Log.snoticeln( Log.snoticeln(
FPSTR(L_OT), F("Received exhaust temp: %.2f (converted: %.2f)"), FPSTR(L_OT), F("Received exhaust temp: %.2f (converted: %.2f)"),
vars.slave.exhaustTemp, convertedExhaustTemp vars.slave.exhaust.temp, convertedExhaustTemp
); );
Sensors::setValueByType( Sensors::setValueByType(
@@ -720,6 +731,76 @@ protected:
} }
} }
// Update solar storage temp
if (Sensors::getAmountByType(Sensors::Type::OT_SOLAR_STORAGE_TEMP)) {
if (this->updateSolarStorageTemp()) {
float convertedSolarStorageTemp = convertTemp(
vars.slave.solar.storage,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
Log.snoticeln(
FPSTR(L_OT), F("Received solar storage temp: %.2f (converted: %.2f)"),
vars.slave.solar.storage, convertedSolarStorageTemp
);
Sensors::setValueByType(
Sensors::Type::OT_SOLAR_STORAGE_TEMP, convertedSolarStorageTemp,
Sensors::ValueType::PRIMARY, true, true
);
} else {
Log.swarningln(FPSTR(L_OT), F("Failed receive solar storage temp"));
}
}
// Update solar collector temp
if (Sensors::getAmountByType(Sensors::Type::OT_SOLAR_COLLECTOR_TEMP)) {
if (this->updateSolarCollectorTemp()) {
float convertedSolarCollectorTemp = convertTemp(
vars.slave.solar.collector,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
Log.snoticeln(
FPSTR(L_OT), F("Received solar collector temp: %.2f (converted: %.2f)"),
vars.slave.solar.collector, convertedSolarCollectorTemp
);
Sensors::setValueByType(
Sensors::Type::OT_SOLAR_COLLECTOR_TEMP, convertedSolarCollectorTemp,
Sensors::ValueType::PRIMARY, true, true
);
} else {
Log.swarningln(FPSTR(L_OT), F("Failed receive solar collector temp"));
}
}
// Update fan speed
if (
Sensors::getAmountByType(Sensors::Type::OT_FAN_SPEED_SETPOINT) ||
Sensors::getAmountByType(Sensors::Type::OT_FAN_SPEED_CURRENT)
) {
if (this->updateFanSpeed()) {
Log.snoticeln(
FPSTR(L_OT), F("Received fan speed, setpoint: %hhu%%, current: %hhu%%"),
vars.slave.fanSpeed.setpoint, vars.slave.fanSpeed.current
);
Sensors::setValueByType(
Sensors::Type::OT_FAN_SPEED_SETPOINT, vars.slave.fanSpeed.setpoint,
Sensors::ValueType::PRIMARY, true, true
);
Sensors::setValueByType(
Sensors::Type::OT_FAN_SPEED_CURRENT, vars.slave.fanSpeed.current,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Update pressure // Update pressure
if (Sensors::getAmountByType(Sensors::Type::OT_PRESSURE)) { if (Sensors::getAmountByType(Sensors::Type::OT_PRESSURE)) {
if (this->updatePressure()) { if (this->updatePressure()) {
@@ -744,6 +825,59 @@ protected:
} }
} }
// Update exhaust CO2
if (Sensors::getAmountByType(Sensors::Type::OT_EXHAUST_CO2)) {
if (this->updateExhaustCo2()) {
Log.snoticeln(
FPSTR(L_OT), F("Received exhaust CO2: %hu ppm"),
vars.slave.exhaust.co2
);
Sensors::setValueByType(
Sensors::Type::OT_EXHAUST_CO2, vars.slave.exhaust.co2,
Sensors::ValueType::PRIMARY, true, true
);
} else {
Log.swarningln(FPSTR(L_OT), F("Failed receive exhaust CO2"));
}
}
// Update exhaust fan speed
if (Sensors::getAmountByType(Sensors::Type::OT_EXHAUST_FAN_SPEED)) {
if (this->updateExhaustFanSpeed()) {
Log.snoticeln(
FPSTR(L_OT), F("Received exhaust fan speed: %hu rpm"),
vars.slave.exhaust.fanSpeed
);
Sensors::setValueByType(
Sensors::Type::OT_EXHAUST_FAN_SPEED, vars.slave.exhaust.fanSpeed,
Sensors::ValueType::PRIMARY, true, true
);
} else {
Log.swarningln(FPSTR(L_OT), F("Failed receive exhaust fan speed"));
}
}
// Update supply fan speed
if (Sensors::getAmountByType(Sensors::Type::OT_SUPPLY_FAN_SPEED)) {
if (this->updateSupplyFanSpeed()) {
Log.snoticeln(
FPSTR(L_OT), F("Received supply fan speed: %hu rpm"),
vars.slave.fanSpeed.supply
);
Sensors::setValueByType(
Sensors::Type::OT_SUPPLY_FAN_SPEED, vars.slave.fanSpeed.supply,
Sensors::ValueType::PRIMARY, true, true
);
} else {
Log.swarningln(FPSTR(L_OT), F("Failed receive supply fan speed"));
}
}
// Fault reset action // Fault reset action
if (vars.actions.resetFault) { if (vars.actions.resetFault) {
@@ -996,42 +1130,59 @@ protected:
|| fabsf(target - vars.slave.ch2.targetTemp) > 0.001f; || fabsf(target - vars.slave.ch2.targetTemp) > 0.001f;
} }
bool setHeatingTemp(const float temperature) { bool updateSlaveConfig() {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature); unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA,
OpenThermMessageType::WRITE_DATA, OpenThermMessageID::SConfigSMemberIDcode,
OpenThermMessageID::TSet, 0
request
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TSet)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::SConfigSMemberIDcode)) {
return false; return false;
} }
vars.slave.heating.targetTemp = CustomOpenTherm::getFloat(response); vars.slave.memberId = response & 0xFF;
vars.slave.flags = (response & 0xFFFF) >> 8;
return CustomOpenTherm::getUInt(response) == request; /*uint8_t flags = (response & 0xFFFF) >> 8;
Log.straceln(
"OT",
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 Raw: %u"),
(bool) (flags & 0x01),
(bool) (flags & 0x02),
(bool) (flags & 0x04),
(bool) (flags & 0x08),
(bool) (flags & 0x10),
(bool) (flags & 0x20),
(bool) (flags & 0x40),
(bool) (flags & 0x80),
response & 0xFF,
response
);*/
return true;
} }
bool setCh2Temp(const float temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature); bool setMaxModulationLevel(const uint8_t value) {
const unsigned int request = CustomOpenTherm::toFloat(value);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::TsetCH2, OpenThermMessageID::MaxRelModLevelSetting,
request request
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TsetCH2)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MaxRelModLevelSetting)) {
return false; return false;
} }
vars.slave.ch2.targetTemp = CustomOpenTherm::getFloat(response); vars.slave.modulation.max = CustomOpenTherm::getFloat(response);
return CustomOpenTherm::getUInt(response) == request; return CustomOpenTherm::getUInt(response) == request;
} }
@@ -1056,6 +1207,47 @@ protected:
return CustomOpenTherm::getUInt(response) == request; return CustomOpenTherm::getUInt(response) == request;
} }
bool setRoomTemp(float temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::Tr,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tr)) {
return false;
}
vars.slave.heating.indoorTemp = CustomOpenTherm::getFloat(response);
return CustomOpenTherm::getUInt(response) == request;
}
bool setRoomTempCh2(float temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TrCH2,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TrCH2)) {
return false;
}
vars.slave.ch2.indoorTemp = CustomOpenTherm::getFloat(response);
return CustomOpenTherm::getUInt(response) == request;
}
bool setRoomSetpoint(const float temperature) { bool setRoomSetpoint(const float temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature); const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
@@ -1096,99 +1288,100 @@ protected:
return CustomOpenTherm::getUInt(response) == request; return CustomOpenTherm::getUInt(response) == request;
} }
bool setRoomTemp(float temperature) { bool setMaxHeatingTemp(const uint8_t temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature); const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::Tr, OpenThermMessageID::MaxTSet,
request request
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tr)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MaxTSet)) {
return false; return false;
} }
vars.slave.heating.indoorTemp = CustomOpenTherm::getFloat(response);
return CustomOpenTherm::getUInt(response) == request; return CustomOpenTherm::getUInt(response) == request;
} }
bool setRoomTempCh2(float temperature) { bool setHeatingTemp(const float temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature); const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TrCH2, OpenThermMessageID::TSet,
request request
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TrCH2)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TSet)) {
return false; return false;
} }
vars.slave.ch2.indoorTemp = CustomOpenTherm::getFloat(response); vars.slave.heating.targetTemp = CustomOpenTherm::getFloat(response);
return CustomOpenTherm::getUInt(response) == request; return CustomOpenTherm::getUInt(response) == request;
} }
bool updateCh2Temp() { bool setCh2Temp(const float temperature) {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
OpenThermRequestType::READ_DATA, const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageID::TflowCH2, OpenThermMessageType::WRITE_DATA,
0 OpenThermMessageID::TsetCH2,
request
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TflowCH2)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TsetCH2)) {
return false; return false;
} }
vars.slave.ch2.currentTemp = CustomOpenTherm::getFloat(response); vars.slave.ch2.targetTemp = CustomOpenTherm::getFloat(response);
return true; return CustomOpenTherm::getUInt(response) == request;
} }
bool updateSlaveConfig() { bool setMasterVersion(const uint8_t version, const uint8_t type) {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::SConfigSMemberIDcode, OpenThermMessageID::MasterVersion,
0 (unsigned int) version | (unsigned int) type << 8
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::SConfigSMemberIDcode)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MasterVersion)) {
return false; return false;
} }
vars.slave.memberId = response & 0xFF; uint8_t rVersion = response & 0xFF;
vars.slave.flags = (response & 0xFFFF) >> 8; uint8_t rType = (response & 0xFFFF) >> 8;
/*uint8_t flags = (response & 0xFFFF) >> 8; return rVersion == version && rType == type;
Log.straceln( }
"OT",
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 Raw: %u"),
(bool) (flags & 0x01),
(bool) (flags & 0x02),
(bool) (flags & 0x04),
(bool) (flags & 0x08),
(bool) (flags & 0x10),
(bool) (flags & 0x20),
(bool) (flags & 0x40),
(bool) (flags & 0x80),
response & 0xFF,
response
);*/
return true; bool setMasterOtVersion(const float version) {
const unsigned int request = CustomOpenTherm::toFloat(version);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::OpenThermVersionMaster,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::OpenThermVersionMaster)) {
return false;
}
return CustomOpenTherm::getUInt(response) == request;
} }
/** /**
@@ -1231,26 +1424,6 @@ protected:
return CustomOpenTherm::getUInt(response) == request; return CustomOpenTherm::getUInt(response) == request;
} }
bool setMaxModulationLevel(const uint8_t value) {
const unsigned int request = CustomOpenTherm::toFloat(value);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::MaxRelModLevelSetting,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MaxRelModLevelSetting)) {
return false;
}
vars.slave.modulation.max = CustomOpenTherm::getFloat(response);
return CustomOpenTherm::getUInt(response) == request;
}
bool updateSlaveOtVersion() { bool updateSlaveOtVersion() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
@@ -1270,24 +1443,6 @@ protected:
return true; return true;
} }
bool setMasterOtVersion(const float version) {
const unsigned int request = CustomOpenTherm::toFloat(version);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::OpenThermVersionMaster,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::OpenThermVersionMaster)) {
return false;
}
return CustomOpenTherm::getUInt(response) == request;
}
bool updateSlaveVersion() { bool updateSlaveVersion() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
@@ -1308,24 +1463,27 @@ protected:
return true; return true;
} }
bool setMasterVersion(const uint8_t version, const uint8_t type) { bool updateMinModulationLevel() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::MasterVersion, OpenThermMessageID::MaxCapacityMinModLevel,
(unsigned int) version | (unsigned int) type << 8 0
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MasterVersion)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MaxCapacityMinModLevel)) {
return false; return false;
} }
uint8_t rVersion = response & 0xFF; vars.slave.modulation.min = response & 0xFF;
uint8_t rType = (response & 0xFFFF) >> 8; vars.slave.power.max = (response & 0xFFFF) >> 8;
vars.slave.power.min = vars.slave.modulation.min > 0 && vars.slave.power.max > 0.1f
? (vars.slave.modulation.min * 0.01f) * vars.slave.power.max
: 0.0f;
return rVersion == version && rType == type; return true;
} }
bool updateMinMaxDhwTemp() { bool updateMinMaxDhwTemp() {
@@ -1382,135 +1540,68 @@ protected:
return false; return false;
} }
bool setMaxHeatingTemp(const uint8_t temperature) { bool updateFaultCode() {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::MaxTSet,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MaxTSet)) {
return false;
}
return CustomOpenTherm::getUInt(response) == request;
}
bool updateOutdoorTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::Toutside, OpenThermMessageID::ASFflags,
0 0
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Toutside)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::ASFflags)) {
return false; return false;
} }
vars.slave.heating.outdoorTemp = CustomOpenTherm::getFloat(response); vars.slave.fault.code = response & 0xFF;
return true; return true;
} }
bool updateExhaustTemp() { bool updateDiagCode() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::Texhaust, OpenThermMessageID::OEMDiagnosticCode,
0 0
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Texhaust)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::OEMDiagnosticCode)) {
return false; return false;
} }
float value = (float) CustomOpenTherm::getInt(response); vars.slave.diag.code = CustomOpenTherm::getUInt(response);
if (!isValidTemp(value, settings.opentherm.unitSystem, -40, 500)) {
return false;
}
vars.slave.exhaustTemp = value;
return true; return true;
} }
bool updateHeatExchangerTemp() { bool updateModulationLevel() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::TboilerHeatExchanger, OpenThermMessageID::RelModLevel,
0 0
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TboilerHeatExchanger)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::RelModLevel)) {
return false;
}
float value = (float) CustomOpenTherm::getInt(response);
if (value <= 0) {
return false;
}
vars.slave.heatExchangerTemp = value;
return true;
}
bool updateHeatingTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tboiler,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tboiler)) {
return false; return false;
} }
float value = CustomOpenTherm::getFloat(response); float value = CustomOpenTherm::getFloat(response);
if (value <= 0) { if (value < 0) {
return false; return false;
} }
vars.slave.heating.currentTemp = value; vars.slave.modulation.current = value;
return true; return true;
} }
bool updateHeatingReturnTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tret,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tret)) {
return false;
}
vars.slave.heating.returnTemp = CustomOpenTherm::getFloat(response);
return true;
}
bool updateDhwTemp() { bool updateDhwTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA, OpenThermMessageType::READ_DATA,
@@ -1589,87 +1680,191 @@ protected:
return true; return true;
} }
bool updateFaultCode() { bool updateHeatingTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermMessageType::READ_DATA,
OpenThermMessageID::ASFflags, OpenThermMessageID::Tboiler,
0 0
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::ASFflags)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tboiler)) {
return false;
}
vars.slave.fault.code = response & 0xFF;
return true;
}
bool updateDiagCode() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::OEMDiagnosticCode,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::OEMDiagnosticCode)) {
return false;
}
vars.slave.diag.code = CustomOpenTherm::getUInt(response);
return true;
}
bool updateModulationLevel() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::RelModLevel,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::RelModLevel)) {
return false; return false;
} }
float value = CustomOpenTherm::getFloat(response); float value = CustomOpenTherm::getFloat(response);
if (value < 0) { if (value <= 0) {
return false; return false;
} }
vars.slave.modulation.current = value; vars.slave.heating.currentTemp = value;
return true; return true;
} }
bool updateMinModulationLevel() { bool updateHeatingReturnTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermMessageType::READ_DATA,
OpenThermMessageID::MaxCapacityMinModLevel, OpenThermMessageID::Tret,
0 0
)); ));
if (!CustomOpenTherm::isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MaxCapacityMinModLevel)) { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tret)) {
return false; return false;
} }
vars.slave.modulation.min = response & 0xFF; vars.slave.heating.returnTemp = CustomOpenTherm::getFloat(response);
vars.slave.power.max = (response & 0xFFFF) >> 8;
vars.slave.power.min = vars.slave.modulation.min > 0 && vars.slave.power.max > 0.1f return true;
? (vars.slave.modulation.min * 0.01f) * vars.slave.power.max }
: 0.0f;
bool updateCh2Temp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::TflowCH2,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TflowCH2)) {
return false;
}
vars.slave.ch2.currentTemp = CustomOpenTherm::getFloat(response);
return true;
}
bool updateExhaustTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::Texhaust,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Texhaust)) {
return false;
}
float value = (float) CustomOpenTherm::getInt(response);
if (!isValidTemp(value, settings.opentherm.unitSystem, -40, 500)) {
return false;
}
vars.slave.exhaust.temp = value;
return true;
}
bool updateHeatExchangerTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::TboilerHeatExchanger,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TboilerHeatExchanger)) {
return false;
}
float value = (float) CustomOpenTherm::getInt(response);
if (value <= 0) {
return false;
}
vars.slave.heatExchangerTemp = value;
return true;
}
bool updateOutdoorTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::Toutside,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Toutside)) {
return false;
}
vars.slave.heating.outdoorTemp = CustomOpenTherm::getFloat(response);
return true;
}
bool updateSolarStorageTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tstorage,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tstorage)) {
return false;
}
vars.slave.solar.storage = CustomOpenTherm::getFloat(response);
return true;
}
bool updateSolarCollectorTemp() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tcollector,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tcollector)) {
return false;
}
vars.slave.solar.collector = CustomOpenTherm::getFloat(response);
return true;
}
bool updateFanSpeed() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::BoilerFanSpeedSetpointAndActual,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::BoilerFanSpeedSetpointAndActual)) {
return false;
}
vars.slave.fanSpeed.setpoint = (response & 0xFFFF) >> 8;
vars.slave.fanSpeed.current = response & 0xFF;
return true; return true;
} }
@@ -1697,4 +1892,61 @@ protected:
return true; return true;
} }
bool updateExhaustCo2() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::CO2exhaust,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::CO2exhaust)) {
return false;
}
vars.slave.exhaust.co2 = CustomOpenTherm::getUInt(response);
return true;
}
bool updateExhaustFanSpeed() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::RPMexhaust,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::RPMexhaust)) {
return false;
}
vars.slave.exhaust.fanSpeed = CustomOpenTherm::getUInt(response);
return true;
}
bool updateSupplyFanSpeed() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::RPMsupply,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::RPMsupply)) {
return false;
}
vars.slave.fanSpeed.supply = CustomOpenTherm::getUInt(response);
return true;
}
}; };

View File

@@ -1,5 +1,5 @@
#define PORTAL_CACHE_TIME "max-age=86400" //#define PORTAL_CACHE "max-age=86400"
#define PORTAL_CACHE (settings.system.logLevel >= TinyLogger::Level::TRACE ? nullptr : PORTAL_CACHE_TIME) #define PORTAL_CACHE nullptr
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266WebServer.h> #include <ESP8266WebServer.h>
#include <Updater.h> #include <Updater.h>
@@ -72,9 +72,24 @@ protected:
void setup() { void setup() {
this->dnsServer->setTTL(0); this->dnsServer->setTTL(0);
this->dnsServer->setErrorReplyCode(DNSReplyCode::NoError); this->dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
#ifdef ARDUINO_ARCH_ESP8266 this->webServer->enableETag(true, [](FS &fs, const String &fName) -> const String {
this->webServer->enableETag(true); char buf[32];
#endif {
MD5Builder md5;
md5.begin();
md5.add(fName);
md5.add(" " BUILD_ENV " " BUILD_VERSION " " __DATE__ " " __TIME__);
md5.calculate();
md5.getChars(buf);
}
String etag;
etag.reserve(34);
etag += '\"';
etag.concat(buf, 32);
etag += '\"';
return etag;
});
// index page // index page
/*auto indexPage = (new DynamicPage("/", &LittleFS, "/pages/index.html")) /*auto indexPage = (new DynamicPage("/", &LittleFS, "/pages/index.html"))
@@ -174,6 +189,11 @@ protected:
return true; return true;
})->setBeforeUpgradeCallback([](UpgradeHandler::UpgradeType type) -> bool { })->setBeforeUpgradeCallback([](UpgradeHandler::UpgradeType type) -> bool {
if (vars.states.restarting) {
return false;
}
vars.states.upgrading = true;
return true; return true;
})->setAfterUpgradeCallback([this](const UpgradeHandler::UpgradeResult& fwResult, const UpgradeHandler::UpgradeResult& fsResult) { })->setAfterUpgradeCallback([this](const UpgradeHandler::UpgradeResult& fwResult, const UpgradeHandler::UpgradeResult& fsResult) {
unsigned short status = 200; unsigned short status = 200;
@@ -194,6 +214,8 @@ protected:
response.concat(fsResult.error); response.concat(fsResult.error);
response.concat(F("\"}}")); response.concat(F("\"}}"));
this->webServer->send(status, F("application/json"), response); this->webServer->send(status, F("application/json"), response);
vars.states.upgrading = false;
}); });
this->webServer->addHandler(upgradeHandler); this->webServer->addHandler(upgradeHandler);
@@ -232,6 +254,10 @@ protected:
} }
} }
if (vars.states.restarting) {
return this->webServer->send(503);
}
const String& plain = this->webServer->arg(0); const String& plain = this->webServer->arg(0);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/backup/restore %d bytes: %s"), plain.length(), plain.c_str()); Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/backup/restore %d bytes: %s"), plain.length(), plain.c_str());
@@ -322,6 +348,10 @@ protected:
return this->webServer->send(401); return this->webServer->send(401);
} }
} }
if (vars.states.restarting) {
return this->webServer->send(503);
}
const String& plain = this->webServer->arg(0); const String& plain = this->webServer->arg(0);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/network/settings %d bytes: %s"), plain.length(), plain.c_str()); Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/network/settings %d bytes: %s"), plain.length(), plain.c_str());
@@ -437,6 +467,10 @@ protected:
return this->webServer->send(401); return this->webServer->send(401);
} }
} }
if (vars.states.restarting) {
return this->webServer->send(503);
}
const String& plain = this->webServer->arg(0); const String& plain = this->webServer->arg(0);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/settings %d bytes: %s"), plain.length(), plain.c_str()); Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/settings %d bytes: %s"), plain.length(), plain.c_str());
@@ -542,6 +576,10 @@ protected:
return this->webServer->send(401); return this->webServer->send(401);
} }
} }
if (vars.states.restarting) {
return this->webServer->send(503);
}
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
if (!this->webServer->hasArg(F("id")) || this->webServer->args() != 1) { if (!this->webServer->hasArg(F("id")) || this->webServer->args() != 1) {

View File

@@ -32,6 +32,10 @@ protected:
#endif #endif
void loop() { void loop() {
if (vars.states.restarting || vars.states.upgrading) {
return;
}
this->indoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP); this->indoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP);
//this->outdoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP); //this->outdoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP);

View File

@@ -18,6 +18,13 @@ public:
OT_PRESSURE = 9, OT_PRESSURE = 9,
OT_MODULATION_LEVEL = 10, OT_MODULATION_LEVEL = 10,
OT_CURRENT_POWER = 11, OT_CURRENT_POWER = 11,
OT_EXHAUST_CO2 = 12,
OT_EXHAUST_FAN_SPEED = 13,
OT_SUPPLY_FAN_SPEED = 14,
OT_SOLAR_STORAGE_TEMP = 15,
OT_SOLAR_COLLECTOR_TEMP = 16,
OT_FAN_SPEED_SETPOINT = 17,
OT_FAN_SPEED_CURRENT = 18,
NTC_10K_TEMP = 50, NTC_10K_TEMP = 50,
DALLAS_TEMP = 51, DALLAS_TEMP = 51,
@@ -38,8 +45,11 @@ public:
DHW_FLOW_RATE = 6, DHW_FLOW_RATE = 6,
EXHAUST_TEMP = 7, EXHAUST_TEMP = 7,
MODULATION_LEVEL = 8, MODULATION_LEVEL = 8,
CURRENT_POWER = 9,
POWER_FACTOR = 248,
POWER = 249,
FAN_SPEED = 250,
CO2 = 251,
PRESSURE = 252, PRESSURE = 252,
HUMIDITY = 253, HUMIDITY = 253,
TEMPERATURE = 254, TEMPERATURE = 254,

View File

@@ -59,6 +59,10 @@ protected:
#endif #endif
void loop() { void loop() {
if (vars.states.restarting || vars.states.upgrading) {
return;
}
if (isPollingDallasSensors()) { if (isPollingDallasSensors()) {
pollingDallasSensors(false); pollingDallasSensors(false);
this->yield(); this->yield();

View File

@@ -215,7 +215,7 @@ Sensors::Settings sensorsSettings[SENSORS_AMOUNT] = {
{ {
true, true,
"Power", "Power",
Sensors::Purpose::CURRENT_POWER, Sensors::Purpose::POWER,
Sensors::Type::OT_CURRENT_POWER, Sensors::Type::OT_CURRENT_POWER,
} }
}; };
@@ -288,7 +288,6 @@ struct Variables {
bool connected = false; bool connected = false;
bool flame = false; bool flame = false;
float pressure = 0.0f; float pressure = 0.0f;
float exhaustTemp = 0.0f;
float heatExchangerTemp = 0.0f; float heatExchangerTemp = 0.0f;
struct { struct {
@@ -313,6 +312,23 @@ struct Variables {
float max = 0.0f; float max = 0.0f;
} power; } power;
struct {
float temp = 0.0f;
uint16_t co2 = 0;
uint16_t fanSpeed = 0;
} exhaust;
struct {
float storage = 0.0f;
float collector = 0.0f;
} solar;
struct {
uint8_t setpoint = 0;
uint8_t current = 0;
uint16_t supply = 0;
} fanSpeed;
struct { struct {
bool active = false; bool active = false;
bool enabled = false; bool enabled = false;
@@ -350,4 +366,9 @@ struct Variables {
bool resetFault = false; bool resetFault = false;
bool resetDiagnostic = false; bool resetDiagnostic = false;
} actions; } actions;
struct {
bool restarting = false;
bool upgrading = false;
} states;
} vars; } vars;

View File

@@ -1490,7 +1490,11 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
case static_cast<uint8_t>(Sensors::Purpose::DHW_FLOW_RATE): case static_cast<uint8_t>(Sensors::Purpose::DHW_FLOW_RATE):
case static_cast<uint8_t>(Sensors::Purpose::EXHAUST_TEMP): case static_cast<uint8_t>(Sensors::Purpose::EXHAUST_TEMP):
case static_cast<uint8_t>(Sensors::Purpose::MODULATION_LEVEL): case static_cast<uint8_t>(Sensors::Purpose::MODULATION_LEVEL):
case static_cast<uint8_t>(Sensors::Purpose::CURRENT_POWER):
case static_cast<uint8_t>(Sensors::Purpose::POWER_FACTOR):
case static_cast<uint8_t>(Sensors::Purpose::POWER):
case static_cast<uint8_t>(Sensors::Purpose::FAN_SPEED):
case static_cast<uint8_t>(Sensors::Purpose::CO2):
case static_cast<uint8_t>(Sensors::Purpose::PRESSURE): case static_cast<uint8_t>(Sensors::Purpose::PRESSURE):
case static_cast<uint8_t>(Sensors::Purpose::HUMIDITY): case static_cast<uint8_t>(Sensors::Purpose::HUMIDITY):
case static_cast<uint8_t>(Sensors::Purpose::TEMPERATURE): case static_cast<uint8_t>(Sensors::Purpose::TEMPERATURE):
@@ -1523,6 +1527,14 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
case static_cast<uint8_t>(Sensors::Type::OT_PRESSURE): case static_cast<uint8_t>(Sensors::Type::OT_PRESSURE):
case static_cast<uint8_t>(Sensors::Type::OT_MODULATION_LEVEL): case static_cast<uint8_t>(Sensors::Type::OT_MODULATION_LEVEL):
case static_cast<uint8_t>(Sensors::Type::OT_CURRENT_POWER): case static_cast<uint8_t>(Sensors::Type::OT_CURRENT_POWER):
case static_cast<uint8_t>(Sensors::Type::OT_EXHAUST_CO2):
case static_cast<uint8_t>(Sensors::Type::OT_EXHAUST_FAN_SPEED):
case static_cast<uint8_t>(Sensors::Type::OT_SUPPLY_FAN_SPEED):
case static_cast<uint8_t>(Sensors::Type::OT_SOLAR_STORAGE_TEMP):
case static_cast<uint8_t>(Sensors::Type::OT_SOLAR_COLLECTOR_TEMP):
case static_cast<uint8_t>(Sensors::Type::OT_FAN_SPEED_SETPOINT):
case static_cast<uint8_t>(Sensors::Type::OT_FAN_SPEED_CURRENT):
case static_cast<uint8_t>(Sensors::Type::NTC_10K_TEMP): case static_cast<uint8_t>(Sensors::Type::NTC_10K_TEMP):
case static_cast<uint8_t>(Sensors::Type::DALLAS_TEMP): case static_cast<uint8_t>(Sensors::Type::DALLAS_TEMP):
case static_cast<uint8_t>(Sensors::Type::BLUETOOTH): case static_cast<uint8_t>(Sensors::Type::BLUETOOTH):
@@ -1662,13 +1674,13 @@ void sensorResultToJson(const uint8_t sensorId, JsonVariant dst) {
dst[FPSTR(S_SIGNAL_QUALITY)] = rSensor.signalQuality; dst[FPSTR(S_SIGNAL_QUALITY)] = rSensor.signalQuality;
if (sSensor.type == Sensors::Type::BLUETOOTH) { if (sSensor.type == Sensors::Type::BLUETOOTH) {
dst[FPSTR(S_TEMPERATURE)] = rSensor.values[static_cast<uint8_t>(Sensors::ValueType::TEMPERATURE)]; dst[FPSTR(S_TEMPERATURE)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::TEMPERATURE)], 3);
dst[FPSTR(S_HUMIDITY)] = rSensor.values[static_cast<uint8_t>(Sensors::ValueType::HUMIDITY)]; dst[FPSTR(S_HUMIDITY)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::HUMIDITY)], 3);
dst[FPSTR(S_BATTERY)] = rSensor.values[static_cast<uint8_t>(Sensors::ValueType::BATTERY)]; dst[FPSTR(S_BATTERY)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::BATTERY)], 1);
dst[FPSTR(S_RSSI)] = rSensor.values[static_cast<uint8_t>(Sensors::ValueType::RSSI)]; dst[FPSTR(S_RSSI)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::RSSI)], 0);
} else { } else {
dst[FPSTR(S_VALUE)] = rSensor.values[static_cast<uint8_t>(Sensors::ValueType::PRIMARY)]; dst[FPSTR(S_VALUE)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::PRIMARY)], 3);
} }
} }
@@ -1759,6 +1771,7 @@ void varsToJson(const Variables& src, JsonVariant dst) {
mDhw[FPSTR(S_MAX_TEMP)] = settings.dhw.maxTemp; mDhw[FPSTR(S_MAX_TEMP)] = settings.dhw.maxTemp;
master[FPSTR(S_NETWORK)][FPSTR(S_CONNECTED)] = src.network.connected; master[FPSTR(S_NETWORK)][FPSTR(S_CONNECTED)] = src.network.connected;
master[FPSTR(S_NETWORK)][FPSTR(S_RSSI)] = src.network.rssi;
master[FPSTR(S_MQTT)][FPSTR(S_CONNECTED)] = src.mqtt.connected; master[FPSTR(S_MQTT)][FPSTR(S_CONNECTED)] = src.mqtt.connected;
master[FPSTR(S_EMERGENCY)][FPSTR(S_STATE)] = src.emergency.state; master[FPSTR(S_EMERGENCY)][FPSTR(S_STATE)] = src.emergency.state;
master[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_STATE)] = src.externalPump.state; master[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_STATE)] = src.externalPump.state;

View File

@@ -179,7 +179,10 @@
"dhwFlowRate": "DHW, flow rate", "dhwFlowRate": "DHW, flow rate",
"exhaustTemp": "Exhaust temperature", "exhaustTemp": "Exhaust temperature",
"modLevel": "Modulation level (in percents)", "modLevel": "Modulation level (in percents)",
"currentPower": "Current power (in kWt)", "powerFactor": "Power (in percent)",
"power": "Power (in kWt)",
"fanSpeed": "Fan speed",
"co2": "CO2",
"pressure": "Pressure", "pressure": "Pressure",
"humidity": "Humidity", "humidity": "Humidity",
"temperature": "Temperature", "temperature": "Temperature",
@@ -199,6 +202,14 @@
"otPressure": "OpenTherm, pressure", "otPressure": "OpenTherm, pressure",
"otModLevel": "OpenTherm, modulation level", "otModLevel": "OpenTherm, modulation level",
"otCurrentPower": "OpenTherm, current power", "otCurrentPower": "OpenTherm, current power",
"otExhaustCo2": "OpenTherm, exhaust CO2",
"otExhaustFanSpeed": "OpenTherm, exhaust fan speed",
"otSupplyFanSpeed": "OpenTherm, supply fan speed",
"otSolarStorageTemp": "OpenTherm, solar storage temp",
"otSolarCollectorTemp": "OpenTherm, solar collector temp",
"otFanSpeedSetpoint": "OpenTherm, setpoint fan speed",
"otFanSpeedCurrent": "OpenTherm, current fan speed",
"ntcTemp": "NTC sensor", "ntcTemp": "NTC sensor",
"dallasTemp": "DALLAS sensor", "dallasTemp": "DALLAS sensor",
"bluetooth": "BLE sensor", "bluetooth": "BLE sensor",
@@ -408,7 +419,7 @@
"note": { "note": {
"disclaimer1": "After a successful upgrade the filesystem, ALL settings will be reset to default values! Save backup before upgrading.", "disclaimer1": "After a successful upgrade the filesystem, ALL settings will be reset to default values! Save backup before upgrading.",
"disclaimer2": "After a successful upgrade, the device will automatically reboot after 10 seconds." "disclaimer2": "After a successful upgrade, the device will automatically reboot after 15 seconds."
}, },
"settingsFile": "Settings file", "settingsFile": "Settings file",

View File

@@ -179,7 +179,10 @@
"dhwFlowRate": "ГВС, расход/скорость потока", "dhwFlowRate": "ГВС, расход/скорость потока",
"exhaustTemp": "Температура выхлопных газов", "exhaustTemp": "Температура выхлопных газов",
"modLevel": "Уровень модуляции (в процентах)", "modLevel": "Уровень модуляции (в процентах)",
"currentPower": "Текущая мощность (в кВт)", "powerFactor": "Мощность (в процентах)",
"power": "Мощность (в кВт)",
"fanSpeed": "Скорость вентилятора",
"co2": "CO2",
"pressure": "Давление", "pressure": "Давление",
"humidity": "Влажность", "humidity": "Влажность",
"temperature": "Температура", "temperature": "Температура",
@@ -199,6 +202,14 @@
"otPressure": "OpenTherm, давление", "otPressure": "OpenTherm, давление",
"otModLevel": "OpenTherm, уровень модуляции", "otModLevel": "OpenTherm, уровень модуляции",
"otCurrentPower": "OpenTherm, текущая мощность", "otCurrentPower": "OpenTherm, текущая мощность",
"otExhaustCo2": "OpenTherm, CO2 вытяжного воздуха",
"otExhaustFanSpeed": "OpenTherm, скорость вытяжного вентилятора",
"otSupplyFanSpeed": "OpenTherm, скорость приточного вентилятора",
"otSolarStorageTemp": "OpenTherm, темп. бойлера солн. коллектора",
"otSolarCollectorTemp": "OpenTherm, темп. солн. коллектора",
"otFanSpeedSetpoint": "OpenTherm, установленная мощн. вентилятора",
"otFanSpeedCurrent": "OpenTherm, текущая мощн. вентилятора",
"ntcTemp": "NTC датчик", "ntcTemp": "NTC датчик",
"dallasTemp": "DALLAS датчик", "dallasTemp": "DALLAS датчик",
"bluetooth": "BLE датчик", "bluetooth": "BLE датчик",
@@ -408,7 +419,7 @@
"note": { "note": {
"disclaimer1": "После успешного обновления файловой системы ВСЕ настройки будут сброшены на стандартные! Создайте резервную копию ПЕРЕД обновлением.", "disclaimer1": "После успешного обновления файловой системы ВСЕ настройки будут сброшены на стандартные! Создайте резервную копию ПЕРЕД обновлением.",
"disclaimer2": "После успешного обновления устройство автоматически перезагрузится через 10 секунд." "disclaimer2": "После успешного обновления устройство автоматически перезагрузится через 15 секунд."
}, },
"settingsFile": "Файл настроек", "settingsFile": "Файл настроек",

View File

@@ -67,7 +67,10 @@
<option value="6" data-i18n>sensors.purposes.dhwFlowRate</option> <option value="6" data-i18n>sensors.purposes.dhwFlowRate</option>
<option value="7" data-i18n>sensors.purposes.exhaustTemp</option> <option value="7" data-i18n>sensors.purposes.exhaustTemp</option>
<option value="8" data-i18n>sensors.purposes.modLevel</option> <option value="8" data-i18n>sensors.purposes.modLevel</option>
<option value="9" data-i18n>sensors.purposes.currentPower</option> <option value="248" data-i18n>sensors.purposes.powerFactor</option>
<option value="249" data-i18n>sensors.purposes.power</option>
<option value="250" data-i18n>sensors.purposes.fanSpeed</option>
<option value="251" data-i18n>sensors.purposes.co2</option>
<option value="252" data-i18n>sensors.purposes.pressure</option> <option value="252" data-i18n>sensors.purposes.pressure</option>
<option value="253" data-i18n>sensors.purposes.humidity</option> <option value="253" data-i18n>sensors.purposes.humidity</option>
<option value="254" data-i18n>sensors.purposes.temperature</option> <option value="254" data-i18n>sensors.purposes.temperature</option>
@@ -90,6 +93,14 @@
<option value="9" data-i18n>sensors.types.otPressure</option> <option value="9" data-i18n>sensors.types.otPressure</option>
<option value="10" data-i18n>sensors.types.otModLevel</option> <option value="10" data-i18n>sensors.types.otModLevel</option>
<option value="11" data-i18n>sensors.types.otCurrentPower</option> <option value="11" data-i18n>sensors.types.otCurrentPower</option>
<option value="12" data-i18n>sensors.types.otExhaustCo2</option>
<option value="13" data-i18n>sensors.types.otExhaustFanSpeed</option>
<option value="14" data-i18n>sensors.types.otSupplyFanSpeed</option>
<option value="15" data-i18n>sensors.types.otSolarStorageTemp</option>
<option value="16" data-i18n>sensors.types.otSolarCollectorTemp</option>
<option value="17" data-i18n>sensors.types.otFanSpeedSetpoint</option>
<option value="18" data-i18n>sensors.types.otFanSpeedCurrent</option>
<option value="50" data-i18n>sensors.types.ntcTemp</option> <option value="50" data-i18n>sensors.types.ntcTemp</option>
<option value="51" data-i18n>sensors.types.dallasTemp</option> <option value="51" data-i18n>sensors.types.dallasTemp</option>
<option value="52" data-i18n>sensors.types.bluetooth</option> <option value="52" data-i18n>sensors.types.bluetooth</option>

View File

@@ -63,7 +63,7 @@ class Lang {
} }
async fetchTranslations(locale) { async fetchTranslations(locale) {
const response = await fetch(`/static/locales/${locale}.json`); const response = await fetch(`/static/locales/${locale}.json?{BUILD_TIME}`);
const data = await response.json(); const data = await response.json();
if (data.values instanceof Object) { if (data.values instanceof Object) {