This commit is contained in:
proddy
2025-03-22 10:32:03 +01:00
parent e418b7d8e7
commit eaa277fef0
281 changed files with 15297 additions and 21851 deletions

View File

@@ -0,0 +1,166 @@
#include "APSettingsService.h"
#include <emsesp.h>
APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE)
, _dnsServer(nullptr)
, _lastManaged(0)
, _reconfigureAp(false)
, _connected(0) {
addUpdateHandler([this] { reconfigureAP(); }, false);
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
}
void APSettingsService::begin() {
_fsPersistence.readFromFS();
// disabled for delayed start, first try station mode
// reconfigureAP();
}
// wait 10 sec on STA disconnect before starting AP
void APSettingsService::WiFiEvent(WiFiEvent_t event) {
uint8_t was_connected = _connected;
switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
_connected &= ~1;
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
_connected &= ~2;
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
_connected |= 1;
break;
case ARDUINO_EVENT_ETH_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP6:
_connected |= 2;
break;
default:
break;
}
// wait 10 sec before starting AP
if (was_connected && !_connected) {
_lastManaged = uuid::get_uptime();
}
}
void APSettingsService::reconfigureAP() {
_lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY;
_reconfigureAp = true;
}
void APSettingsService::loop() {
unsigned long currentMillis = uuid::get_uptime();
unsigned long manageElapsed = static_cast<uint32_t>(currentMillis - _lastManaged);
if (manageElapsed >= MANAGE_NETWORK_DELAY) {
_lastManaged = currentMillis;
manageAP();
}
handleDNS();
}
void APSettingsService::manageAP() {
WiFiMode_t currentWiFiMode = WiFi.getMode();
if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && !_connected)) {
if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
startAP();
}
} else if ((currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA) && _connected && (_reconfigureAp || !WiFi.softAPgetStationNum())) {
stopAP();
}
_reconfigureAp = false;
}
void APSettingsService::startAP() {
#if ESP_IDF_VERSION_MAJOR < 5
WiFi.softAPenableIpV6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
#else
WiFi.softAPenableIPv6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
#endif
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);
#if CONFIG_IDF_TARGET_ESP32C3
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
#endif
if (!_dnsServer) {
IPAddress apIp = WiFi.softAPIP();
emsesp::EMSESP::logger().info("Starting Access Point with captive portal on %s", apIp.toString().c_str());
_dnsServer = new DNSServer;
_dnsServer->start(DNS_PORT, "*", apIp);
}
}
void APSettingsService::stopAP() {
if (_dnsServer) {
emsesp::EMSESP::logger().info("Stopping Access Point");
_dnsServer->stop();
delete _dnsServer;
_dnsServer = nullptr;
}
WiFi.softAPdisconnect(true);
}
void APSettingsService::handleDNS() {
if (_dnsServer) {
_dnsServer->processNextRequest();
}
}
APNetworkStatus APSettingsService::getAPNetworkStatus() {
WiFiMode_t currentWiFiMode = WiFi.getMode();
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA;
if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
return APNetworkStatus::LINGERING;
}
return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE;
}
void APSettings::read(const APSettings & settings, JsonObject root) {
root["provision_mode"] = settings.provisionMode;
root["ssid"] = settings.ssid;
root["password"] = settings.password;
root["channel"] = settings.channel;
root["ssid_hidden"] = settings.ssidHidden;
root["max_clients"] = settings.maxClients;
root["local_ip"] = settings.localIP.toString();
root["gateway_ip"] = settings.gatewayIP.toString();
root["subnet_mask"] = settings.subnetMask.toString();
}
StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
APSettings newSettings = {};
newSettings.provisionMode = static_cast<uint8_t>(root["provision_mode"] | FACTORY_AP_PROVISION_MODE);
switch (settings.provisionMode) {
case AP_MODE_ALWAYS:
case AP_MODE_DISCONNECTED:
case AP_MODE_NEVER:
break;
default:
newSettings.provisionMode = AP_MODE_ALWAYS;
}
newSettings.ssid = root["ssid"] | FACTORY_AP_SSID;
newSettings.password = root["password"] | FACTORY_AP_PASSWORD;
newSettings.channel = static_cast<uint8_t>(root["channel"] | FACTORY_AP_CHANNEL);
newSettings.ssidHidden = root["ssid_hidden"] | FACTORY_AP_SSID_HIDDEN;
newSettings.maxClients = static_cast<uint8_t>(root["max_clients"] | FACTORY_AP_MAX_CLIENTS);
JsonUtils::readIP(root, "local_ip", newSettings.localIP, String(FACTORY_AP_LOCAL_IP));
JsonUtils::readIP(root, "gateway_ip", newSettings.gatewayIP, String(FACTORY_AP_GATEWAY_IP));
JsonUtils::readIP(root, "subnet_mask", newSettings.subnetMask, String(FACTORY_AP_SUBNET_MASK));
if (newSettings == settings) {
return StateUpdateResult::UNCHANGED;
}
settings = newSettings;
return StateUpdateResult::CHANGED;
}

View File

@@ -0,0 +1,110 @@
#ifndef APSettingsConfig_h
#define APSettingsConfig_h
#include "HttpEndpoint.h"
#include "FSPersistence.h"
#include "JsonUtils.h"
#include <DNSServer.h>
#include <IPAddress.h>
#ifndef FACTORY_AP_PROVISION_MODE
#define FACTORY_AP_PROVISION_MODE AP_MODE_DISCONNECTED
#endif
#ifndef FACTORY_AP_SSID
#define FACTORY_AP_SSID "ems-esp"
#endif
#ifndef FACTORY_AP_PASSWORD
#define FACTORY_AP_PASSWORD "ems-esp-neo"
#endif
#ifndef FACTORY_AP_LOCAL_IP
#define FACTORY_AP_LOCAL_IP "192.168.4.1"
#endif
#ifndef FACTORY_AP_GATEWAY_IP
#define FACTORY_AP_GATEWAY_IP "192.168.4.1"
#endif
#ifndef FACTORY_AP_SUBNET_MASK
#define FACTORY_AP_SUBNET_MASK "255.255.255.0"
#endif
#ifndef FACTORY_AP_CHANNEL
#define FACTORY_AP_CHANNEL 1
#endif
#ifndef FACTORY_AP_SSID_HIDDEN
#define FACTORY_AP_SSID_HIDDEN false
#endif
#ifndef FACTORY_AP_MAX_CLIENTS
#define FACTORY_AP_MAX_CLIENTS 4
#endif
#define AP_SETTINGS_FILE "/config/apSettings.json"
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
#define AP_MODE_ALWAYS 0
#define AP_MODE_DISCONNECTED 1
#define AP_MODE_NEVER 2
#define MANAGE_NETWORK_DELAY 10000
#define DNS_PORT 53
enum APNetworkStatus { ACTIVE = 0, INACTIVE, LINGERING };
class APSettings {
public:
uint8_t provisionMode;
String ssid;
String password;
uint8_t channel;
bool ssidHidden;
uint8_t maxClients;
IPAddress localIP;
IPAddress gatewayIP;
IPAddress subnetMask;
bool operator==(const APSettings & settings) const {
return provisionMode == settings.provisionMode && ssid == settings.ssid && password == settings.password && channel == settings.channel
&& ssidHidden == settings.ssidHidden && maxClients == settings.maxClients && localIP == settings.localIP && gatewayIP == settings.gatewayIP
&& subnetMask == settings.subnetMask;
}
static void read(const APSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, APSettings & settings);
};
class APSettingsService : public StatefulService<APSettings> {
public:
APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void loop();
APNetworkStatus getAPNetworkStatus();
private:
HttpEndpoint<APSettings> _httpEndpoint;
FSPersistence<APSettings> _fsPersistence;
// for the captive portal
DNSServer * _dnsServer;
// for the management delay loop
volatile unsigned long _lastManaged;
volatile boolean _reconfigureAp;
uint8_t _connected;
void reconfigureAP();
void manageAP();
void startAP();
void stopAP();
void handleDNS();
void WiFiEvent(WiFiEvent_t event);
};
#endif

View File

@@ -0,0 +1,21 @@
#include "APStatus.h"
APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService)
: _apSettingsService(apSettingsService) {
securityManager->addEndpoint(server, AP_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
apStatus(request);
});
}
void APStatus::apStatus(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot();
root["status"] = _apSettingsService->getAPNetworkStatus();
root["ip_address"] = WiFi.softAPIP().toString();
root["mac_address"] = WiFi.softAPmacAddress();
root["station_num"] = WiFi.softAPgetStationNum();
response->setLength();
request->send(response);
}

25
src/ESP32React/APStatus.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef APStatus_h
#define APStatus_h
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <IPAddress.h>
#include "SecurityManager.h"
#include "APSettingsService.h"
#define AP_STATUS_SERVICE_PATH "/rest/apStatus"
class APStatus {
public:
APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService);
private:
APSettingsService * _apSettingsService;
void apStatus(AsyncWebServerRequest * request);
};
#endif

View File

@@ -0,0 +1,137 @@
#include "ArduinoJsonJWT.h"
#include <array>
ArduinoJsonJWT::ArduinoJsonJWT(String secret)
: _secret(std::move(secret)) {
}
void ArduinoJsonJWT::setSecret(String secret) {
_secret = std::move(secret);
}
String ArduinoJsonJWT::getSecret() {
return _secret;
}
String ArduinoJsonJWT::buildJWT(JsonObject payload) {
// serialize, then encode payload
String jwt;
serializeJson(payload, jwt);
jwt = encode(jwt.c_str(), static_cast<int>(jwt.length()));
// add the header to payload
jwt = getJWTHeader() + '.' + jwt;
// add signature
jwt += '.' + sign(jwt);
return jwt;
}
void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
// clear json document before we begin, jsonDocument wil be null on failure
jsonDocument.clear();
const String & jwt_header = getJWTHeader();
const unsigned int jwt_header_size = jwt_header.length();
// must have the correct header and delimiter
if (!jwt.startsWith(jwt_header) || jwt.indexOf('.') != static_cast<int>(jwt_header_size)) {
return;
}
// check there is a signature delimieter
const int signatureDelimiterIndex = jwt.lastIndexOf('.');
if (signatureDelimiterIndex == static_cast<int>(jwt_header_size)) {
return;
}
// check the signature is valid
const String signature = jwt.substring(static_cast<unsigned int>(signatureDelimiterIndex) + 1);
jwt = jwt.substring(0, static_cast<unsigned int>(signatureDelimiterIndex));
if (sign(jwt) != signature) {
return;
}
// decode payload
jwt = jwt.substring(jwt_header_size + 1);
jwt = decode(jwt);
// parse payload, clearing json document after failure
const DeserializationError error = deserializeJson(jsonDocument, jwt);
if (error != DeserializationError::Ok || !jsonDocument.is<JsonObject>()) {
jsonDocument.clear();
}
}
/*
* ESP32 uses mbedtls, with decent HMAC implementations supporting sha256, as well as others.
* No need to pull in additional crypto libraries - lets use what we already have.
*/
String ArduinoJsonJWT::sign(String & payload) {
std::array<unsigned char, 32> hmacResult{};
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(_secret.c_str()), _secret.length());
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length());
mbedtls_md_hmac_finish(&ctx, hmacResult.data());
mbedtls_md_free(&ctx);
}
return encode(reinterpret_cast<const char *>(hmacResult.data()), hmacResult.size());
}
String ArduinoJsonJWT::encode(const char * cstr, int inputLen) {
// prepare encoder
base64_encodestate _state;
base64_init_encodestate(&_state);
// prepare buffer of correct length
const auto bufferLength = static_cast<std::size_t>(base64_encode_expected_len(inputLen)) + 1;
auto * buffer = new char[bufferLength];
// encode to buffer
int len = base64_encode_block(cstr, inputLen, &buffer[0], &_state);
len += base64_encode_blockend(&buffer[len], &_state);
buffer[len] = '\0';
// convert to arduino string, freeing buffer
auto result = String(buffer);
delete[] buffer;
buffer = nullptr;
// remove padding and convert to URL safe form
while (result.length() > 0 && result.charAt(result.length() - 1) == '=') {
result.remove(result.length() - 1);
}
result.replace('+', '-');
result.replace('/', '_');
// return as string
return result;
}
String ArduinoJsonJWT::decode(String value) {
// convert to standard base64
value.replace('-', '+');
value.replace('_', '/');
// prepare buffer of correct length
const auto bufferLength = static_cast<std::size_t>(base64_decode_expected_len(value.length()) + 1);
auto * buffer = new char[bufferLength];
// decode
const int len = base64_decode_chars(value.c_str(), static_cast<int>(value.length()), &buffer[0]);
buffer[len] = '\0';
// convert to arduino string, freeing buffer
auto result = String(buffer);
delete[] buffer;
buffer = nullptr;
// return as string
return result;
}

View File

@@ -0,0 +1,35 @@
#ifndef ArduinoJsonJWT_H
#define ArduinoJsonJWT_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include <libb64/cdecode.h>
#include <libb64/cencode.h>
#include <mbedtls/md.h>
class ArduinoJsonJWT {
public:
explicit ArduinoJsonJWT(String secret);
void setSecret(String secret);
String getSecret();
String buildJWT(JsonObject payload);
void parseJWT(String jwt, JsonDocument & jsonDocument);
private:
String _secret;
String sign(String & value);
static String encode(const char * cstr, int len);
static String decode(String value);
static const String & getJWTHeader() {
static const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
return JWT_HEADER;
}
};
#endif

View File

@@ -0,0 +1,37 @@
#include "AuthenticationService.h"
AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) {
// none of these need authentication
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { verifyAuthorization(request); });
auto * handler = new AsyncCallbackJsonWebHandler(SIGN_IN_PATH);
handler->onRequest([this](AsyncWebServerRequest * request, JsonVariant json) { signIn(request, json); });
server->addHandler(handler);
}
// Verifies that the request supplied a valid JWT.
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest * request) {
Authentication authentication = _securityManager->authenticateRequest(request);
request->send(authentication.authenticated ? 200 : 401);
}
// Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in subsequent requests.
void AuthenticationService::signIn(AsyncWebServerRequest * request, JsonVariant json) {
if (json.is<JsonObject>()) {
String username = json["username"];
String password = json["password"];
Authentication authentication = _securityManager->authenticate(username, password);
if (authentication.authenticated) {
User * user = authentication.user;
auto * response = new AsyncJsonResponse(false);
JsonObject jsonObject = response->getRoot();
jsonObject["access_token"] = _securityManager->generateJWT(user);
response->setLength();
request->send(response);
return;
}
}
AsyncWebServerResponse * response = request->beginResponse(401);
request->send(response);
}

View File

@@ -0,0 +1,22 @@
#ifndef AuthenticationService_H_
#define AuthenticationService_H_
#include "SecurityManager.h"
#include <ESPAsyncWebServer.h>
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
#define SIGN_IN_PATH "/rest/signIn"
class AuthenticationService {
public:
AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager);
private:
SecurityManager * _securityManager;
void signIn(AsyncWebServerRequest * request, JsonVariant json);
void verifyAuthorization(AsyncWebServerRequest * request);
};
#endif

View File

@@ -0,0 +1,86 @@
#include "ESP32React.h"
#include "WWWData.h" // include auto-generated static web resources
ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
: _securitySettingsService(server, fs)
, _networkSettingsService(server, fs, &_securitySettingsService)
, _wifiScanner(server, &_securitySettingsService)
, _networkStatus(server, &_securitySettingsService)
, _apSettingsService(server, fs, &_securitySettingsService)
, _apStatus(server, &_securitySettingsService, &_apSettingsService)
, _ntpSettingsService(server, fs, &_securitySettingsService)
, _ntpStatus(server, &_securitySettingsService)
, _uploadFileService(server, &_securitySettingsService)
, _mqttSettingsService(server, fs, &_securitySettingsService)
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
, _authenticationService(server, &_securitySettingsService) {
//
// Serve static web resources
//
// Populate the last modification date based on build datetime
static char last_modified[50];
sprintf(last_modified, "%s %s CET", __DATE__, __TIME__);
WWWData::registerRoutes([server](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
ArRequestHandlerFunction requestHandler = [contentType, content, len, hash](AsyncWebServerRequest * request) {
// Check if the client already has the same version and respond with a 304 (Not modified)
if (request->header("If-Modified-Since").indexOf(last_modified) > 0) {
return request->send(304);
} else if (request->header("If-None-Match").equals(hash)) {
return request->send(304);
}
AsyncWebServerResponse * response = request->beginResponse(200, contentType, content, len);
response->addHeader("Content-Encoding", "gzip");
// response->addHeader("Content-Encoding", "br"); // only works over HTTPS
// response->addHeader("Cache-Control", "public, immutable, max-age=31536000");
response->addHeader("Cache-Control", "must-revalidate"); // ensure that a client will check the server for a change
response->addHeader("Last-Modified", last_modified);
response->addHeader("ETag", hash);
request->send(response);
};
server->on(uri, HTTP_GET, requestHandler);
// Serving non matching get requests with "/index.html"
// OPTIONS get a straight up 200 response
if (strncmp(uri, "/index.html", 11) == 0) {
server->onNotFound([requestHandler](AsyncWebServerRequest * request) {
if (request->method() == HTTP_GET) {
requestHandler(request);
} else if (request->method() == HTTP_OPTIONS) {
request->send(200);
} else {
request->send(404);
}
});
}
});
}
void ESP32React::begin() {
_networkSettingsService.begin();
_networkSettingsService.read([&](NetworkSettings & networkSettings) {
DefaultHeaders & defaultHeaders = DefaultHeaders::Instance();
if (networkSettings.enableCORS) {
defaultHeaders.addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin);
defaultHeaders.addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
defaultHeaders.addHeader("Access-Control-Allow-Credentials", "true");
}
defaultHeaders.addHeader("Server", networkSettings.hostname);
});
_apSettingsService.begin();
_ntpSettingsService.begin();
_mqttSettingsService.begin();
_securitySettingsService.begin();
}
void ESP32React::loop() {
_networkSettingsService.loop();
_apSettingsService.loop();
_mqttSettingsService.loop();
}

View File

@@ -0,0 +1,86 @@
#ifndef ESP32React_h
#define ESP32React_h
#include "APSettingsService.h"
#include "APStatus.h"
#include "AuthenticationService.h"
#include "MqttSettingsService.h"
#include "MqttStatus.h"
#include "NTPSettingsService.h"
#include "NTPStatus.h"
#include "UploadFileService.h"
#include "SecuritySettingsService.h"
#include "WiFiScanner.h"
#include "NetworkSettingsService.h"
#include "NetworkStatus.h"
#include <Arduino.h>
#include <AsyncJson.h>
#include <AsyncMessagePack.h>
#include <AsyncTCP.h>
#include <WiFi.h>
class ESP32React {
public:
ESP32React(AsyncWebServer * server, FS * fs);
void begin();
void loop();
SecurityManager * getSecurityManager() {
return &_securitySettingsService;
}
StatefulService<SecuritySettings> * getSecuritySettingsService() {
return &_securitySettingsService;
}
StatefulService<NetworkSettings> * getNetworkSettingsService() {
return &_networkSettingsService;
}
StatefulService<APSettings> * getAPSettingsService() {
return &_apSettingsService;
}
StatefulService<NTPSettings> * getNTPSettingsService() {
return &_ntpSettingsService;
}
StatefulService<MqttSettings> * getMqttSettingsService() {
return &_mqttSettingsService;
}
MqttClient * getMqttClient() {
return _mqttSettingsService.getMqttClient();
}
//
// special functions needed outside scope
//
// true if AP is active
bool apStatus() {
return _apSettingsService.getAPNetworkStatus() == APNetworkStatus::ACTIVE;
}
uint16_t getWifiReconnects() {
return _networkSettingsService.getWifiReconnects();
}
private:
SecuritySettingsService _securitySettingsService;
NetworkSettingsService _networkSettingsService;
WiFiScanner _wifiScanner;
NetworkStatus _networkStatus;
APSettingsService _apSettingsService;
APStatus _apStatus;
NTPSettingsService _ntpSettingsService;
NTPStatus _ntpStatus;
UploadFileService _uploadFileService;
MqttSettingsService _mqttSettingsService;
MqttStatus _mqttStatus;
AuthenticationService _authenticationService;
};
#endif

View File

@@ -0,0 +1,119 @@
#ifndef FSPersistence_h
#define FSPersistence_h
#include "StatefulService.h"
#include "FS.h"
template <class T>
class FSPersistence {
public:
FSPersistence(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, FS * fs, const char * filePath)
: _stateReader(stateReader)
, _stateUpdater(stateUpdater)
, _statefulService(statefulService)
, _fs(fs)
, _filePath(filePath)
, _updateHandlerId(0) {
enableUpdateHandler();
}
void readFromFS() {
File settingsFile = _fs->open(_filePath, "r");
if (settingsFile) {
JsonDocument jsonDocument;
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
#if defined(EMSESP_DEBUG)
// Serial.println();
// Serial.printf("Reading settings from %s ", _filePath);
// serializeJson(jsonDocument, Serial);
// Serial.println();
#endif
settingsFile.close();
return;
}
settingsFile.close();
}
// If we reach here we have not been successful in loading the config,
// hard-coded emergency defaults are now applied.
#if defined(EMSESP_DEBUG)
// Serial.println();
// Serial.printf("Applying defaults to %s", _filePath);
// Serial.println();
#endif
applyDefaults();
writeToFS(); // added to make sure the initial file is created
}
bool writeToFS() {
// create and populate a new json object
JsonDocument jsonDocument;
JsonObject jsonObject = jsonDocument.to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
// make directories if required, for new IDF4.2 & LittleFS
String path(_filePath);
int index = 0;
while ((index = path.indexOf('/', static_cast<unsigned int>(index) + 1)) != -1) {
String segment = path.substring(0, static_cast<unsigned int>(index));
if (!_fs->exists(segment)) {
_fs->mkdir(segment);
}
}
// serialize it to filesystem
File settingsFile = _fs->open(_filePath, "w");
// failed to open file, return false
if (!settingsFile || !jsonObject.size()) {
return false;
}
// serialize the data to the file
#if defined(EMSESP_DEBUG)
// Serial.println();
// Serial.printf("Writing settings to %s ", _filePath);
// serializeJson(jsonDocument, Serial);
// Serial.println();
#endif
serializeJson(jsonDocument, settingsFile);
settingsFile.close();
return true;
}
void disableUpdateHandler() {
if (_updateHandlerId) {
_statefulService->removeUpdateHandler(_updateHandlerId);
_updateHandlerId = 0;
}
}
void enableUpdateHandler() {
if (!_updateHandlerId) {
_updateHandlerId = _statefulService->addUpdateHandler([&] { writeToFS(); });
}
}
private:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService;
FS * _fs;
const char * _filePath;
update_handler_id_t _updateHandlerId;
protected:
// We assume the updater supplies sensible defaults if an empty object
// is supplied, this virtual function allows that to be changed.
virtual void applyDefaults() {
JsonDocument jsonDocument;
JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
}
};
#endif

View File

@@ -0,0 +1,68 @@
#ifndef HttpEndpoint_h
#define HttpEndpoint_h
#include <functional>
#include <ESPAsyncWebServer.h>
#include "SecurityManager.h"
#include "StatefulService.h"
#define HTTP_ENDPOINT_ORIGIN_ID "http"
template <class T>
class HttpEndpoint {
protected:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService;
public:
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN)
: _stateReader(stateReader)
, _stateUpdater(stateUpdater)
, _statefulService(statefulService) {
// Create handler for both GET and POST endpoints
securityManager->addEndpoint(
server, servicePath, authenticationPredicate, [this](AsyncWebServerRequest * request, JsonVariant json) { handleRequest(request, json); }, HTTP_ANY); // ALL methods
}
protected:
// for POST
void handleRequest(AsyncWebServerRequest * request, JsonVariant json) {
if (request->method() == HTTP_POST) {
// Handle POST
if (!json.is<JsonObject>()) {
request->send(400); // error, bad request
return;
}
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(json.as<JsonObject>(), _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
request->send(400); // error, bad request
return;
} else if (outcome == StateUpdateResult::CHANGED || outcome == StateUpdateResult::CHANGED_RESTART) {
// persist changes
request->onDisconnect([this] { _statefulService->callUpdateHandlers(); });
if (outcome == StateUpdateResult::CHANGED_RESTART) {
request->send(205); // reboot required
return;
}
}
}
auto * response = new AsyncJsonResponse(false);
JsonObject jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
response->setLength();
request->send(response);
}
};
#endif

22
src/ESP32React/IPUtils.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef IPUtils_h
#define IPUtils_h
#include <IPAddress.h>
class IPUtils {
public:
static bool isSet(const IPAddress & ip) {
return ip != getNotSetIP();
}
static bool isNotSet(const IPAddress & ip) {
return ip == getNotSetIP();
}
private:
static const IPAddress & getNotSetIP() {
static const IPAddress IP_NOT_SET = IPAddress(INADDR_NONE);
return IP_NOT_SET;
}
};
#endif

View File

@@ -0,0 +1,31 @@
#ifndef JsonUtils_h
#define JsonUtils_h
#include <Arduino.h>
#include <ArduinoJson.h>
#include <IPAddress.h>
#include "IPUtils.h"
class JsonUtils {
public:
static void readIP(JsonObject root, const String & key, IPAddress & ip, const String & def) {
IPAddress defaultIp = {};
if (!defaultIp.fromString(def)) {
defaultIp = INADDR_NONE;
}
readIP(root, key, ip, defaultIp);
}
static void readIP(JsonObject root, const String & key, IPAddress & ip, const IPAddress & defaultIp = INADDR_NONE) {
if (!root[key].is<String>() || !ip.fromString(root[key].as<String>())) {
ip = defaultIp;
}
}
static void writeIP(JsonObject root, const String & key, const IPAddress & ip) {
if (IPUtils::isSet(ip)) {
root[key] = ip.toString();
}
}
};
#endif

View File

@@ -0,0 +1,404 @@
#include "MqttSettingsService.h"
#include <emsesp.h>
MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE)
, _reconfigureMqtt(false)
, _disconnectedAt(0)
, _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED)
, _mqttClient(nullptr) {
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
addUpdateHandler([this] { onConfigUpdated(); }, false);
}
static String generateClientId() {
#ifdef EMSESP_STANDALONE
return "ems-esp";
#else
return "esp32-" + String(static_cast<uint32_t>(ESP.getEfuseMac()), HEX);
#endif
}
MqttSettingsService::~MqttSettingsService() {
delete _mqttClient;
}
void MqttSettingsService::begin() {
_fsPersistence.readFromFS();
startClient();
}
void MqttSettingsService::startClient() {
static bool isSecure = false;
if (_mqttClient != nullptr) {
// do we need to change the client?
if ((isSecure && _state.enableTLS) || (!isSecure && _state.enableTLS)) {
return;
}
delete _mqttClient;
_mqttClient = nullptr;
}
#ifndef TASMOTA_SDK
if (_state.enableTLS) {
isSecure = true;
if (emsesp::EMSESP::system_.PSram() > 0) {
_mqttClient = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::YES);
} else {
_mqttClient = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
}
if (_state.rootCA == "insecure") {
static_cast<espMqttClientSecure *>(_mqttClient)->setInsecure();
} else {
String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n";
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(certificate.c_str());
}
static_cast<espMqttClientSecure *>(_mqttClient)->onConnect([this](bool sessionPresent) { onMqttConnect(sessionPresent); });
static_cast<espMqttClientSecure *>(_mqttClient)->onDisconnect([this](espMqttClientTypes::DisconnectReason reason) { onMqttDisconnect(reason); });
static_cast<espMqttClientSecure *>(_mqttClient)
->onMessage(
[this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) {
onMqttMessage(properties, topic, payload, len, index, total);
});
return;
}
#endif
isSecure = false;
if (emsesp::EMSESP::system_.PSram() > 0) {
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::YES);
} else {
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
}
static_cast<espMqttClient *>(_mqttClient)->onConnect([this](bool sessionPresent) { onMqttConnect(sessionPresent); });
static_cast<espMqttClient *>(_mqttClient)->onDisconnect([this](espMqttClientTypes::DisconnectReason reason) { onMqttDisconnect(reason); });
static_cast<espMqttClient *>(_mqttClient)
->onMessage(
[this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) {
onMqttMessage(properties, topic, payload, len, index, total);
});
}
void MqttSettingsService::loop() {
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
// reconfigure MQTT client
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
_reconfigureMqtt = false;
}
if (emsesp::EMSESP::system_.PSram() == 0) {
_mqttClient->loop();
}
}
bool MqttSettingsService::isEnabled() {
return _state.enabled;
}
bool MqttSettingsService::isConnected() {
return _mqttClient->connected();
}
const char * MqttSettingsService::getClientId() {
return _mqttClient->getClientId();
}
void MqttSettingsService::onMqttMessage(const espMqttClientTypes::MessageProperties & properties,
const char * topic,
const uint8_t * payload,
size_t len,
size_t index,
size_t total) {
(void)properties;
(void)index;
(void)total;
emsesp::EMSESP::mqtt_.on_message(topic, payload, len);
}
espMqttClientTypes::DisconnectReason MqttSettingsService::getDisconnectReason() {
return _disconnectReason;
}
MqttClient * MqttSettingsService::getMqttClient() {
return _mqttClient;
}
void MqttSettingsService::onMqttConnect(bool sessionPresent) {
(void)sessionPresent;
emsesp::EMSESP::mqtt_.on_connect();
}
void MqttSettingsService::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) {
_disconnectReason = reason;
if (!_disconnectedAt) {
_disconnectedAt = uuid::get_uptime();
}
emsesp::EMSESP::mqtt_.on_disconnect(reason);
}
void MqttSettingsService::onConfigUpdated() {
_reconfigureMqtt = true;
_disconnectedAt = 0;
startClient();
emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
}
void MqttSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP6:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
if (_state.enabled && !_mqttClient->connected()) {
onConfigUpdated();
}
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_ETH_DISCONNECTED:
if (_state.enabled) {
_mqttClient->disconnect(true);
}
break;
default:
break;
}
}
bool MqttSettingsService::configureMqtt() {
// disconnect if already connected
if (_mqttClient->connected()) {
emsesp::EMSESP::logger().info("Disconnecting to configure");
_mqttClient->disconnect(true);
}
// only connect if WiFi is connected and MQTT is enabled
if (_state.enabled && emsesp::EMSESP::system_.network_connected() && !_state.host.isEmpty()) {
// create last will topic with the base prefixed. It has to be static because the client destroys the reference
static char will_topic[FACTORY_MQTT_MAX_TOPIC_LENGTH];
if (_state.base.isEmpty()) {
snprintf(will_topic, sizeof(will_topic), "status");
} else {
snprintf(will_topic, sizeof(will_topic), "%s/status", _state.base.c_str());
}
_reconfigureMqtt = false;
#ifndef TASMOTA_SDK
if (_state.enableTLS) {
#if defined(EMSESP_DEBUG)
emsesp::EMSESP::logger().debug("Start secure MQTT with rootCA");
#endif
static_cast<espMqttClientSecure *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
if (_state.username.length() > 0) {
static_cast<espMqttClientSecure *>(_mqttClient)
->setCredentials(_state.username.c_str(), _state.password.length() > 0 ? _state.password.c_str() : nullptr);
}
static_cast<espMqttClientSecure *>(_mqttClient)->setClientId(_state.clientId.c_str());
static_cast<espMqttClientSecure *>(_mqttClient)->setKeepAlive(_state.keepAlive);
static_cast<espMqttClientSecure *>(_mqttClient)->setCleanSession(_state.cleanSession);
static_cast<espMqttClientSecure *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain
return _mqttClient->connect();
}
#endif
static_cast<espMqttClient *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
if (_state.username.length() > 0) {
static_cast<espMqttClient *>(_mqttClient)->setCredentials(_state.username.c_str(), _state.password.length() > 0 ? _state.password.c_str() : nullptr);
}
static_cast<espMqttClient *>(_mqttClient)->setClientId(_state.clientId.c_str());
static_cast<espMqttClient *>(_mqttClient)->setKeepAlive(_state.keepAlive);
static_cast<espMqttClient *>(_mqttClient)->setCleanSession(_state.cleanSession);
static_cast<espMqttClient *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain
return _mqttClient->connect();
}
return false;
}
void MqttSettings::read(MqttSettings & settings, JsonObject root) {
#ifndef TASMOTA_SDK
root["enableTLS"] = settings.enableTLS;
root["rootCA"] = settings.rootCA;
#endif
root["enabled"] = settings.enabled;
root["host"] = settings.host;
root["port"] = settings.port;
root["base"] = settings.base;
root["username"] = settings.username;
root["password"] = settings.password;
root["client_id"] = settings.clientId;
root["keep_alive"] = settings.keepAlive;
root["clean_session"] = settings.cleanSession;
root["entity_format"] = settings.entity_format;
root["publish_time_boiler"] = settings.publish_time_boiler;
root["publish_time_thermostat"] = settings.publish_time_thermostat;
root["publish_time_solar"] = settings.publish_time_solar;
root["publish_time_mixer"] = settings.publish_time_mixer;
root["publish_time_water"] = settings.publish_time_water;
root["publish_time_other"] = settings.publish_time_other;
root["publish_time_sensor"] = settings.publish_time_sensor;
root["publish_time_heartbeat"] = settings.publish_time_heartbeat;
root["mqtt_qos"] = settings.mqtt_qos;
root["mqtt_retain"] = settings.mqtt_retain;
root["ha_enabled"] = settings.ha_enabled;
root["nested_format"] = settings.nested_format;
root["discovery_prefix"] = settings.discovery_prefix;
root["discovery_type"] = settings.discovery_type;
root["publish_single"] = settings.publish_single;
root["publish_single2cmd"] = settings.publish_single2cmd;
root["send_response"] = settings.send_response;
}
StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings) {
MqttSettings newSettings = {};
bool changed = false;
#ifndef TASMOTA_SDK
newSettings.enableTLS = root["enableTLS"];
newSettings.rootCA = root["rootCA"] | "";
#else
newSettings.enableTLS = false;
#endif
newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
newSettings.host = root["host"] | FACTORY_MQTT_HOST;
newSettings.port = static_cast<uint16_t>(root["port"] | FACTORY_MQTT_PORT);
newSettings.base = root["base"] | FACTORY_MQTT_BASE;
newSettings.username = root["username"] | FACTORY_MQTT_USERNAME;
newSettings.password = root["password"] | FACTORY_MQTT_PASSWORD;
newSettings.clientId = root["client_id"] | generateClientId();
newSettings.keepAlive = static_cast<uint16_t>(root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE);
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
newSettings.mqtt_qos = static_cast<uint8_t>(root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS);
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
newSettings.publish_time_boiler = static_cast<uint16_t>(root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_thermostat = static_cast<uint16_t>(root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_solar = static_cast<uint16_t>(root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_mixer = static_cast<uint16_t>(root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_water = static_cast<uint16_t>(root["publish_time_water"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_other = static_cast<uint16_t>(root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME_OTHER);
newSettings.publish_time_sensor = static_cast<uint16_t>(root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_heartbeat = static_cast<uint16_t>(root["publish_time_heartbeat"] | EMSESP_DEFAULT_PUBLISH_HEARTBEAT);
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
newSettings.nested_format = static_cast<uint8_t>(root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT);
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
newSettings.discovery_type = static_cast<uint8_t>(root["discovery_type"] | EMSESP_DEFAULT_DISCOVERY_TYPE);
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
newSettings.entity_format = static_cast<uint8_t>(root["entity_format"] | EMSESP_DEFAULT_ENTITY_FORMAT);
if (newSettings.enabled != settings.enabled) {
changed = true;
}
if (newSettings.mqtt_qos != settings.mqtt_qos) {
emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
changed = true;
}
if (newSettings.nested_format != settings.nested_format) {
changed = true;
}
if (newSettings.discovery_prefix != settings.discovery_prefix) {
changed = true;
}
if (newSettings.discovery_type != settings.discovery_type) {
changed = true;
}
if (newSettings.entity_format != settings.entity_format) {
changed = true;
}
// if both settings are stored from older version, HA has priority
if (newSettings.ha_enabled && newSettings.publish_single) {
newSettings.publish_single = false;
}
if (newSettings.publish_single != settings.publish_single) {
if (newSettings.publish_single) {
newSettings.ha_enabled = false;
}
changed = true;
}
if (newSettings.publish_single2cmd != settings.publish_single2cmd) {
changed = true;
}
if (newSettings.send_response != settings.send_response) {
changed = true;
}
if (newSettings.ha_enabled != settings.ha_enabled) {
emsesp::EMSESP::mqtt_.ha_enabled(newSettings.ha_enabled);
if (newSettings.ha_enabled) {
newSettings.publish_single = false;
}
changed = true;
}
if (newSettings.mqtt_retain != settings.mqtt_retain) {
emsesp::EMSESP::mqtt_.set_retain(newSettings.mqtt_retain);
changed = true;
}
if (newSettings.publish_time_boiler != settings.publish_time_boiler) {
emsesp::EMSESP::mqtt_.set_publish_time_boiler(newSettings.publish_time_boiler);
}
if (newSettings.publish_time_thermostat != settings.publish_time_thermostat) {
emsesp::EMSESP::mqtt_.set_publish_time_thermostat(newSettings.publish_time_thermostat);
}
if (newSettings.publish_time_solar != settings.publish_time_solar) {
emsesp::EMSESP::mqtt_.set_publish_time_solar(newSettings.publish_time_solar);
}
if (newSettings.publish_time_mixer != settings.publish_time_mixer) {
emsesp::EMSESP::mqtt_.set_publish_time_mixer(newSettings.publish_time_mixer);
}
if (newSettings.publish_time_water != settings.publish_time_water) {
emsesp::EMSESP::mqtt_.set_publish_time_water(newSettings.publish_time_water);
}
if (newSettings.publish_time_other != settings.publish_time_other) {
emsesp::EMSESP::mqtt_.set_publish_time_other(newSettings.publish_time_other);
}
if (newSettings.publish_time_sensor != settings.publish_time_sensor) {
emsesp::EMSESP::mqtt_.set_publish_time_sensor(newSettings.publish_time_sensor);
}
if (newSettings.publish_time_heartbeat != settings.publish_time_heartbeat) {
emsesp::EMSESP::mqtt_.set_publish_time_heartbeat(newSettings.publish_time_heartbeat);
}
#ifndef TASMOTA_SDK
// strip down to certificate only
newSettings.rootCA.replace("\r", "");
newSettings.rootCA.replace("\n", "");
newSettings.rootCA.replace("-----BEGIN CERTIFICATE-----", "");
newSettings.rootCA.replace("-----END CERTIFICATE-----", "");
newSettings.rootCA.replace(" ", "");
if (newSettings.rootCA.length() == 0 && newSettings.enableTLS) {
newSettings.rootCA = "insecure";
}
if (newSettings.enableTLS != settings.enableTLS || newSettings.rootCA != settings.rootCA) {
changed = true;
}
#endif
// save the new settings
settings = newSettings;
if (changed) {
emsesp::EMSESP::mqtt_.reset_mqtt();
}
return StateUpdateResult::CHANGED;
}

View File

@@ -0,0 +1,131 @@
#ifndef MqttSettingsService_h
#define MqttSettingsService_h
#include "StatefulService.h"
#include "HttpEndpoint.h"
#include "FSPersistence.h"
#include <espMqttClient.h>
#include <uuid/common.h>
#define MQTT_RECONNECTION_DELAY 2000 // 2 seconds
#define MQTT_SETTINGS_FILE "/config/mqttSettings.json"
#define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings"
#ifndef FACTORY_MQTT_ENABLED
#define FACTORY_MQTT_ENABLED false
#endif
#ifndef FACTORY_MQTT_HOST
#define FACTORY_MQTT_HOST "" // is blank
#endif
#ifndef FACTORY_MQTT_PORT
#define FACTORY_MQTT_PORT 1883
#endif
#ifndef FACTORY_MQTT_BASE
#define FACTORY_MQTT_BASE "ems-esp"
#endif
#ifndef FACTORY_MQTT_USERNAME
#define FACTORY_MQTT_USERNAME ""
#endif
#ifndef FACTORY_MQTT_PASSWORD
#define FACTORY_MQTT_PASSWORD ""
#endif
#ifndef FACTORY_MQTT_KEEP_ALIVE
#define FACTORY_MQTT_KEEP_ALIVE 16
#endif
#ifndef FACTORY_MQTT_CLEAN_SESSION
#define FACTORY_MQTT_CLEAN_SESSION false
#endif
#ifndef FACTORY_MQTT_MAX_TOPIC_LENGTH
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
#endif
class MqttSettings {
public:
bool enabled;
String host;
uint16_t port;
String rootCA;
bool enableTLS;
String username;
String password;
String clientId;
uint16_t keepAlive;
bool cleanSession;
// EMS-ESP specific
String base;
uint16_t publish_time_boiler;
uint16_t publish_time_thermostat;
uint16_t publish_time_solar;
uint16_t publish_time_mixer;
uint16_t publish_time_water;
uint16_t publish_time_other;
uint16_t publish_time_sensor;
uint16_t publish_time_heartbeat;
uint8_t mqtt_qos;
bool mqtt_retain;
bool ha_enabled;
uint8_t nested_format;
String discovery_prefix;
uint8_t discovery_type;
bool publish_single;
bool publish_single2cmd;
bool send_response;
uint8_t entity_format;
static void read(MqttSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, MqttSettings & settings);
};
class MqttSettingsService : public StatefulService<MqttSettings> {
public:
MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
~MqttSettingsService();
void begin();
void startClient();
void loop();
bool isEnabled();
bool isConnected();
const char * getClientId();
espMqttClientTypes::DisconnectReason getDisconnectReason();
MqttClient * getMqttClient();
protected:
void onConfigUpdated();
private:
HttpEndpoint<MqttSettings> _httpEndpoint;
FSPersistence<MqttSettings> _fsPersistence;
// variable to help manage connection
bool _reconfigureMqtt;
unsigned long _disconnectedAt;
// connection status
espMqttClientTypes::DisconnectReason _disconnectReason;
// the MQTT client instance
MqttClient * _mqttClient;
void WiFiEvent(WiFiEvent_t event);
void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason);
void
onMqttMessage(const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total);
bool configureMqtt();
};
#endif

View File

@@ -0,0 +1,27 @@
#include "MqttStatus.h"
#include <emsesp.h>
MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager)
: _mqttSettingsService(mqttSettingsService) {
securityManager->addEndpoint(server, MQTT_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
mqttStatus(request);
});
}
void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot();
root["enabled"] = _mqttSettingsService->isEnabled();
root["connected"] = _mqttSettingsService->isConnected();
root["client_id"] = _mqttSettingsService->getClientId();
root["disconnect_reason"] = (uint8_t)_mqttSettingsService->getDisconnectReason();
root["mqtt_queued"] = emsesp::Mqtt::publish_queued();
root["mqtt_fails"] = emsesp::Mqtt::publish_fails();
root["connect_count"] = emsesp::Mqtt::connect_count();
response->setLength();
request->send(response);
}

View File

@@ -0,0 +1,23 @@
#ifndef MqttStatus_h
#define MqttStatus_h
#include <WiFi.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include "MqttSettingsService.h"
#include "SecurityManager.h"
#define MQTT_STATUS_SERVICE_PATH "/rest/mqttStatus"
class MqttStatus {
public:
MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager);
private:
MqttSettingsService * _mqttSettingsService;
void mqttStatus(AsyncWebServerRequest * request);
};
#endif

View File

@@ -0,0 +1,101 @@
#include "NTPSettingsService.h"
#include <emsesp.h>
NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE)
, _connected(false) {
// POST
securityManager->addEndpoint(server, TIME_PATH, AuthenticationPredicates::IS_ADMIN, [this](AsyncWebServerRequest * request, JsonVariant json) {
configureTime(request, json);
});
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
addUpdateHandler([this] { configureNTP(); }, false);
}
void NTPSettingsService::begin() {
_fsPersistence.readFromFS();
configureNTP();
}
// handles both WiFI and Ethernet
void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_ETH_DISCONNECTED:
if (_connected) {
emsesp::EMSESP::logger().info("WiFi connection dropped, stopping NTP");
_connected = false;
configureNTP();
}
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP:
// emsesp::EMSESP::logger().info("Got IP address, starting NTP synchronization");
_connected = true;
configureNTP();
break;
default:
break;
}
}
// https://werner.rothschopf.net/microcontroller/202103_arduino_esp32_ntp_en.htm
void NTPSettingsService::configureNTP() {
emsesp::EMSESP::system_.ntp_connected(false);
if (_connected && _state.enabled) {
emsesp::EMSESP::logger().info("Starting NTP service");
esp_sntp_set_sync_interval(3600000); // one hour
esp_sntp_set_time_sync_notification_cb(ntp_received);
configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
} else {
setenv("TZ", _state.tzFormat.c_str(), 1);
tzset();
esp_sntp_stop();
}
}
void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVariant json) {
if (json.is<JsonObject>()) {
struct tm tm = {};
String timeLocal = json["local_time"];
char * s = strptime(timeLocal.c_str(), "%Y-%m-%dT%H:%M:%S", &tm);
if (s != nullptr) {
tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving
time_t time = mktime(&tm);
struct timeval now = {.tv_sec = time, .tv_usec = {}};
settimeofday(&now, nullptr);
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
return;
}
}
AsyncWebServerResponse * response = request->beginResponse(400);
request->send(response);
}
void NTPSettingsService::ntp_received(struct timeval * tv) {
(void)tv;
// emsesp::EMSESP::logger().info("NTP sync to %d sec", tv->tv_sec);
emsesp::EMSESP::system_.ntp_connected(true);
}
void NTPSettings::read(NTPSettings & settings, JsonObject root) {
root["enabled"] = settings.enabled;
root["server"] = settings.server;
root["tz_label"] = settings.tzLabel;
root["tz_format"] = settings.tzFormat;
}
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
settings.server = root["server"] | FACTORY_NTP_SERVER;
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
return StateUpdateResult::CHANGED;
}

View File

@@ -0,0 +1,59 @@
#ifndef NTPSettingsService_h
#define NTPSettingsService_h
#include "HttpEndpoint.h"
#include "FSPersistence.h"
#include <ctime>
#include <esp_sntp.h>
#ifndef FACTORY_NTP_ENABLED
#define FACTORY_NTP_ENABLED true
#endif
#ifndef FACTORY_NTP_TIME_ZONE_LABEL
#define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London"
#endif
#ifndef FACTORY_NTP_TIME_ZONE_FORMAT
#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0"
#endif
#ifndef FACTORY_NTP_SERVER
#define FACTORY_NTP_SERVER "time.google.com"
#endif
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
#define TIME_PATH "/rest/time"
class NTPSettings {
public:
bool enabled;
String tzLabel;
String tzFormat;
String server;
static void read(NTPSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, NTPSettings & settings);
};
class NTPSettingsService : public StatefulService<NTPSettings> {
public:
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
static void ntp_received(struct timeval * tv);
private:
HttpEndpoint<NTPSettings> _httpEndpoint;
FSPersistence<NTPSettings> _fsPersistence;
bool _connected;
void WiFiEvent(WiFiEvent_t event);
void configureNTP();
void configureTime(AsyncWebServerRequest * request, JsonVariant json);
};
#endif

View File

@@ -0,0 +1,62 @@
#include "NTPStatus.h"
#include <emsesp.h>
#include <array>
NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) {
securityManager->addEndpoint(server, NTP_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
ntpStatus(request);
});
}
/*
* Formats the time using the format provided.
*
* Uses a 25 byte buffer, large enough to fit an ISO time string with offset.
*/
String formatTime(tm * time, const char * format) {
std::array<char, 25> time_string{};
strftime(time_string.data(), time_string.size(), format, time);
return {time_string.data()};
}
String toUTCTimeString(tm * time) {
return formatTime(time, "%FT%TZ");
}
String toLocalTimeString(tm * time) {
return formatTime(time, "%FT%T");
}
void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot();
// grab the current instant in unix seconds
time_t now = time(nullptr);
// only provide enabled/disabled status for now
root["status"] = [] {
if (esp_sntp_enabled()) {
if (emsesp::EMSESP::system_.ntp_connected()) {
return 2;
} else {
return 1;
}
}
return 0;
}();
// the current time in UTC
root["utc_time"] = toUTCTimeString(gmtime(&now));
// local time with offset
root["local_time"] = toLocalTimeString(localtime(&now));
// the sntp server name
root["server"] = esp_sntp_getservername(0);
response->setLength();
request->send(response);
}

View File

@@ -0,0 +1,24 @@
#ifndef NTPStatus_h
#define NTPStatus_h
#include <ctime>
#include <WiFi.h>
#include <esp_sntp.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include "SecurityManager.h"
#include <uuid/common.h>
#define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus"
class NTPStatus {
public:
NTPStatus(AsyncWebServer * server, SecurityManager * securityManager);
private:
void ntpStatus(AsyncWebServerRequest * request);
};
#endif

View File

@@ -0,0 +1,466 @@
#include "NetworkSettingsService.h"
#include <emsesp.h>
NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE)
, _lastConnectionAttempt(0)
, _stopping(false) {
addUpdateHandler([this] { reconfigureWiFiConnection(); }, false);
// Eth is also bound to the WifiGeneric event handler
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event, info); });
}
static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) {
uint tmp[6];
if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) {
return false;
}
for (uint8_t i = 0; i < 6; i++) {
mac[i] = static_cast<uint8_t>(tmp[i]);
}
return true;
}
void NetworkSettingsService::begin() {
// TODO: may need to change this for Arduino Core 3.1 / IDF 5.x
// We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default.
// If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future.
if (WiFi.getMode() != WIFI_OFF) {
WiFi.mode(WIFI_OFF);
}
// Disable WiFi config persistance and auto reconnect
WiFi.persistent(false);
WiFi.setAutoReconnect(false);
WiFi.mode(WIFI_MODE_MAX);
WiFi.mode(WIFI_MODE_NULL);
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN, connect issues in 2.0.14
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set
_fsPersistence.readFromFS();
}
void NetworkSettingsService::reconfigureWiFiConnection() {
// do not disconnect for switching to eth, restart is needed
if (WiFi.isConnected() && _state.ssid.length() == 0) {
return;
}
// disconnect and de-configure wifi
if (WiFi.disconnect(true)) {
_stopping = true;
}
}
void NetworkSettingsService::loop() {
unsigned long currentMillis = millis();
if (!_lastConnectionAttempt || static_cast<uint32_t>(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
_lastConnectionAttempt = currentMillis;
manageSTA();
}
}
void NetworkSettingsService::manageSTA() {
// Abort if already connected, or if we have no SSID
if (WiFi.isConnected() || _state.ssid.length() == 0) {
#if ESP_IDF_VERSION_MAJOR >= 5
if (_state.ssid.length() == 0) {
ETH.enableIPv6(true);
}
#endif
return;
}
// Connect or reconnect as required
if ((WiFi.getMode() & WIFI_STA) == 0) {
#if ESP_IDF_VERSION_MAJOR >= 5
WiFi.enableIPv6(true);
#endif
if (_state.staticIPConfig) {
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); // configure for static IP
}
WiFi.setHostname(_state.hostname.c_str()); // set hostname
// www.esp32.com/viewtopic.php?t=12055
if (_state.bandwidth20) {
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT20);
} else {
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
}
if (_state.nosleep) {
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
}
// attempt to connect to the network
uint8_t bssid[6];
if (formatBssid(_state.bssid, bssid)) {
WiFi.begin(_state.ssid.c_str(), _state.password.c_str(), 0, bssid);
} else {
WiFi.begin(_state.ssid.c_str(), _state.password.c_str());
}
#ifdef BOARD_C3_MINI_V1
// always hardcode Tx power for Wemos CS Mini v1
// v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979
// https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
WiFi.setTxPower(WIFI_POWER_8_5dBm);
#else
if (_state.tx_power != 0) {
// if not set to Auto (0) set the Tx power now
if (!WiFi.setTxPower(static_cast<wifi_power_t>(_state.tx_power))) {
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
}
}
#endif
} else { // not connected but STA-mode active => disconnect
reconfigureWiFiConnection();
}
}
// set the TxPower based on the RSSI (signal strength), picking the lowest value
// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function
void NetworkSettingsService::setWiFiPowerOnRSSI() {
// Range ESP32 : 2dBm - 20dBm
// 802.11b - wifi1
// 802.11a - wifi2
// 802.11g - wifi3
// 802.11n - wifi4
// 802.11ac - wifi5
// 802.11ax - wifi6
int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4
int threshold = WIFI_SENSITIVITY_n + 120; // Margin in dBm * 10 on top of threshold
// Assume AP sends with max set by ETSI standard.
// 2.4 GHz: 100 mWatt (20 dBm)
// US and some other countries allow 1000 mW (30 dBm)
int rssi = WiFi.RSSI() * 10;
int newrssi = rssi - 200; // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time.
int min_tx_pwr = 0;
if (newrssi < threshold) {
min_tx_pwr = threshold - newrssi;
}
if (min_tx_pwr > max_tx_pwr) {
min_tx_pwr = max_tx_pwr;
}
// from WiFIGeneric.h use:
// WIFI_POWER_19_5dBm = 78,// 19.5dBm
// WIFI_POWER_19dBm = 76,// 19dBm
// WIFI_POWER_18_5dBm = 74,// 18.5dBm
// WIFI_POWER_17dBm = 68,// 17dBm
// WIFI_POWER_15dBm = 60,// 15dBm
// WIFI_POWER_13dBm = 52,// 13dBm
// WIFI_POWER_11dBm = 44,// 11dBm
// WIFI_POWER_8_5dBm = 34,// 8.5dBm
// WIFI_POWER_7dBm = 28,// 7dBm
// WIFI_POWER_5dBm = 20,// 5dBm
// WIFI_POWER_2dBm = 8,// 2dBm
// WIFI_POWER_MINUS_1dBm = -4// -1dBm
wifi_power_t p = WIFI_POWER_2dBm;
if (min_tx_pwr > 185)
p = WIFI_POWER_19_5dBm;
else if (min_tx_pwr > 170)
p = WIFI_POWER_18_5dBm;
else if (min_tx_pwr > 150)
p = WIFI_POWER_17dBm;
else if (min_tx_pwr > 130)
p = WIFI_POWER_15dBm;
else if (min_tx_pwr > 110)
p = WIFI_POWER_13dBm;
else if (min_tx_pwr > 85)
p = WIFI_POWER_11dBm;
else if (min_tx_pwr > 70)
p = WIFI_POWER_8_5dBm;
else if (min_tx_pwr > 50)
p = WIFI_POWER_7dBm;
else if (min_tx_pwr > 20)
p = WIFI_POWER_5dBm;
#if defined(EMSESP_DEBUG)
// emsesp::EMSESP::logger().debug("Recommended WiFi Tx Power (set_power %d, new power %d, rssi %d, threshold %d)", min_tx_pwr / 10, p, rssi, threshold);
#endif
if (!WiFi.setTxPower(p)) {
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
}
}
// start the multicast UDP service so EMS-ESP is discoverable via .local
void NetworkSettingsService::mDNS_start() const {
#ifndef EMSESP_STANDALONE
MDNS.end();
if (_state.enableMDNS) {
if (!MDNS.begin(emsesp::EMSESP::system_.hostname().c_str())) {
emsesp::EMSESP::logger().warning("Failed to start mDNS Responder service");
return;
}
std::string address_s = emsesp::EMSESP::system_.hostname() + ".local";
MDNS.addService("http", "tcp", 80); // add our web server and rest API
MDNS.addService("telnet", "tcp", 23); // add our telnet console
// MDNS.addServiceTxt("http", "tcp", "version", EMSESP_APP_VERSION);
MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str());
emsesp::EMSESP::logger().info("Starting mDNS Responder service");
}
#endif
}
const char * NetworkSettingsService::disconnectReason(uint8_t code) {
#ifndef EMSESP_STANDALONE
switch (code) {
case WIFI_REASON_UNSPECIFIED: // = 1,
return "unspecified";
case WIFI_REASON_AUTH_EXPIRE: // = 2,
return "auth expire";
case WIFI_REASON_AUTH_LEAVE: // = 3,
return "auth leave";
case WIFI_REASON_ASSOC_EXPIRE: // = 4,
return "assoc expired";
case WIFI_REASON_ASSOC_TOOMANY: // = 5,
return "assoc too many";
case WIFI_REASON_NOT_AUTHED: // = 6,
return "not authenticated";
case WIFI_REASON_NOT_ASSOCED: // = 7,
return "not assoc";
case WIFI_REASON_ASSOC_LEAVE: // = 8,
return "assoc leave";
case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9,
return "assoc not authed";
case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10,
return "disassoc powerCAP bad";
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11,
return "disassoc supchan bad";
case WIFI_REASON_IE_INVALID: // = 13,
return "IE invalid";
case WIFI_REASON_MIC_FAILURE: // = 14,
return "MIC failure";
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15,
return "4way handshake timeout";
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16,
return "group key-update timeout";
case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17,
return "IE in 4way differs";
case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18,
return "group cipher invalid";
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19,
return "pairwise cipher invalid";
case WIFI_REASON_AKMP_INVALID: // = 20,
return "AKMP invalid";
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21,
return "unsupported RSN_IE version";
case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22,
return "invalid RSN_IE_CAP";
case WIFI_REASON_802_1X_AUTH_FAILED: // = 23,
return "802 X1 auth failed";
case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24,
return "cipher suite rejected";
case WIFI_REASON_BEACON_TIMEOUT: // = 200,
return "beacon timeout";
case WIFI_REASON_NO_AP_FOUND: // = 201,
return "no AP found";
case WIFI_REASON_AUTH_FAIL: // = 202,
return "auth fail";
case WIFI_REASON_ASSOC_FAIL: // = 203,
return "assoc fail";
case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204,
return "handshake timeout";
case WIFI_REASON_CONNECTION_FAIL: // 205,
return "connection fail";
case WIFI_REASON_AP_TSF_RESET: // 206,
return "AP tsf reset";
case WIFI_REASON_ROAMING: // 207,
return "roaming";
case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: // 208,
return "assoc comeback time too long";
case WIFI_REASON_SA_QUERY_TIMEOUT: // 209,
return "sa query timeout";
default:
return "unknown";
}
#endif
return "";
}
// handles both WiFI and Ethernet
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#ifndef EMSESP_STANDALONE
switch (event) {
case ARDUINO_EVENT_WIFI_STA_STOP:
if (_stopping) {
_lastConnectionAttempt = 0;
_stopping = false;
}
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
connectcount_++; // count the number of WiFi reconnects
emsesp::EMSESP::logger().warning("WiFi disconnected (#%d). Reason: %s (%d)",
connectcount_,
disconnectReason(info.wifi_sta_disconnected.reason),
info.wifi_sta_disconnected.reason); // IDF 4.0
emsesp::EMSESP::system_.has_ipv6(false);
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
char result[10];
emsesp::EMSESP::logger().info("WiFi connected (Local IP=%s, hostname=%s, TxPower=%s dBm)",
WiFi.localIP().toString().c_str(),
WiFi.getHostname(),
emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1));
mDNS_start();
break;
case ARDUINO_EVENT_ETH_START:
// configure for static IP
if (_state.staticIPConfig) {
ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
}
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
break;
case ARDUINO_EVENT_ETH_GOT_IP:
// prevent double calls to mDNS
if (!emsesp::EMSESP::system_.ethernet_connected()) {
emsesp::EMSESP::logger().info("Ethernet connected (Local IP=%s, speed %d Mbps)", ETH.localIP().toString().c_str(), ETH.linkSpeed());
emsesp::EMSESP::system_.ethernet_connected(true);
mDNS_start();
}
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
emsesp::EMSESP::logger().warning("Ethernet disconnected. Reason: %s (%d)",
disconnectReason(info.wifi_sta_disconnected.reason),
info.wifi_sta_disconnected.reason);
emsesp::EMSESP::system_.ethernet_connected(false);
emsesp::EMSESP::system_.has_ipv6(false);
break;
case ARDUINO_EVENT_ETH_STOP:
emsesp::EMSESP::logger().info("Ethernet stopped");
emsesp::EMSESP::system_.ethernet_connected(false);
emsesp::EMSESP::system_.has_ipv6(false);
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
// Set the TxPower after the connection is established, if we're using TxPower = 0 (Auto)
if (_state.tx_power == 0) {
setWiFiPowerOnRSSI();
}
#if ESP_IDF_VERSION_MAJOR < 5
WiFi.enableIpV6(); // force ipv6
#endif
break;
case ARDUINO_EVENT_ETH_CONNECTED:
#if ESP_IDF_VERSION_MAJOR < 5
ETH.enableIpV6(); // force ipv6
#endif
break;
// IPv6 specific - WiFi/Eth
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
case ARDUINO_EVENT_ETH_GOT_IP6: {
#if !TASMOTA_SDK && ESP_IDF_VERSION_MAJOR < 5
auto ip6 = IPv6Address((uint8_t *)info.got_ip6.ip6_info.ip.addr).toString();
#else
auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString();
#endif
const char * link = event == ARDUINO_EVENT_ETH_GOT_IP6 ? "Eth" : "WiFi";
if (ip6.startsWith("fe80")) {
emsesp::EMSESP::logger().info("IPv6 (%s) local: %s", link, ip6.c_str());
} else if (ip6.startsWith("fd") || ip6.startsWith("fc")) {
emsesp::EMSESP::logger().info("IPv6 (%s) ULA: %s", link, ip6.c_str());
} else {
emsesp::EMSESP::logger().info("IPv6 (%s) global: %s", link, ip6.c_str());
}
emsesp::EMSESP::system_.has_ipv6(true);
} break;
default:
break;
}
#endif
}
void NetworkSettings::read(NetworkSettings & settings, JsonObject root) {
// connection settings
root["ssid"] = settings.ssid;
root["bssid"] = settings.bssid;
root["password"] = settings.password;
root["hostname"] = settings.hostname;
root["static_ip_config"] = settings.staticIPConfig;
root["bandwidth20"] = settings.bandwidth20;
root["nosleep"] = settings.nosleep;
root["enableMDNS"] = settings.enableMDNS;
root["enableCORS"] = settings.enableCORS;
root["CORSOrigin"] = settings.CORSOrigin;
root["tx_power"] = settings.tx_power;
// extended settings
JsonUtils::writeIP(root, "local_ip", settings.localIP);
JsonUtils::writeIP(root, "gateway_ip", settings.gatewayIP);
JsonUtils::writeIP(root, "subnet_mask", settings.subnetMask);
JsonUtils::writeIP(root, "dns_ip_1", settings.dnsIP1);
JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2);
}
StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & settings) {
// keep copy of original settings
auto enableCORS = settings.enableCORS;
auto CORSOrigin = settings.CORSOrigin;
auto ssid = settings.ssid;
auto tx_power = settings.tx_power;
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
settings.bssid = root["bssid"] | "";
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
settings.staticIPConfig = root["static_ip_config"];
settings.bandwidth20 = root["bandwidth20"];
settings.tx_power = static_cast<uint8_t>(root["tx_power"] | 0);
settings.nosleep = root["nosleep"] | true;
settings.enableMDNS = root["enableMDNS"] | true;
settings.enableCORS = root["enableCORS"];
settings.CORSOrigin = root["CORSOrigin"] | "*";
// extended settings
JsonUtils::readIP(root, "local_ip", settings.localIP);
JsonUtils::readIP(root, "gateway_ip", settings.gatewayIP);
JsonUtils::readIP(root, "subnet_mask", settings.subnetMask);
JsonUtils::readIP(root, "dns_ip_1", settings.dnsIP1);
JsonUtils::readIP(root, "dns_ip_2", settings.dnsIP2);
// Swap around the dns servers if 2 is populated but 1 is not
if (IPUtils::isNotSet(settings.dnsIP1) && IPUtils::isSet(settings.dnsIP2)) {
settings.dnsIP1 = settings.dnsIP2;
settings.dnsIP2 = INADDR_NONE;
}
// Turning off static ip config if we don't meet the minimum requirements
// of ipAddress, gateway and subnet. This may change to static ip only
// as sensible defaults can be assumed for gateway and subnet
if (settings.staticIPConfig && (IPUtils::isNotSet(settings.localIP) || IPUtils::isNotSet(settings.gatewayIP) || IPUtils::isNotSet(settings.subnetMask))) {
settings.staticIPConfig = false;
}
// see if we need to inform the user of a restart
if (tx_power != settings.tx_power || enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin
|| (ssid != settings.ssid && settings.ssid.isEmpty())) {
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
}
return StateUpdateResult::CHANGED;
}

View File

@@ -0,0 +1,118 @@
#ifndef NetworkSettingsService_h
#define NetworkSettingsService_h
#include "StatefulService.h"
#include "FSPersistence.h"
#include "HttpEndpoint.h"
#include "JsonUtils.h"
#ifndef EMSESP_STANDALONE
#include <esp_wifi.h>
#include <ETH.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPmDNS.h>
#endif
#define NETWORK_SETTINGS_FILE "/config/networkSettings.json"
#define NETWORK_SETTINGS_SERVICE_PATH "/rest/networkSettings"
#define WIFI_RECONNECTION_DELAY (1000 * 3)
#ifndef FACTORY_WIFI_SSID
#define FACTORY_WIFI_SSID ""
#endif
#ifndef FACTORY_WIFI_PASSWORD
#define FACTORY_WIFI_PASSWORD ""
#endif
#ifndef FACTORY_WIFI_HOSTNAME
#define FACTORY_WIFI_HOSTNAME ""
#endif
// copied from Tasmota
#if CONFIG_IDF_TARGET_ESP32S2
#define MAX_TX_PWR_DBM_11b 195
#define MAX_TX_PWR_DBM_54g 150
#define MAX_TX_PWR_DBM_n 130
#define WIFI_SENSITIVITY_11b -880
#define WIFI_SENSITIVITY_54g -750
#define WIFI_SENSITIVITY_n -720
#elif CONFIG_IDF_TARGET_ESP32S3
#define MAX_TX_PWR_DBM_11b 210
#define MAX_TX_PWR_DBM_54g 190
#define MAX_TX_PWR_DBM_n 185
#define WIFI_SENSITIVITY_11b -880
#define WIFI_SENSITIVITY_54g -760
#define WIFI_SENSITIVITY_n -720
#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3
#define MAX_TX_PWR_DBM_11b 210
#define MAX_TX_PWR_DBM_54g 190
#define MAX_TX_PWR_DBM_n 185
#define WIFI_SENSITIVITY_11b -880
#define WIFI_SENSITIVITY_54g -760
#define WIFI_SENSITIVITY_n -730
#else
#define MAX_TX_PWR_DBM_11b 195
#define MAX_TX_PWR_DBM_54g 160
#define MAX_TX_PWR_DBM_n 140
#define WIFI_SENSITIVITY_11b -880
#define WIFI_SENSITIVITY_54g -750
#define WIFI_SENSITIVITY_n -700
#endif
class NetworkSettings {
public:
// core wifi configuration
String ssid;
String bssid;
String password;
String hostname;
bool staticIPConfig;
bool bandwidth20;
uint8_t tx_power;
bool nosleep;
bool enableMDNS;
bool enableCORS;
String CORSOrigin;
// optional configuration for static IP address
IPAddress localIP;
IPAddress gatewayIP;
IPAddress subnetMask;
IPAddress dnsIP1;
IPAddress dnsIP2;
static void read(NetworkSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, NetworkSettings & settings);
};
class NetworkSettingsService : public StatefulService<NetworkSettings> {
public:
NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void loop();
uint16_t getWifiReconnects() const {
return connectcount_;
}
private:
HttpEndpoint<NetworkSettings> _httpEndpoint;
FSPersistence<NetworkSettings> _fsPersistence;
unsigned long _lastConnectionAttempt;
bool _stopping;
uint16_t connectcount_ = 0; // number of wifi reconnects
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void mDNS_start() const;
const char * disconnectReason(uint8_t code);
void reconfigureWiFiConnection();
void manageSTA();
void setWiFiPowerOnRSSI();
};
#endif

View File

@@ -0,0 +1,93 @@
#include "NetworkStatus.h"
#include <emsesp.h>
#ifdef TASMOTA_SDK
#include "lwip/dns.h"
#endif
NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) {
securityManager->addEndpoint(server, NETWORK_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
networkStatus(request);
});
}
void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot();
bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected();
wl_status_t wifi_status = WiFi.status();
// see if Ethernet is connected
if (ethernet_connected) {
root["status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED
root["hostname"] = ETH.getHostname();
} else {
root["status"] = static_cast<uint8_t>(wifi_status);
root["hostname"] = WiFi.getHostname();
}
// for both connections show ethernet
if (ethernet_connected) {
// Ethernet
root["local_ip"] = ETH.localIP().toString();
#if ESP_IDF_VERSION_MAJOR < 5
root["local_ipv6"] = ETH.localIPv6().toString();
#else
root["local_ipv6"] = ETH.linkLocalIPv6().toString();
#endif
root["mac_address"] = ETH.macAddress();
root["subnet_mask"] = ETH.subnetMask().toString();
root["gateway_ip"] = ETH.gatewayIP().toString();
#ifdef TASMOTA_SDK
IPAddress dnsIP1 = IPAddress(dns_getserver(0));
IPAddress dnsIP2 = IPAddress(dns_getserver(1));
#else
IPAddress dnsIP1 = ETH.dnsIP(0);
IPAddress dnsIP2 = ETH.dnsIP(1);
#endif
if (IPUtils::isSet(dnsIP1)) {
root["dns_ip_1"] = dnsIP1.toString();
}
if (IPUtils::isSet(dnsIP2)) {
root["dns_ip_2"] = dnsIP2.toString();
}
} else if (wifi_status == WL_CONNECTED) {
root["local_ip"] = WiFi.localIP().toString();
// #if ESP_ARDUINO_VERSION_MAJOR < 3
#if ESP_IDF_VERSION_MAJOR < 5
root["local_ipv6"] = WiFi.localIPv6().toString();
#else
root["local_ipv6"] = WiFi.linkLocalIPv6().toString();
#endif
root["mac_address"] = WiFi.macAddress();
root["rssi"] = WiFi.RSSI();
root["ssid"] = WiFi.SSID();
root["bssid"] = WiFi.BSSIDstr();
root["channel"] = WiFi.channel();
root["reconnect_count"] = emsesp::EMSESP::esp32React.getWifiReconnects();
root["subnet_mask"] = WiFi.subnetMask().toString();
if (WiFi.gatewayIP() != INADDR_NONE) {
root["gateway_ip"] = WiFi.gatewayIP().toString();
}
#ifdef TASMOTA_SDK
IPAddress dnsIP1 = IPAddress(dns_getserver(0));
IPAddress dnsIP2 = IPAddress(dns_getserver(1));
#else
IPAddress dnsIP1 = WiFi.dnsIP(0);
IPAddress dnsIP2 = WiFi.dnsIP(1);
#endif
if (dnsIP1 != INADDR_NONE) {
root["dns_ip_1"] = dnsIP1.toString();
}
if (dnsIP2 != INADDR_NONE) {
root["dns_ip_2"] = dnsIP2.toString();
}
}
response->setLength();
request->send(response);
}

View File

@@ -0,0 +1,21 @@
#ifndef NetworkStatus_h
#define NetworkStatus_h
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <IPAddress.h>
#include "IPUtils.h"
#include "SecurityManager.h"
#define NETWORK_STATUS_SERVICE_PATH "/rest/networkStatus"
class NetworkStatus {
public:
NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager);
private:
void networkStatus(AsyncWebServerRequest * request);
};
#endif

View File

@@ -0,0 +1,112 @@
#ifndef SecurityManager_h
#define SecurityManager_h
#include "ArduinoJsonJWT.h"
#include <ESPAsyncWebServer.h>
#include <AsyncJson.h>
#include <list>
#define ACCESS_TOKEN_PARAMATER "access_token"
#define AUTHORIZATION_HEADER "Authorization"
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
class User {
public:
String username;
String password;
bool admin;
public:
User(String username, String password, bool admin)
: username(std::move(username))
, password(std::move(password))
, admin(admin) {
}
};
class Authentication {
public:
User * user = nullptr;
boolean authenticated = false;
public:
explicit Authentication(const User & user)
: user(new User(user))
, authenticated(true) {
}
Authentication() = default;
~Authentication() {
delete user;
}
};
typedef std::function<boolean(Authentication & authentication)> AuthenticationPredicate;
class AuthenticationPredicates {
public:
static bool NONE_REQUIRED(const Authentication & authentication) {
(void)authentication;
return true;
};
static bool IS_AUTHENTICATED(const Authentication & authentication) {
return authentication.authenticated;
};
static bool IS_ADMIN(const Authentication & authentication) {
return authentication.authenticated && authentication.user->admin;
};
};
class SecurityManager {
public:
// Authenticate, returning the user if found
virtual Authentication authenticate(const String & username, const String & password) = 0;
// Generate a JWT for the user provided
virtual String generateJWT(const User * user) = 0;
// Check the request header for the Authorization token
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
// Filter a request with the provided predicate, only returning true if the predicate matches.
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
// Wrap the provided request to provide validation against an AuthenticationPredicate.
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
// Wrap the provided json request callback to provide validation against an AuthenticationPredicate.
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
// Json endpoints - default POST
void addEndpoint(AsyncWebServer * server,
const String & path,
AuthenticationPredicate predicate,
ArJsonRequestHandlerFunction function,
WebRequestMethodComposite method = HTTP_POST) {
auto handler = new AsyncCallbackJsonWebHandler(path);
handler->onRequest(
wrapCallback([this, function](AsyncWebServerRequest * request, JsonVariant json) { function(request, json); }, AuthenticationPredicates::IS_ADMIN));
handler->setMethod(method);
server->addHandler(handler);
}
// non-Json endpoints - default GET
void addEndpoint(AsyncWebServer * server,
const String & path,
AuthenticationPredicate predicate,
ArRequestHandlerFunction function,
WebRequestMethodComposite method = HTTP_GET) {
auto * handler = new AsyncCallbackWebHandler();
handler->onRequest(wrapRequest([this, function](AsyncWebServerRequest * request) { function(request); }, predicate));
handler->setUri(path);
handler->setMethod(method);
server->addHandler(handler);
}
};
#endif

View File

@@ -0,0 +1,124 @@
#include "SecuritySettingsService.h"
SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs)
: _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this)
, _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE)
, _jwtHandler(FACTORY_JWT_SECRET) {
addUpdateHandler([this] { configureJWTHandler(); }, false);
server->on(GENERATE_TOKEN_PATH,
HTTP_GET,
SecuritySettingsService::wrapRequest([this](AsyncWebServerRequest * request) { generateToken(request); }, AuthenticationPredicates::IS_ADMIN));
}
void SecuritySettingsService::begin() {
_fsPersistence.readFromFS();
configureJWTHandler();
}
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) {
auto authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
if (authorizationHeader) {
String value = authorizationHeader->value();
if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
return authenticateJWT(value);
}
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
auto tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
String value = tokenParamater->value();
return authenticateJWT(value);
}
return {};
}
void SecuritySettingsService::configureJWTHandler() {
_jwtHandler.setSecret(_state.jwtSecret);
}
Authentication SecuritySettingsService::authenticateJWT(String & jwt) {
JsonDocument payloadDocument;
_jwtHandler.parseJWT(jwt, payloadDocument);
if (payloadDocument.is<JsonObject>()) {
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
String username = parsedPayload["username"];
for (const User & _user : _state.users) {
if (_user.username == username && validatePayload(parsedPayload, &_user)) {
return Authentication(_user);
}
}
}
return {};
}
Authentication SecuritySettingsService::authenticate(const String & username, const String & password) {
for (const User & _user : _state.users) {
if (_user.username == username && _user.password == password) {
return Authentication(_user);
}
}
return {};
}
inline void populateJWTPayload(JsonObject payload, const User * user) {
payload["username"] = user->username;
payload["admin"] = user->admin;
}
boolean SecuritySettingsService::validatePayload(JsonObject parsedPayload, const User * user) {
JsonDocument jsonDocument;
JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user);
return payload == parsedPayload;
}
String SecuritySettingsService::generateJWT(const User * user) {
JsonDocument jsonDocument;
JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user);
return _jwtHandler.buildJWT(payload);
}
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest * request) {
Authentication authentication = authenticateRequest(request);
return predicate(authentication);
};
}
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
return [this, onRequest, predicate](AsyncWebServerRequest * request) {
Authentication authentication = authenticateRequest(request);
if (!predicate(authentication)) {
request->send(401);
return;
}
onRequest(request);
};
}
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
return [this, onRequest, predicate](AsyncWebServerRequest * request, JsonVariant json) {
Authentication authentication = authenticateRequest(request);
if (!predicate(authentication)) {
request->send(401);
return;
}
onRequest(request, json);
};
}
void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) {
auto usernameParam = request->getParam("username");
for (const User & _user : _state.users) {
if (_user.username == usernameParam->value()) {
auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot();
root["token"] = generateJWT(&_user);
response->setLength();
request->send(response);
return;
}
}
request->send(401);
}

View File

@@ -0,0 +1,94 @@
#ifndef SecuritySettingsService_h
#define SecuritySettingsService_h
#include "SecurityManager.h"
#include "HttpEndpoint.h"
#include "FSPersistence.h"
#ifndef FACTORY_ADMIN_USERNAME
#define FACTORY_ADMIN_USERNAME "admin"
#endif
#ifndef FACTORY_ADMIN_PASSWORD
#define FACTORY_ADMIN_PASSWORD "admin"
#endif
#ifndef FACTORY_GUEST_USERNAME
#define FACTORY_GUEST_USERNAME "guest"
#endif
#ifndef FACTORY_GUEST_PASSWORD
#define FACTORY_GUEST_PASSWORD "guest"
#endif
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
#define GENERATE_TOKEN_SIZE 512
#define GENERATE_TOKEN_PATH "/rest/generateToken"
class SecuritySettings {
public:
String jwtSecret;
std::vector<User> users;
static void read(SecuritySettings & settings, JsonObject root) {
// secret
root["jwt_secret"] = settings.jwtSecret;
// users
JsonArray users = root["users"].to<JsonArray>();
for (const User & user : settings.users) {
JsonObject userRoot = users.add<JsonObject>();
userRoot["username"] = user.username;
userRoot["password"] = user.password;
userRoot["admin"] = user.admin;
}
}
static StateUpdateResult update(JsonObject root, SecuritySettings & settings) {
// secret
settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET;
// users
settings.users.clear();
if (root["users"].is<JsonArray>()) {
for (JsonVariant user : root["users"].as<JsonArray>()) {
settings.users.emplace_back(user["username"], user["password"], user["admin"]);
}
} else {
settings.users.emplace_back(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
settings.users.emplace_back(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false);
}
return StateUpdateResult::CHANGED;
}
};
class SecuritySettingsService final : public StatefulService<SecuritySettings>, public SecurityManager {
public:
SecuritySettingsService(AsyncWebServer * server, FS * fs);
void begin();
Authentication authenticate(const String & username, const String & password) override;
Authentication authenticateRequest(AsyncWebServerRequest * request) override;
String generateJWT(const User * user) override;
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) override;
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) override;
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate) override;
private:
HttpEndpoint<SecuritySettings> _httpEndpoint;
FSPersistence<SecuritySettings> _fsPersistence;
ArduinoJsonJWT _jwtHandler;
void generateToken(AsyncWebServerRequest * request);
void configureJWTHandler();
Authentication authenticateJWT(String & jwt); // Lookup the user by JWT
boolean validatePayload(JsonObject parsedPayload, const User * user); // Verify the payload is correct
};
#endif

View File

@@ -0,0 +1,3 @@
#include "StatefulService.h"
update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0;

View File

@@ -0,0 +1,137 @@
#ifndef StatefulService_h
#define StatefulService_h
#include <Arduino.h>
#include <ArduinoJson.h>
#include <vector>
#include <list>
#include <functional>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
enum class StateUpdateResult {
CHANGED = 0, // The update changed the state and propagation should take place if required
CHANGED_RESTART, // a restart of the device is needed
UNCHANGED, // The state was unchanged, propagation should not take place
ERROR // There was a problem updating the state, propagation should not take place
};
template <typename T>
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject root, T & settings)>;
template <typename T>
using JsonStateReader = std::function<void(T & settings, JsonObject root)>;
typedef size_t update_handler_id_t;
typedef std::function<void()> StateUpdateCallback;
typedef struct StateUpdateHandlerInfo {
static update_handler_id_t currentUpdatedHandlerId;
update_handler_id_t _id;
StateUpdateCallback _cb;
bool _allowRemove;
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove)
: _id(++currentUpdatedHandlerId)
, _cb(std::move(cb))
, _allowRemove(allowRemove) {};
} StateUpdateHandlerInfo_t;
template <class T>
class StatefulService {
public:
template <typename... Args>
explicit StatefulService(Args &&... args)
: _state(std::forward<Args>(args)...)
, _accessMutex(xSemaphoreCreateRecursiveMutex()) {
}
update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
if (!cb) {
return 0;
}
StateUpdateHandlerInfo_t updateHandler(std::move(cb), allowRemove);
_updateHandlers.push_back(std::move(updateHandler));
return updateHandler._id;
}
void removeUpdateHandler(update_handler_id_t id) {
for (auto it = _updateHandlers.begin(); it != _updateHandlers.end();) {
auto & elem = *it;
if (elem._allowRemove && elem._id == id) {
it = _updateHandlers.erase(it);
} else {
++it;
}
}
}
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(_state);
endTransaction();
if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers();
}
return result;
}
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T &)> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(_state);
endTransaction();
return result;
}
StateUpdateResult update(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction();
if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers();
}
return result;
}
StateUpdateResult updateWithoutPropagation(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction();
return result;
}
void read(std::function<void(T &)> stateReader) {
beginTransaction();
stateReader(_state);
endTransaction();
}
void read(JsonObject jsonObject, JsonStateReader<T> stateReader) {
beginTransaction();
stateReader(_state, jsonObject);
endTransaction();
}
void callUpdateHandlers() {
for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) {
updateHandler._cb();
}
}
protected:
T _state;
inline void beginTransaction() {
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
}
inline void endTransaction() {
xSemaphoreGiveRecursive(_accessMutex);
}
private:
SemaphoreHandle_t _accessMutex;
std::vector<StateUpdateHandlerInfo_t> _updateHandlers;
};
#endif

View File

@@ -0,0 +1,169 @@
#include "UploadFileService.h"
#include <emsesp.h>
#include <esp_app_format.h>
static String getFilenameExtension(const String & filename) {
const auto pos = filename.lastIndexOf('.');
if (pos != -1) {
return filename.substring(static_cast<unsigned int>(pos) + 1);
}
return {};
}
UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager)
, _is_firmware(false)
, _md5() {
// upload a file via a form
server->on(
UPLOAD_FILE_PATH,
HTTP_POST,
[this](AsyncWebServerRequest * request) { uploadComplete(request); },
[this](AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
handleUpload(request, filename, index, data, len, final);
});
}
void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
// quit if not authorized
Authentication authentication = _securityManager->authenticateRequest(request);
if (!AuthenticationPredicates::IS_ADMIN(authentication)) {
handleError(request, 403); // send the forbidden response
return;
}
// at init
if (!index) {
// check details of the file, to see if its a valid bin or json file
const String extension = getFilenameExtension(filename);
const std::size_t filesize = request->contentLength();
_is_firmware = false;
if ((extension == "bin") && (filesize > 1000000)) {
_is_firmware = true;
} else if (extension == "json") {
_md5[0] = '\0'; // clear md5
} else if (extension == "md5") {
if (len == _md5.size() - 1) {
std::memcpy(_md5.data(), data, _md5.size() - 1);
_md5.back() = '\0';
}
return;
} else {
_md5.front() = '\0';
handleError(request, 406); // Not Acceptable - unsupported file type
return;
}
if (_is_firmware) {
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32S2
if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32C3
if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32S3
if (len > 12 && (data[0] != 0xE9 || data[12] != 9)) {
handleError(request, 503); // service unavailable
return;
}
#endif
// it's firmware - initialize the ArduinoOTA updater
if (Update.begin(filesize - sizeof(esp_image_header_t))) {
if (strlen(_md5.data()) == _md5.size() - 1) {
Update.setMD5(_md5.data());
_md5.front() = '\0';
}
request->onDisconnect([this] { handleEarlyDisconnect(); }); // success, let's make sure we end the update if the client hangs up
} else {
handleError(request, 507); // failed to begin, send an error response Insufficient Storage
return;
}
} else {
// its a normal file, open a new temp file to write the contents too
request->_tempFile = LittleFS.open(TEMP_FILENAME_PATH, "w");
}
}
if (!_is_firmware) {
if (len && len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file
handleError(request, 507); // 507-Insufficient Storage
}
} else if (!request->_tempObject) { // if we haven't delt with an error, continue with the firmware update
if (Update.write(data, len) != len) {
handleError(request, 500); // internal error, failed
return;
}
if (final && !Update.end(true)) {
handleError(request, 500); // internal error, failed
}
}
}
void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
// did we just complete uploading a json file?
if (request->_tempFile) {
request->_tempFile.close(); // close the file handle as the upload is now done
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
emsesp::EMSESP::system_.systemStatus(
emsesp::SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor
return;
}
// check if it was a firmware upgrade
// if no error, send the success response as a JSON
if (_is_firmware && !request->_tempObject) {
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
emsesp::EMSESP::system_.systemStatus(
emsesp::SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor
return;
}
if (strlen(_md5.data()) == _md5.size() - 1) {
auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot();
root["md5"] = _md5.data();
response->setLength();
request->send(response);
return;
}
handleError(request, 500);
}
void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
// if we have had an error already, do nothing
if (request->_tempObject) {
return;
}
// send the error code to the client and record the error code in the temp object
AsyncWebServerResponse * response = request->beginResponse(code);
request->send(response);
// check for invalid extension and immediately kill the connection, which will through an error
// that is caught by the web code. Unfortunately the http error code is not sent to the client on fast network connections
if (code == 406) {
request->client()->close(true);
handleEarlyDisconnect();
}
}
void UploadFileService::handleEarlyDisconnect() {
_is_firmware = false;
Update.abort();
}

View File

@@ -0,0 +1,34 @@
#ifndef UploadFileService_h
#define UploadFileService_h
#include "SecurityManager.h"
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <Update.h>
#include <WiFi.h>
#include <array>
#define UPLOAD_FILE_PATH "/rest/uploadFile"
#define TEMP_FILENAME_PATH "/pre_load.json" // for uploaded json files, handled by System::check_restore()
class UploadFileService {
public:
UploadFileService(AsyncWebServer * server, SecurityManager * securityManager);
private:
SecurityManager * _securityManager;
bool _is_firmware;
std::array<char, 33> _md5;
void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final);
void uploadComplete(AsyncWebServerRequest * request);
void handleError(AsyncWebServerRequest * request, int code);
void handleEarlyDisconnect();
};
#endif

View File

@@ -0,0 +1,43 @@
#include "WiFiScanner.h"
WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) {
securityManager->addEndpoint(server, SCAN_NETWORKS_SERVICE_PATH, AuthenticationPredicates::IS_ADMIN, [this](AsyncWebServerRequest * request) {
scanNetworks(request);
});
securityManager->addEndpoint(server, LIST_NETWORKS_SERVICE_PATH, AuthenticationPredicates::IS_ADMIN, [this](AsyncWebServerRequest * request) {
listNetworks(request);
});
};
void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
request->send(202); // special code to indicate scan in progress
if (WiFi.scanComplete() != -1) {
WiFi.scanDelete();
WiFi.scanNetworks(true);
}
}
void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
const int numNetworks = WiFi.scanComplete();
if (numNetworks > -1) {
auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot();
JsonArray networks = root["networks"].to<JsonArray>();
for (uint8_t i = 0; i < numNetworks; i++) {
JsonObject network = networks.add<JsonObject>();
network["rssi"] = WiFi.RSSI(i);
network["ssid"] = WiFi.SSID(i);
network["bssid"] = WiFi.BSSIDstr(i);
network["channel"] = WiFi.channel(i);
network["encryption_type"] = static_cast<uint8_t>(WiFi.encryptionType(i));
}
response->setLength();
request->send(response);
} else if (numNetworks == -1) {
request->send(202); // special code to indicate scan in progress
} else {
scanNetworks(request);
}
}

View File

@@ -0,0 +1,23 @@
#ifndef WiFiScanner_h
#define WiFiScanner_h
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include "SecurityManager.h"
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
#define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks"
class WiFiScanner {
public:
WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager);
private:
void scanNetworks(AsyncWebServerRequest * request);
void listNetworks(AsyncWebServerRequest * request);
};
#endif

View File

@@ -640,6 +640,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
return true; // no sensors, return true
}
// return all values if its an info and values command
if (!strcmp(cmd, F_(info)) || !strcmp(cmd, F_(values))) {
for (const auto & sensor : sensors_) {
output[sensor.name()] = sensor.value();
@@ -647,6 +648,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
return true;
}
// show all entity details of the command is entities
if (!strcmp(cmd, F_(entities))) {
for (const auto & sensor : sensors_) {
get_value_json(output[sensor.name()].to<JsonObject>(), sensor);
@@ -654,7 +656,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
return true;
}
// this is for a specific sensor
// this is for a specific sensor, return the value
const char * attribute_s = Command::get_attribute(cmd);
for (const auto & sensor : sensors_) {
@@ -668,6 +670,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
return false; // not found
}
// note we don't add the device and state classes here, as we do in the custom entity service
void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
output["name"] = sensor.name();
output["fullname"] = sensor.name();
@@ -724,6 +727,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
}
val = b ? 1 : 0;
}
for (auto & sensor : sensors_) {
if (sensor.gpio() == gpio) {
double oldoffset = sensor.offset();
@@ -790,6 +794,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
return true;
}
}
return false;
}

View File

@@ -113,16 +113,16 @@ class AnalogSensor {
~AnalogSensor() = default;
enum AnalogType : int8_t {
NOTUSED, // 0 - disabled
DIGITAL_IN,
COUNTER,
ADC,
TIMER,
RATE,
DIGITAL_OUT,
PWM_0,
PWM_1,
PWM_2
NOTUSED = 0, // 0 = disabled
DIGITAL_IN = 1,
COUNTER = 2,
ADC = 3,
TIMER = 4,
RATE = 5,
DIGITAL_OUT = 6,
PWM_0 = 7,
PWM_1 = 8,
PWM_2 = 9
};
void start();

View File

@@ -31,13 +31,17 @@ std::vector<Command::CmdFunction> Command::cmdfunctions_;
// the entry point will be either via the Web API (api/) or MQTT (<base>/)
// returns a return code and json output
uint8_t Command::process(const char * path, const bool is_admin, const JsonObject input, JsonObject output) {
// check for MQTT, if so strip the "<base>" from the path
if (!strncmp(path, Mqtt::base().c_str(), Mqtt::base().length())) {
path += Mqtt::base().length();
}
SUrlParser p; // parse URL for the path names
p.parse(path);
// check first if it's from API or MQTT, if so strip the "api/" or "<base>/" from the path
if (p.paths().size() && ((p.paths().front() == "api") || (p.paths().front() == Mqtt::base()))) {
// check if it's from API
if (p.paths().size() && ((p.paths().front() == "api"))) {
p.paths().erase(p.paths().begin());
} else {
} else if (!p.paths().size()) {
return json_message(CommandRet::ERROR, "invalid path", output, path); // error
}
@@ -180,7 +184,8 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
}
// call the command based on the type
uint8_t return_code = CommandRet::ERROR;
uint8_t return_code = CommandRet::OK;
if (data.is<const char *>()) {
return_code = Command::call(device_type, command_p, data.as<const char *>(), is_admin, id_n, output);
} else if (data.is<int>()) {
@@ -216,7 +221,7 @@ const char * Command::return_code_string(const uint8_t return_code) {
default:
break;
}
char s[4];
static char s[4]; // static to avoid allocation on each call and loosing scope
return Helpers::smallitoa(s, return_code);
}
@@ -294,6 +299,8 @@ const char * Command::get_attribute(const char * cmd) {
return nullptr;
}
// returns the attribute in the given JSON object as a key/value pair called api_data
// or errors if the attribute is not found
bool Command::set_attribute(JsonObject output, const char * cmd, const char * attribute) {
if (attribute == nullptr) {
return true;
@@ -305,7 +312,20 @@ bool Command::set_attribute(JsonObject output, const char * cmd, const char * at
output["api_data"] = data; // always as a string
return true;
}
return EMSESP::return_not_found(output, attribute, cmd); // not found
output.clear();
// attribute isn't found
// it could be a value command, but the value doesn't exist?
if (strcmp(attribute, "value") == 0) {
LOG_DEBUG("%s has no value set", cmd);
return false; // fail
}
char error[100];
snprintf(error, sizeof(error), "no attribute '%s' in %s", attribute, cmd);
output["message"] = error;
return false;
}
// calls a command directly
@@ -326,7 +346,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
return CommandRet::NOT_FOUND;
}
char cmd[COMMAND_MAX_LENGTH];
strcpy(cmd, Helpers::toLower(command).c_str());
strlcpy(cmd, Helpers::toLower(command).c_str(), sizeof(cmd));
auto dname = EMSdevice::device_type_2_device_name(device_type); // device name, not translated
@@ -339,7 +359,12 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
return Command::list(device_type, output);
}
if (EMSESP::get_device_value_info(output, cmd, id, device_type)) { // entity = cmd
LOG_DEBUG("Fetched device entity/attributes for %s/%s", dname, cmd);
LOG_DEBUG("Fetched device entity/attributes for %s/%s (id=%d)", dname, cmd, id);
return CommandRet::OK;
}
} else if (device_type == EMSdevice::DeviceType::SYSTEM && strchr(cmd, '/')) {
// check service commands, if not found continue with command functions
if (EMSESP::system_.command_service(cmd, value)) {
return CommandRet::OK;
}
}
@@ -358,18 +383,27 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
flag = CommandFlag::CMD_FLAG_AHS;
}
// see if there is a command registered and it's valid
// see if there is a command registered for this EMS device
auto cf = find_command(device_type, device_id, cmd, flag);
if (!cf) {
// if we don't already have a message set, set it to invalid command
if (output["message"]) {
LOG_WARNING("Command failed: %s", output["message"].as<const char *>());
return CommandRet::ERROR;
} else {
std::string err = "no " + std::string(cmd) + " in " + dname;
// not an error, no test if we're fetching a value, but the value is not set
auto attribute_s = Command::get_attribute(cmd);
if (attribute_s) {
if (strcmp(attribute_s, "value") == 0) {
return CommandRet::NO_VALUE;
}
}
std::string err = "no entity '" + std::string(cmd) + "' in " + dname;
output["message"] = err;
LOG_WARNING("Command failed: %s", err.c_str());
}
return CommandRet::ERROR;
return CommandRet::NOT_FOUND;
}
// before calling the command, check permissions and abort if not authorized
@@ -422,7 +456,8 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
logger_.debug("%sCalled command %s", ro.c_str(), info_s);
} else {
if (id > 0) {
LOG_INFO(("%sCalled command %s with value %s and id %d on device 0x%02X"), ro.c_str(), info_s, value, id, device_id);
(device_id) ? LOG_INFO(("%sCalled command %s with value %s and id %d on device 0x%02X"), ro.c_str(), info_s, value, id, device_id)
: LOG_INFO(("%sCalled command %s with value %s and id %d"), ro.c_str(), info_s, value, id);
} else {
LOG_INFO(("%sCalled command %s with value %s"), ro.c_str(), info_s, value);
}
@@ -472,12 +507,12 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ui
for (auto & cf : cmdfunctions_) {
if (Helpers::toLower(cmd) == Helpers::toLower(cf.cmd_) && (cf.device_type_ == device_type) && (!device_id || cf.device_id_ == device_id)
&& (flag == CommandFlag::CMD_FLAG_DEFAULT || (flag & 0x3F) == (cf.flags_ & 0x3F))) {
&& (cf.device_type_ < EMSdevice::DeviceType::BOILER || flag == CommandFlag::CMD_FLAG_DEFAULT || (flag & 0x3F) == (cf.flags_ & 0x3F))) {
return &cf;
}
}
return nullptr; // command not found
return nullptr; // command not found, could be an attribute?
}
void Command::erase_device_commands(const uint8_t device_type) {
@@ -531,6 +566,16 @@ bool Command::list(const uint8_t device_type, JsonObject output) {
output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd));
output[F_(entities)] = Helpers::translated_word(FL_(entities_cmd));
if (device_type == EMSdevice::DeviceType::SYSTEM) {
output["settings/showertimer"] = Helpers::translated_word(FL_(system_cmd));
output["settings/showeralert"] = Helpers::translated_word(FL_(system_cmd));
output["settings/hideled"] = Helpers::translated_word(FL_(system_cmd));
output["settings/analogenabled"] = Helpers::translated_word(FL_(system_cmd));
output["mqtt/enabled"] = Helpers::translated_word(FL_(system_cmd));
output["ntp/enabled"] = Helpers::translated_word(FL_(system_cmd));
output["ap/enabled"] = Helpers::translated_word(FL_(system_cmd));
output["syslog/enabled"] = Helpers::translated_word(FL_(system_cmd));
}
// create a list of commands we have registered, and sort them
std::list<std::string> sorted_cmds;
for (const auto & cf : cmdfunctions_) {
@@ -559,7 +604,16 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
sorted_cmds.push_back(tagged_cmd(cf.cmd_, cf.flags_));
}
}
if (device_type == EMSdevice::DeviceType::SYSTEM) {
sorted_cmds.emplace_back("settings/showertimer");
sorted_cmds.emplace_back("settings/showeralert");
sorted_cmds.emplace_back("settings/hideled");
sorted_cmds.emplace_back("settings/analogenabled");
sorted_cmds.emplace_back("mqtt/enabled");
sorted_cmds.emplace_back("ntp/enabled");
sorted_cmds.emplace_back("ap/enabled");
sorted_cmds.emplace_back("syslog/enabled");
}
sorted_cmds.sort(); // sort them
// if not in verbose mode, just print them on a single line and exit
@@ -567,6 +621,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
sorted_cmds.emplace_front(F_(info));
sorted_cmds.emplace_front(F_(commands));
sorted_cmds.emplace_front(F_(values));
sorted_cmds.emplace_front(F_(entities));
for (const auto & cl : sorted_cmds) {
shell.print(cl);
shell.print(" ");
@@ -590,6 +645,19 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
shell.print(' ');
}
shell.print(COLOR_BRIGHT_CYAN);
if (cf.has_flags(CommandFlag::CMD_FLAG_HC)) {
shell.print(Helpers::translated_word(FL_(tag_hcx)));
shell.print(' ');
} else if (cf.has_flags(CommandFlag::CMD_FLAG_DHW)) {
shell.print(Helpers::translated_word(FL_(tag_dhwx)));
shell.print(' ');
} else if (cf.has_flags(CommandFlag::CMD_FLAG_AHS)) {
shell.print(Helpers::translated_word(FL_(tag_ahsx)));
shell.print(' ');
} else if (cf.has_flags(CommandFlag::CMD_FLAG_HS)) {
shell.print(Helpers::translated_word(FL_(tag_hsx)));
shell.print(' ');
}
shell.print(Helpers::translated_word(cf.description_));
if (!cf.has_flags(CommandFlag::ADMIN_ONLY)) {
shell.print(' ');
@@ -597,9 +665,9 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
shell.print('*');
}
shell.print(COLOR_RESET);
shell.println();
}
}
shell.println();
}
}
@@ -632,11 +700,7 @@ bool Command::device_has_commands(const uint8_t device_type) {
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
for (const auto & cf : cmdfunctions_) {
if (cf.device_type_ == device_type) {
return true;
}
}
return true;
}
}
@@ -677,6 +741,8 @@ void Command::show_all(uuid::console::Shell & shell) {
shell.println(COLOR_RESET);
shell.printf(" values \t\t\t%slist all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
shell.printf(" entities \t\t\t%slist all entities %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
// show system ones first
show(shell, EMSdevice::DeviceType::SYSTEM, true);

View File

@@ -48,7 +48,8 @@ enum CommandRet : uint8_t {
NOT_FOUND, // 2
ERROR, // 3
NOT_ALLOWED, // 4 - needs authentication
INVALID // 5 - invalid (tag)
INVALID, // 5 - invalid (tag)
NO_VALUE // 6 - no value
};
using cmd_function_p = std::function<bool(const char * data, const int8_t id)>;

View File

@@ -20,10 +20,9 @@
#include "console.h"
#include "console_stream.h"
#include "emsesp.h"
#include "version.h"
#if defined(EMSESP_TEST)
#include "test/test.h"
#include "../test/test.h"
#endif
using ::uuid::console::Commands;
@@ -150,7 +149,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
if (completed) {
uint64_t now = uuid::get_uptime_ms();
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
EMSESP::esp32React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (!password.empty() && (securitySettings.jwtSecret.equals(password.c_str()))) {
become_admin(shell);
} else {
@@ -174,7 +173,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getSecuritySettingsService()->update([&](SecuritySettings & securitySettings) {
EMSESP::esp32React.getSecuritySettingsService()->update([&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = password2.c_str();
return StateUpdateResult::CHANGED;
});
@@ -208,6 +207,40 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
//
// SET commands
//
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
string_vector{F_(set), F_(admin), F_(password)},
[](Shell & shell, const std::vector<std::string> & arguments) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
#ifndef EMSESP_STANDALONE
EMSESP::esp32React.getSecuritySettingsService()->updateWithoutPropagation(
[&](SecuritySettings & securitySettings) {
// find the username 'admin' and update its password
for (auto & user : securitySettings.users) {
if (user.username == "admin") {
user.password = password2.c_str();
return StateUpdateResult::CHANGED;
}
}
// otherwise add a new admin user
securitySettings.users.emplace_back(F_(admin), password2.c_str(), true);
return StateUpdateResult::CHANGED;
});
#endif
shell.println("Admin user's password updated.");
} else {
shell.println("Passwords do not match");
}
}
});
}
});
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
string_vector{F_(set), F_(wifi), F_(password)},
@@ -217,11 +250,10 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation(
[&](NetworkSettings & networkSettings) {
networkSettings.password = password2.c_str();
return StateUpdateResult::CHANGED;
});
EMSESP::esp32React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
networkSettings.password = password2.c_str();
return StateUpdateResult::CHANGED;
});
shell.println("WiFi password updated. Reconnecting...");
EMSESP::system_.wifi_reconnect();
} else {
@@ -241,7 +273,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
shell.println("The network connection will be reset...");
Shell::loop_all();
delay(1000); // wait a second
EMSESP::esp8266React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) {
EMSESP::esp32React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) {
networkSettings.hostname = arguments.front().c_str();
return StateUpdateResult::CHANGED;
});
@@ -252,7 +284,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
string_vector{F_(set), F_(wifi), F_(ssid)},
{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
EMSESP::esp32React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
networkSettings.ssid = arguments.front().c_str();
return StateUpdateResult::CHANGED;
});
@@ -327,38 +359,6 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
EMSESP::uart_init();
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
string_vector{F_(set), F_(service)},
string_vector{F_(service_mandatory), F_(enable_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.back() == "enable" || arguments.back() == "disable") {
bool enable = arguments.back() == "enable";
if (arguments.front() == "mqtt") {
EMSESP::esp8266React.getMqttSettingsService()->update([&](MqttSettings & Settings) {
Settings.enabled = enable;
return StateUpdateResult::CHANGED;
});
} else if (arguments.front() == "ntp") {
EMSESP::esp8266React.getNTPSettingsService()->update([&](NTPSettings & Settings) {
Settings.enabled = enable;
return StateUpdateResult::CHANGED;
});
} else if (arguments.front() == "ap") {
EMSESP::esp8266React.getAPSettingsService()->update([&](APSettings & Settings) {
Settings.provisionMode = enable ? 0 : 2;
return StateUpdateResult::CHANGED;
});
} else {
shell.printfln("unknown service: %s", arguments.front().c_str());
return;
}
shell.printfln("service '%s' %sd", arguments.front().c_str(), arguments.back().c_str());
} else {
shell.println("Must be `enable` or `disable`");
}
});
//
// EMS device commands
//
@@ -626,7 +626,7 @@ void EMSESPShell::display_banner() {
println();
// set console name
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { console_hostname_ = networkSettings.hostname.c_str(); });
EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { console_hostname_ = networkSettings.hostname.c_str(); });
if (console_hostname_.empty()) {
console_hostname_ = "ems-esp";
}

View File

@@ -134,7 +134,7 @@
#endif
#ifndef EMSESP_DEFAULT_MODBUS_TIMEOUT
#define EMSESP_DEFAULT_MODBUS_TIMEOUT 10000
#define EMSESP_DEFAULT_MODBUS_TIMEOUT 300
#endif
#ifndef EMSESP_DEFAULT_BOARD_PROFILE

View File

@@ -39,6 +39,7 @@
{132, DeviceType::BOILER, "GC7000F", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{133, DeviceType::BOILER, "Logano GB125/KB195i, Logamatic MC110", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{154, DeviceType::BOILER, "Greenstar 30Ri Compact", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{155, DeviceType::BOILER, "Suprapur-o", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{167, DeviceType::BOILER, "Cerapur Aero", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{168, DeviceType::BOILER, "Hybrid Heatpump", DeviceFlags::EMS_DEVICE_FLAG_HYBRID},
{170, DeviceType::BOILER, "Logano GB212", DeviceFlags::EMS_DEVICE_FLAG_NONE},
@@ -63,6 +64,7 @@
{121, DeviceType::CONTROLLER, "MCM10", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{125, DeviceType::CONTROLLER, "BC25", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{152, DeviceType::CONTROLLER, "Controller", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{166, DeviceType::CONTROLLER, "Suprapur-o", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{168, DeviceType::CONTROLLER, "Hybrid Heatpump", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{169, DeviceType::CONTROLLER, "BC40", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{190, DeviceType::CONTROLLER, "BC10", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
@@ -82,6 +84,7 @@
// Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19-0x1B for hc2-4 / 0x38
{ 4, DeviceType::THERMOSTAT, "UI800, BC400", DeviceFlags::EMS_DEVICE_FLAG_BC400}, // 0x10
{ 10, DeviceType::THERMOSTAT, "CR11", DeviceFlags::EMS_DEVICE_FLAG_CR11}, // 0x18
{ 65, DeviceType::THERMOSTAT, "RC10", DeviceFlags::EMS_DEVICE_FLAG_RC20_N},// 0x17
{ 67, DeviceType::THERMOSTAT, "RC30", DeviceFlags::EMS_DEVICE_FLAG_RC30_N},// 0x10 - based on RC35
{ 77, DeviceType::THERMOSTAT, "RC20, Moduline 300", DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17
@@ -93,9 +96,9 @@
{ 93, DeviceType::THERMOSTAT, "RC20RF", DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19
{ 94, DeviceType::THERMOSTAT, "RFM20 Remote", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18
{151, DeviceType::THERMOSTAT, "RC25", DeviceFlags::EMS_DEVICE_FLAG_RC25}, // 0x17
{157, DeviceType::THERMOSTAT, "RC200, CW100, CR120, CR50", DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18,, CR120 v22 is FLAG_BC400
{157, DeviceType::THERMOSTAT, "RC200, CW100, CR120, CR50", DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, CR120 v22 is FLAG_BC400
{158, DeviceType::THERMOSTAT, "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410", DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{165, DeviceType::THERMOSTAT, "RC100, CR10, Moduline 1000/1010", DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
{165, DeviceType::THERMOSTAT, "RC100, CR10, Moduline 1000/1010", DeviceFlags::EMS_DEVICE_FLAG_CR11}, // 0x18, 0x38
{172, DeviceType::THERMOSTAT, "Rego 2000/3000", DeviceFlags::EMS_DEVICE_FLAG_R3000}, // 0x10
{215, DeviceType::THERMOSTAT, "Comfort RF", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{216, DeviceType::THERMOSTAT, "CRF200S", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
@@ -195,8 +198,8 @@
{161, DeviceType::WATER, "MM200", DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{163, DeviceType::WATER, "SM100, MS100", DeviceFlags::EMS_DEVICE_FLAG_SM100},
{164, DeviceType::WATER, "SM200, MS200", DeviceFlags::EMS_DEVICE_FLAG_SM100},
{248, DeviceType::MIXER, "HM210", DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{157, DeviceType::THERMOSTAT, "RC120", DeviceFlags::EMS_DEVICE_FLAG_CR120}
{248, DeviceType::MIXER, "HM210", DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}
// {157, DeviceType::THERMOSTAT, "RC120", DeviceFlags::EMS_DEVICE_FLAG_CR120}
#endif
// clang-format on

View File

@@ -21,12 +21,13 @@
namespace emsesp {
// returns number of visible device values (entries) for this device
// returns number of visible device values (entities) for this device, for the Devices page
// this includes commands since they can also be entities and visible in the web UI
uint8_t EMSdevice::count_entities() {
uint8_t count = 0;
for (const auto & dv : devicevalues_) {
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.hasValue()) {
// if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.hasValue()) {
if (dv.hasValue()) {
count++;
}
}
@@ -586,7 +587,7 @@ void EMSdevice::add_device_value(int8_t tag, // to b
if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) {
char entity[70];
if (tag < DeviceValueTAG::TAG_HC1) {
strncpy(entity, short_name, sizeof(entity));
strncpy(entity, short_name, sizeof(entity) - 1);
} else {
snprintf(entity, sizeof(entity), "%s/%s", tag_to_mqtt(tag), short_name);
}
@@ -1182,13 +1183,26 @@ void EMSdevice::set_climate_minmax(int8_t tag, int16_t min, uint32_t max) {
}
}
void EMSdevice::setValueEnum(const void * value_p, const char * const ** options) {
for (auto & dv : devicevalues_) {
if (dv.value_p == value_p) {
if (dv.options != options && Mqtt::ha_enabled()) {
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
}
dv.options = options;
dv.options_size = Helpers::count_items(options);
break;
}
}
}
// set mask per device entity based on the id which is prefixed with the 2 char hex mask value
// returns true if the entity has a mask set (not 0 the default)
void EMSdevice::setCustomizationEntity(const std::string & entity_id) {
for (auto & dv : devicevalues_) {
char entity_name[70];
if (dv.tag < DeviceValueTAG::TAG_HC1) {
strncpy(entity_name, dv.short_name, sizeof(entity_name));
strncpy(entity_name, dv.short_name, sizeof(entity_name) - 1);
} else {
snprintf(entity_name, sizeof(entity_name), "%s/%s", tag_to_mqtt(dv.tag), dv.short_name);
}
@@ -1284,6 +1298,13 @@ void EMSdevice::dump_devicevalue_info() {
Serial.print(product_id_);
Serial.print(',');
// <tag>/<shortname> unless generating for modbus registers - used in scripts/generate_csv_and_headers.sh
#ifndef EMSESP_MODBUS
if (dv.tag > DeviceValueTAG::TAG_DEVICE_DATA) {
Serial.print(tag_to_string(dv.tag));
Serial.print('.');
}
#endif
Serial.print(dv.short_name);
Serial.print(',');
@@ -1507,6 +1528,7 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
if (cmd_s == Helpers::toLower(dv.short_name) && (tag <= 0 || tag == dv.tag)) {
get_value_json(output, dv);
// if we're filtering on an attribute, go find it
// if we can't find it, maybe it exists but doesn't not have a value assigned yet
return Command::set_attribute(output, cmd_s, attribute_s);
}
}
@@ -1534,7 +1556,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
json["circuit"] = tag_to_mqtt(dv.tag);
}
char val[10];
char val[20];
switch (dv.type) {
case DeviceValueType::ENUM: {
if (*(uint8_t *)(dv.value_p) < dv.options_size) {
@@ -1650,10 +1672,8 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
}
}
// add uom if it's not a " " (single space)
if (dv.uom != DeviceValueUOM::NONE) {
json["uom"] = uom_to_string(dv.uom);
}
// add uom, state class and device class
Mqtt::add_ha_uom(json, dv.type, dv.uom, dv.short_name, false); // no icon
json["readable"] = !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
json["writeable"] = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
@@ -1772,7 +1792,7 @@ bool EMSdevice::generate_values(JsonObject output, const int8_t tag_filter, cons
: (dv.uom == DeviceValueUOM::DEGREES) ? 2
: (dv.uom == DeviceValueUOM::DEGREES_R) ? 1
: 0;
char val[10] = {'\0'};
char val[20] = {'\0'};
if (dv.type == DeviceValueType::INT8) {
json[name] = serialized(Helpers::render_value(val, *(int8_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
} else if (dv.type == DeviceValueType::UINT8) {
@@ -1984,7 +2004,7 @@ std::string EMSdevice::name() {
if (model().empty()) {
return default_name();
}
return model() + "/" + default_name();
return model() + "/" + std::string(default_name());
}
return custom_name();
@@ -2029,7 +2049,7 @@ int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std:
return -5;
}
for (auto i = 0; i < register_length_s; i++) {
for (size_t i = 0; i < register_length_s; i++) {
auto hi = (uint8_t)value_s[2 * i];
auto lo = (uint8_t)(2 * i + 1 < length_s ? value_s[2 * i + 1] : 0);
result[i] = ((uint16_t)hi << 8) | lo;
@@ -2050,7 +2070,7 @@ int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std:
else if (dv.type == DeviceValueType::INT8) {
if (result.size() != 1)
return -8;
result[0] = (uint16_t)(uint8_t)(*(int8_t *)(dv.value_p));
result[0] = (uint16_t)(int16_t)(*(int8_t *)(dv.value_p));
} else if (dv.type == DeviceValueType::UINT8) {
if (result.size() != 1)
return -9;
@@ -2123,7 +2143,12 @@ int EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname,
return -4;
}
jsonValue["value"] = Helpers::numericoperator2scalefactor(dv.numeric_operator) * (float)((uint16_t)modbus_data[0] << 8 | (uint16_t)modbus_data[1]);
if (dv.type == DeviceValueType::INT8 || dv.type == DeviceValueType::INT16) { // handle signed
jsonValue["value"] =
Helpers::numericoperator2scalefactor(dv.numeric_operator) * (float)(int16_t)((uint16_t)modbus_data[0] << 8 | (uint16_t)modbus_data[1]);
} else {
jsonValue["value"] = Helpers::numericoperator2scalefactor(dv.numeric_operator) * (float)((uint16_t)modbus_data[0] << 8 | (uint16_t)modbus_data[1]);
}
} else if (dv.type == DeviceValueType::UINT24 || dv.type == DeviceValueType::UINT32 || dv.type == DeviceValueType::TIME) {
// these data types are 2 16 bit register
if (modbus_data.size() != 4) {
@@ -2142,7 +2167,7 @@ int EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname,
}
uint32_t value = 0;
for (auto i = 0; i < modbus_data.size(); i++) {
for (size_t i = 0; i < modbus_data.size(); i++) {
value += (uint32_t)modbus_data[modbus_data.size() - i - 1] << (i * 8);
}

View File

@@ -151,7 +151,7 @@ class EMSdevice {
}
void has_update(char * value, const char * newvalue, size_t len) {
if (strcmp(value, newvalue) != 0) {
if (value && newvalue && strcmp(value, newvalue) != 0) {
strlcpy(value, newvalue, len);
has_update_ = true;
publish_value(value);
@@ -174,6 +174,14 @@ class EMSdevice {
}
}
void has_update(int16_t & value, int16_t newvalue) {
if (value != newvalue) {
value = newvalue;
has_update_ = true;
publish_value((void *)&value);
}
}
void has_update(uint32_t & value, uint32_t newvalue) {
if (value != newvalue) {
value = newvalue;
@@ -216,6 +224,7 @@ class EMSdevice {
void add_handlers_ignored(const uint16_t handler);
void set_climate_minmax(int8_t tag, int16_t min, uint32_t max);
void setValueEnum(const void * value_p, const char * const ** options);
void setCustomizationEntity(const std::string & entity_id);
void getCustomizationEntities(std::vector<std::string> & entity_ids);
@@ -458,6 +467,7 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_FLAG_BC400 = 14; // mostly like RC300, but some changes
static constexpr uint8_t EMS_DEVICE_FLAG_R3000 = 15; // Rego3000, same as RC300 with different wwmodes
static constexpr uint8_t EMS_DEVICE_FLAG_CR120 = 16; // mostly like RC300, but some changes
static constexpr uint8_t EMS_DEVICE_FLAG_CR11 = 17; // CRF200 only monitor
uint8_t count_entities();
uint8_t count_entities_fav();

View File

@@ -28,38 +28,52 @@ static_assert(uuid::console::thread_safe, "uuid-console must be thread-safe");
namespace emsesp {
// Static member definitions
std::deque<std::unique_ptr<EMSdevice>> EMSESP::emsdevices;
std::vector<EMSESP::Device_record> EMSESP::device_library_;
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE;
uint8_t EMSESP::watch_ = 0;
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
uint16_t EMSESP::response_id_ = 0;
bool EMSESP::tap_water_active_ = false;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
bool EMSESP::trace_raw_ = false;
uint16_t EMSESP::wait_validate_ = 0;
bool EMSESP::wait_km_ = false;
uint32_t EMSESP::last_fetch_ = 0;
AsyncWebServer webServer(80);
#if defined(EMSESP_STANDALONE)
FS dummyFS;
ESP8266React EMSESP::esp8266React(&webServer, &dummyFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
ESP32React EMSESP::esp32React(&webServer, &dummyFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
#else
ESP8266React EMSESP::esp8266React(&webServer, &LittleFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
ESP32React EMSESP::esp32React(&webServer, &LittleFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
#endif
WebActivityService EMSESP::webActivityService = WebActivityService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebDataService EMSESP::webDataService = WebDataService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebAPIService EMSESP::webAPIService = WebAPIService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebActivityService EMSESP::webActivityService = WebActivityService(&webServer, EMSESP::esp32React.getSecurityManager());
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp32React.getSecurityManager());
WebDataService EMSESP::webDataService = WebDataService(&webServer, EMSESP::esp32React.getSecurityManager());
WebAPIService EMSESP::webAPIService = WebAPIService(&webServer, EMSESP::esp32React.getSecurityManager());
WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP::esp32React.getSecurityManager());
using DeviceFlags = EMSdevice;
using DeviceType = EMSdevice::DeviceType;
std::deque<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices
std::vector<EMSESP::Device_record> EMSESP::device_library_; // library of all our known EMS devices, in heap
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
uuid::log::Logger EMSESP::logger() {
return logger_;
}
@@ -79,21 +93,6 @@ AnalogSensor EMSESP::analogsensor_; // Analog sensors
Shower EMSESP::shower_; // Shower logic
Preferences EMSESP::nvs_; // NV Storage
// static/common variables
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
uint8_t EMSESP::watch_ = 0; // trace off
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
uint16_t EMSESP::response_id_ = 0;
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
bool EMSESP::trace_raw_ = false;
uint16_t EMSESP::wait_validate_ = 0;
bool EMSESP::wait_km_ = true;
// for a specific EMS device go and request data values
// or if device_id is 0 it will fetch from all our known and active devices
void EMSESP::fetch_device_values(const uint8_t device_id) {
@@ -329,10 +328,11 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
shell.println();
}
// Dump all entities to Serial out
// Dump all entities to Serial out - used to create the dump_entities.csv file
// this is intended to run within the OS with lots of available memory!
#if defined(EMSESP_STANDALONE)
void EMSESP::dump_all_entities(uuid::console::Shell & shell) {
Serial.println();
Serial.println("---- CSV START ----"); // marker use by py script
// add header for CSV
Serial.println("device name,device type,product id,shortname,fullname,type [options...] \\| (min/max),uom,writeable,discovery entityid v3.4,discovery "
@@ -358,12 +358,16 @@ void EMSESP::dump_all_entities(uuid::console::Shell & shell) {
if (device.device_type == DeviceType::MIXER) {
device_id = 0x20; // hc
}
// For Heatsource set AM200
if (device.device_type == DeviceType::HEATSOURCE) {
device_id = EMSdevice::EMS_DEVICE_ID_AHS1;
}
// add the device and print out all the entities
// for testing the mixer use ... if (device.product_id == 69) {
emsdevices.push_back(
EMSFactory::add(device.device_type, device_id, device.product_id, "1.0", device.default_name, device.flags, EMSdevice::Brand::NO_BRAND));
emsdevices.back()->dump_devicevalue_info();
emsdevices.back()->dump_devicevalue_info(); // print out the entities
}
}
}
@@ -378,6 +382,7 @@ void EMSESP::dump_all_entities(uuid::console::Shell & shell) {
void EMSESP::dump_all_telegrams(uuid::console::Shell & shell) {
std::vector<EMSdevice::TelegramFunctionDump> telegram_functions_dump;
Serial.println();
Serial.println("---- CSV START ----"); // marker use by py script
// add header for CSV
Serial.println("telegram_type_id,name,is_fetched");
@@ -762,14 +767,20 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8_t id, const uint8_t devicetype) {
// check first for EMS devices
bool found_device = false;
for (const auto & emsdevice : emsdevices) {
if (emsdevice->device_type() == devicetype) {
found_device = true;
// we may have multiple devices of the same type, so we need to check the id (e.g. id could be a hc number)
if (emsdevice->get_value_info(root, cmd, id)) {
return true;
// if we have no values, keep going traversing the devices, it may be a thermostat with multiple hc's
if (root.size()) {
return true;
}
}
}
}
// if the EMS device was valid, but the cmd not found exit. it will be handled upstream.
if (found_device) {
return false;
@@ -805,15 +816,6 @@ bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8
return false; // not found
}
// sends JSON error message, used with API calls
bool EMSESP::return_not_found(JsonObject output, const char * msg, const char * cmd) {
output.clear();
char error[100];
snprintf(error, sizeof(error), "no %s in %s", msg, cmd);
output["message"] = error;
return false;
}
// search for recognized device_ids : Me, All, otherwise print hex value
std::string EMSESP::device_tostring(const uint8_t device_id) {
if ((device_id & 0x7F) == EMSbus::ems_bus_id()) {
@@ -914,14 +916,14 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
std::string str;
str.reserve(200);
if (telegram->operation == Telegram::Operation::RX_READ) {
str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), R, " + type_name + "("
str = src_name + "(" + Helpers::hextoa(src) + ") R " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "("
+ Helpers::hextoa(telegram->type_id) + "), length: " + Helpers::itoa(telegram->message_data[0])
+ ((telegram->message_length > 1) ? ", data: " + Helpers::data_to_hex(telegram->message_data + 1, telegram->message_length - 1) : "");
} else if (telegram->dest == 0) {
str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), B, " + type_name + "("
str = src_name + "(" + Helpers::hextoa(src) + ") B " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "("
+ Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message();
} else {
str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), W, " + type_name + "("
str = src_name + "(" + Helpers::hextoa(src) + ") W " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "("
+ Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message();
}
@@ -1630,13 +1632,13 @@ void EMSESP::start() {
webLogService.begin();
// loads core system services settings (network, mqtt, ap, ntp etc)
esp8266React.begin();
esp32React.begin();
#ifndef EMSESP_STANDALONE
LOG_INFO("EMS-ESP version %s (%s partition)", EMSESP_APP_VERSION,
esp_ota_get_running_partition()->label); // welcome message
LOG_INFO("EMS-ESP version %s", EMSESP_APP_VERSION);
LOG_DEBUG("Boot partition %s, Active partition %s", esp_ota_get_boot_partition()->label, esp_ota_get_running_partition()->label);
#else
LOG_INFO("EMS-ESP version %s", EMSESP_APP_VERSION); // welcome message
LOG_INFO("EMS-ESP version %s", EMSESP_APP_VERSION);
#endif
LOG_DEBUG("System is running in Debug mode");
LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
@@ -1689,8 +1691,9 @@ void EMSESP::start() {
// start services
if (system_.modbus_enabled()) {
modbus_ = new Modbus;
modbus_->start(1, system_.modbus_port(), system_.modbus_max_clients(), system_.modbus_timeout());
modbus_->start(1, system_.modbus_port(), system_.modbus_max_clients(), system_.modbus_timeout() * 1000);
}
mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart
shower_.start(); // initialize shower timer and shower alert
@@ -1704,34 +1707,55 @@ void EMSESP::start() {
LOG_INFO("Starting Web Server");
}
void EMSESP::start_serial_console() {
shell_ = std::make_shared<EMSESPConsole>(*this, serial_console_, true);
shell_->maximum_log_messages(100);
shell_->start();
#if defined(EMSESP_DEBUG)
shell_->log_level(uuid::log::Level::DEBUG);
shell_->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when compiled with debug
#else
shell_->log_level(uuid::log::Level::TRACE);
#endif
}
void EMSESP::shell_prompt() {
#ifndef EMSESP_STANDALONE
serial_console_.println();
serial_console_.printf("EMS-ESP %s: press CTRL-D to activate this serial console", EMSESP_APP_VERSION);
serial_console_.println();
#endif
}
// main loop calling all services
void EMSESP::loop() {
esp8266React.loop(); // web services
system_.loop(); // does LED and checks system health, and syslog service
esp32React.loop(); // web services
system_.loop(); // does LED and checks system health, and syslog service
// if we're doing an OTA upload, skip everything except from console refresh
static bool upload_status = true; // ready for any OTA uploads
if (!system_.upload_isrunning()) {
// service loops
// run the loop, unless we're in the middle of an OTA upload
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) {
webLogService.loop(); // log in Web UI
rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off
temperaturesensor_.loop(); // read sensor temperatures
analogsensor_.loop(); // read analog sensor values
publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue
publish_all_loop(); // with HA messages in parts to avoid flooding the MQTT queue
mqtt_.loop(); // sends out anything in the MQTT queue
webModulesService.loop(); // loop through the external library modules
if (system_.PSram() == 0) { // run non-async if there is no PSRAM available
webSchedulerService.loop();
}
scheduled_fetch_values(); // force a query on the EMS devices to fetch latest data at a set interval (1 min)
}
} else if (upload_status) {
// start an upload from a URL, if it exists. This is blocking.
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_PENDING_UPLOAD) {
// start an upload from a URL, assuming the URL exists and set from a previous pass
// Note this next call is synchronous and blocking.
if (!system_.uploadFirmwareURL()) {
upload_status = false; // abort all other attempts, until reset (after a restart normally)
system_.upload_isrunning(false);
// upload failed, send a "reset" to return back to normal
Shell::loop_all(); // flush log buffers so latest error message are shown in console
system_.uploadFirmwareURL("reset");
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
}
}
@@ -1773,24 +1797,4 @@ void EMSESP::loop() {
}
}
void EMSESP::start_serial_console() {
shell_ = std::make_shared<EMSESPConsole>(*this, serial_console_, true);
shell_->maximum_log_messages(100);
shell_->start();
#if defined(EMSESP_DEBUG)
shell_->log_level(uuid::log::Level::DEBUG);
shell_->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when compiled with debug
#else
shell_->log_level(uuid::log::Level::TRACE);
#endif
}
void EMSESP::shell_prompt() {
#ifndef EMSESP_STANDALONE
serial_console_.println();
serial_console_.printf("EMS-ESP %s: press CTRL-D to activate this serial console", EMSESP_APP_VERSION);
serial_console_.println();
#endif
}
} // namespace emsesp

View File

@@ -36,20 +36,25 @@
#ifndef EMSESP_STANDALONE
#include <uuid/telnet.h>
#endif
#ifndef EMSESP_STANDALONE
#include "ESP32React/ESP32React.h"
#else
#include "../lib_standalone/ESP32React.h"
#endif
#include <Preferences.h>
#include "ESP8266React.h"
#include "web/WebStatusService.h"
#include "web/WebActivityService.h"
#include "web/WebDataService.h"
#include "web/WebSettingsService.h"
#include "web/WebCustomizationService.h"
#include "web/WebSchedulerService.h"
#include "web/WebAPIService.h"
#include "web/WebLogService.h"
#include "web/WebCustomEntityService.h"
#include "web/WebModulesService.h"
#include "../web/WebStatusService.h"
#include "../web/WebActivityService.h"
#include "../web/WebDataService.h"
#include "../web/WebSettingsService.h"
#include "../web/WebCustomizationService.h"
#include "../web/WebSchedulerService.h"
#include "../web/WebAPIService.h"
#include "../web/WebLogService.h"
#include "../web/WebCustomEntityService.h"
#include "../web/WebModulesService.h"
#include "emsdevicevalue.h"
#include "emsdevice.h"
@@ -65,7 +70,8 @@
#include "shower.h"
#include "roomcontrol.h"
#include "command.h"
#include "version.h"
#include "../version.h"
// Load external modules
class Module {}; // forward declaration
@@ -93,8 +99,8 @@ class EMSESP {
public:
EMSESP();
~EMSESP() = default;
virtual void start();
virtual void loop();
void start();
void loop();
static uuid::log::Logger logger();
@@ -216,8 +222,6 @@ class EMSESP {
static void scan_devices();
static void clear_all_devices();
static bool return_not_found(JsonObject output, const char * msg, const char * cmd);
static std::deque<std::unique_ptr<EMSdevice>> emsdevices;
// services
@@ -232,7 +236,7 @@ class EMSESP {
static Preferences nvs_;
// web controllers
static ESP8266React esp8266React;
static ESP32React esp32React;
static WebSettingsService webSettingsService;
static WebStatusService webStatusService;
static WebActivityService webActivityService;

View File

@@ -33,7 +33,7 @@ char * Helpers::hextoa(char * result, const uint8_t value) {
return result;
}
// same as above but to a hex string
// same as hextoa but uses to a hex std::string
std::string Helpers::hextoa(const uint8_t value, bool prefix) {
char buf[3];
if (prefix) {
@@ -98,11 +98,8 @@ char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) {
}
#endif
/**
* fast itoa returning a std::string
* http://www.strudel.org.uk/itoa/
*
*/
// fast itoa returning a std::string
// http://www.strudel.org.uk/itoa/
std::string Helpers::itoa(int16_t value) {
std::string buf;
buf.reserve(25); // Pre-allocate enough space.
@@ -254,13 +251,19 @@ char * Helpers::render_value(char * result, const double value, const int8_t for
char * ret = result;
double v = value < 0 ? value - 1.0 / (2 * p[format]) : value + 1.0 / (2 * p[format]);
auto whole = (int32_t)v;
auto whole = (long long)v;
if (whole == 0 && v < 0) {
if (whole <= 0 && v < 0) {
result[0] = '-';
result++;
whole = -whole;
v = -v;
}
itoa(whole, result, 10);
#ifndef EMSESP_STANDALONE
lltoa(whole, result, 10);
#else
ultostr(result, whole, 10);
#endif
while (*result != '\0') {
result++;
@@ -354,16 +357,16 @@ char * Helpers::render_value(char * result, const uint32_t value, const int8_t f
#ifndef EMSESP_STANDALONE
if (!format) {
strlcpy(result, ltoa(new_value, s, 10), sizeof(s)); // format is 0
strlcpy(result, lltoa(new_value, s, 10), sizeof(s)); // format is 0
} else if (format > 0) {
strlcpy(result, ltoa(new_value / format, s, 10), sizeof(s));
strlcpy(result, lltoa(new_value / format, s, 10), sizeof(s));
strlcat(result, ".", sizeof(s));
strlcat(result, itoa(((new_value % format) * 10) / format, s, 10), sizeof(s));
if (format == 100) {
strlcat(result, itoa(new_value % 10, s, 10), sizeof(s));
}
} else {
strlcpy(result, ltoa(new_value * format * -1, s, 10), sizeof(s));
strlcpy(result, lltoa(new_value * format * -1, s, 10), sizeof(s));
}
#else
if (!format) {
@@ -458,26 +461,22 @@ int Helpers::atoint(const char * value) {
// fahrenheit=0 - off, no conversion
// fahrenheit=1 - relative, 1.8t
// fahrenheit=2 - absolute, 1.8t + 32(fahrenheit-1)
float Helpers::transformNumFloat(float value, const int8_t numeric_operator, const uint8_t fahrenheit) {
float val;
double Helpers::transformNumFloat(double value, const int8_t numeric_operator, const uint8_t fahrenheit) {
double val;
if (numeric_operator == 0) { // DV_NUMOP_NONE
val = value * 100 + 0.5;
val = value * 100;
} else if (numeric_operator > 0) { // DV_NUMOP_DIVxx
val = value * 100 / numeric_operator + 0.5;
val = value * 100 / numeric_operator;
} else { // DV_NUMOP_MULxx
val = value * -100 * numeric_operator;
}
if (value < 0) { // negative rounding
val = val - 1;
}
if (fahrenheit) {
val = val * 1.8 + 3200 * (fahrenheit - 1);
}
return ((int32_t)val) / 100.0;
return (round(val)) / 100.0;
}
// abs of a signed 32-bit integer

View File

@@ -51,7 +51,7 @@ class Helpers {
static uint16_t string2minutes(const std::string & str);
static float numericoperator2scalefactor(int8_t numeric_operator);
static float transformNumFloat(float value, const int8_t numeric_operator, const uint8_t fahrenheit = 0);
static double transformNumFloat(double value, const int8_t numeric_operator = 0, const uint8_t fahrenheit = 0);
static std::string toLower(std::string const & s);
static std::string toUpper(std::string const & s);

View File

@@ -35,6 +35,7 @@ MAKE_WORD(show)
MAKE_WORD(su)
MAKE_WORD(scan)
MAKE_WORD(password)
MAKE_WORD(admin)
MAKE_WORD(read)
MAKE_WORD(values)
MAKE_WORD(system)
@@ -156,7 +157,7 @@ MAKE_WORD_CUSTOM(deviceid_mandatory, "<deviceID>")
MAKE_WORD_CUSTOM(device_type_optional, "[device]")
MAKE_WORD_CUSTOM(invalid_log_level, "Invalid log level")
MAKE_WORD_CUSTOM(log_level_optional, "[level]")
MAKE_WORD_CUSTOM(show_commands, "[system | users | devices | log | ems | values | mqtt | commands")
MAKE_WORD_CUSTOM(show_commands, "[system | users | devices | log | ems | values | mqtt | commands]")
MAKE_WORD_CUSTOM(name_mandatory, "<name>")
MAKE_WORD_CUSTOM(name_optional, "[name]")
MAKE_WORD_CUSTOM(new_password_prompt1, "Enter new password: ")
@@ -317,6 +318,7 @@ MAKE_ENUM(enum_wwMode5, FL_(normal), FL_(comfort), FL_(ecoplus)) // Rego3000
MAKE_ENUM(enum_wwMode6, FL_(off), FL_(comfort), FL_(auto)) // CR120
MAKE_ENUM(enum_heatingtype, FL_(off), FL_(radiator), FL_(convector), FL_(floor))
MAKE_ENUM(enum_heatingtype1, FL_(off), FL_(curve), FL_(radiator), FL_(convector), FL_(floor))
MAKE_ENUM(enum_heatingtype2, FL_(off), FL_(radiator), FL_(convector), FL_(floor), FL_(roomflow), FL_(roomload))
MAKE_ENUM(enum_summermode, FL_(summer), FL_(auto), FL_(winter))
MAKE_ENUM(enum_hpoperatingmode, FL_(off), FL_(auto), FL_(heating), FL_(cooling))
MAKE_ENUM(enum_summer, FL_(winter), FL_(summer))
@@ -363,7 +365,7 @@ MAKE_ENUM(enum_hybridStrategy1, FL_(cost_optimized), FL_(co2_optimized), FL_(out
MAKE_ENUM(enum_lowNoiseMode, FL_(off), FL_(reduced_output), FL_(switchoff), FL_(perm))
// heat pump
MAKE_ENUM(enum_hpactivity, FL_(none), FL_(heating), FL_(cooling), FL_(hot_water), FL_(pool), FL_(unknown), FL_(defrost))
MAKE_ENUM(enum_hpactivity, FL_(off), FL_(heating), FL_(cooling), FL_(hot_water), FL_(pool), FL_(pool_heating), FL_(defrost), FL_(compressor_alarm)) // BV name COMPRESSOR_E21_STATUS
MAKE_ENUM(enum_silentMode, FL_(off), FL_(auto), FL_(on))
MAKE_ENUM(enum_4way, FL_(cool_defrost), FL_(heat_ww))
@@ -392,4 +394,4 @@ MAKE_ENUM(enum_errorDisp, FL_(off), FL_(normal), FL_(inverted))
#pragma GCC diagnostic pop
// clang-format on
// clang-format on

View File

@@ -37,50 +37,51 @@
// if there is no translation, it will default to en
//
// device types, as display in Web and Console
MAKE_WORD_TRANSLATION(boiler_hp_device, "Boiler/HP", "Kessel/WP", "CV ketel/WP", "Värmepanna/VP", "Kocioł/PC", "Varmekjele/VP", "", "Kazan/IP", "Caldaia/PC", "Kotol/Tč", "Kotel/TČ") // TODO translate
MAKE_WORD_TRANSLATION(boiler_hp_device, "Boiler/HP", "Kessel/WP", "CV ketel/WP", "Värmepanna/VP", "Kocioł/PC", "Varmekjele/VP", "", "Kazan/IP", "Caldaia/PC", "Kotol/TČ", "Kotel/TČ") // TODO translate
MAKE_WORD_TRANSLATION(boiler_device, "Boiler", "Kessel", "CV ketel", "Värmepanna", "Kocioł", "Varmekjele", "", "Kazan", "Caldaia", "Kotol", "Kotel") // TODO translate
MAKE_WORD_TRANSLATION(thermostat_device, "Thermostat", "Thermostat", "Thermostaat", "Termostat", "Termostat", "Termostat", "", "Termostat", "Termostato", "Termostat", "Termostat") // TODO translate
MAKE_WORD_TRANSLATION(heatpump_device, "Heat Pump", "Wärmepumpe", "Warmtepomp", "Värmepump", "Pompa ciepła", "Varmepumpe", "", "Isı Pompası", "Pompa di Calore", "Tepelné čerpadlo", "Tepelné čerpadlo") // TODO translate
MAKE_WORD_TRANSLATION(solar_device, "Solar Module", "Solarmodul", "Solar Module", "Solmodul", "Moduł solarny", "Solmodul", "", "Güneş Enerjisi Cihazı", "Modulo Solare", "Solárny modul", "Solární modul") // TODO translate
MAKE_WORD_TRANSLATION(connect_device, "Connect Module", "Verbindungsmodul", "Connect Module", "Uppkopplingsmodul", "Moduł przyłączeń", "Sammenkoblingsmodul", "", "Güneş Enerjisi Cihazı", "Modulo connessione", "Pripojte modul", "Modul připojení") // TODO translate
MAKE_WORD_TRANSLATION(mixer_device, "Mixer Module", "Mischermodul", "Mixer Module", "Blandningsmodul", "Moduł mieszacza", "Miksermodul", "", "Karışım Cihazı", "Modulo Miscela", "Modul mixera", "Směšovací modul") // TODO translate
MAKE_WORD_TRANSLATION(mixer_device, "Mixer Module", "Mischermodul", "Mixer Module", "Blandarmodul", "Moduł mieszacza", "Miksermodul", "", "Karışım Cihazı", "Modulo Miscela", "Modul mixera", "Směšovací modul") // TODO translate
MAKE_WORD_TRANSLATION(controller_device, "Controller Module", "Regelmodul", "Controller Module", "Styrmodul", "Moduł sterujący", "Styremodul", "", "Kontrol Ünitesi", "Modulo Controllo", "Modul ovládača", "Řídicí modul") // TODO translate
MAKE_WORD_TRANSLATION(switch_device, "Switch Module", "Schaltmodul", "Switch Module", "Relämodul", "Moduł przełączający", "Switch modul", "", "Anahtar", "Modulo Switch", "Spínací modul", "Spínací modul") // TODO translate
MAKE_WORD_TRANSLATION(gateway_device, "Gateway Module", "Gateway-Modul", "Gateway Module", "Gateway", "Moduł IP", "Gateway", "", "Ağ Geçidi", "Modulo Gateway", "Modul brány", "Modul brány") // TODO translate
MAKE_WORD_TRANSLATION(alert_device, "Alert Module", "Alarmmodul", "Alert Module", "Larmmodul", "Moduł alarmowy", "Alarmmodul", "", "Alarm Cihazı", "Module Avviso", "Modul upozornení", "Modul upozornění") // TODO translate
//MAKE_WORD_TRANSLATION(pump_device, "Pump Module", "Pumpenmodul", "Pump Module", "Pumpmodul", "Moduł pompy", "Pumpemodul", "", "Pompa", "Module Pompa", "Modul čerpadla", "Modul čerpadla") // TODO translate
MAKE_WORD_TRANSLATION(extension_device, "Extension Module", "Erweiterungsnmodul", "Module", "Modul", "Moduł rozszerzeń", "Modul", "", "", "Module", "Rozširujúci modul", "Rozšiřující modul") // TODO translate
MAKE_WORD_TRANSLATION(extension_device, "Extension Module", "Erweiterungsnmodul", "Module", "Utökningsmodul", "Moduł rozszerzeń", "Modul", "", "", "Module", "Rozširujúci modul", "Rozšiřující modul") // TODO translate
MAKE_WORD_TRANSLATION(heatsource_device, "Heatsource", "Wärmequelle", "Heatsource", "Värmekälla", "Źródło ciepła", "Varmekilde", "", "Isı Kaynağı", "Fonte di calore", "Tepelný zdroj", "Zdroj tepla") // TODO translate
MAKE_WORD_TRANSLATION(sensors_device, "Sensors", "Sensoren", "Sensoren", "Sensorer", "Czujniki", "Sensorer", "Capteurs", "Sensör Cihazı", "Sensori", "Snímače", "Senzory")
MAKE_WORD_TRANSLATION(unknown_device, "Unknown", "Unbekannt", "Onbekend", "Okänt", "Nieznane urządzenie", "Ukjent", "Inconnu", "Bilinmeyen", "Sconosciuto", "Neznámy", "Neznámé")
MAKE_WORD_TRANSLATION(custom_device, "Custom", "Nutzerdefiniert", "Aangepast", "", "Niestandardowe", "", "", "Özel", "Personalizzato", "Vlastné", "Vlastní") // TODO translate
MAKE_WORD_TRANSLATION(custom_device_name, "Custom Entities", "Nutzer deklarierte Entitäten", "Gebruiker gedefineerd", "", "Encje zdefiniowane przez użytkownika", "", "", "Kullanıcı tarafından tanımlanmış varlıklar", "Entità definita da utente", "Používateľom definované entity", "Vlastní entity") // TODO translate
MAKE_WORD_TRANSLATION(ventilation_device, "Ventilation", "Belüftung", "Ventilatie", "", "Wentylacja", "", "", "Havalandırma", "Ventilazione", "Vetranie", "Větrání") // TODO translate
MAKE_WORD_TRANSLATION(water_device, "Water Module", "Wassermodul", "", "", "Moduł wodny", "", "", "", "", "Modul vody", "Modul vody") // TODO translate
MAKE_WORD_TRANSLATION(pool_device, "Pool Module", "Poolmodul", "", "", "Moduł basenu", "", "", "", "", "Modul bazéna", "Modul bazénu") // TODO translate
MAKE_WORD_TRANSLATION(custom_device, "Custom", "Nutzerdefiniert", "Aangepast", "Anpassad", "Niestandardowe", "", "", "Özel", "Personalizzato", "Vlastné", "Vlastní") // TODO translate
MAKE_WORD_TRANSLATION(custom_device_name, "Custom Entities", "Nutzer deklarierte Entitäten", "Gebruiker gedefineerd", "Egendefinierade entiteter", "Encje zdefiniowane przez użytkownika", "", "", "Kullanıcı tarafından tanımlanmış varlıklar", "Entità definita da utente", "Používateľom definované entity", "Vlastní entity") // TODO translate
MAKE_WORD_TRANSLATION(ventilation_device, "Ventilation", "Belüftung", "Ventilatie", "Ventilation", "Wentylacja", "", "", "Havalandırma", "Ventilazione", "Vetranie", "Větrání") // TODO translate
MAKE_WORD_TRANSLATION(water_device, "Water Module", "Wassermodul", "", "Vattenmodul", "Moduł wodny", "", "", "", "", "Modul vody", "Modul vody") // TODO translate
MAKE_WORD_TRANSLATION(pool_device, "Pool Module", "Poolmodul", "", "Poolmodul", "Moduł basenu", "", "", "", "", "Modul bazéna", "Modul bazénu") // TODO translate
// commands
MAKE_WORD_TRANSLATION(info_cmd, "list all values (verbose)", "Liste aller Werte", "lijst van alle waardes", "", "wyświetl wszystkie wartości", "Viser alle verdier", "", "Tüm değerleri listele", "elenca tutti i valori", "zobraziť všetky hodnoty", "vypsat všechny hodnoty (podrobně)") // TODO translate
MAKE_WORD_TRANSLATION(commands_cmd, "list all commands", "Liste aller Kommandos", "lijst van alle commando's", "", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi", "zobraziť všetky príkazy", "vypsat všechny příkazy") // TODO translate
MAKE_WORD_TRANSLATION(entities_cmd, "list all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity", "vypsat všechny entity") // TODO translate
MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram", "odeslat telegram") // TODO translate
MAKE_WORD_TRANSLATION(read_cmd, "send read request", "", "", "", "", "", "", "", "", "odoslať žiadosť o prečítanie", "") // TODO translate
MAKE_WORD_TRANSLATION(setiovalue_cmd, "set I/O value", "Setze Werte E/A", "instellen standaardwaarde", "", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io", "nastavit hodnotu I/O") // TODO translate
MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Protokollebene", "aanpassen log niveau", "", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir", "cambia livello registrazione", "zmeniť úroveň protokolu", "změnit úroveň protokolování") // TODO translate
MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Aktualisiere alle EMS-Werte", "Verversen alle EMS waardes", "", "odśwież wszystkie wartości EMS", "oppfrisk alle EMS verdier", "", "Bütün EMS değerlerini yenile", "aggiornare tutti i valori EMS", "obnoviť všetky hodnoty EMS", "aktualizovat všechny EMS hodnoty") // TODO translate
MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "opnieuw opstarten", "", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "redémarrer EMS-ESP", "EMS-ESPyi yeniden başlat", "riavvia EMS-ESP", "reštart EMS-ESP", "restartovat EMS-ESP") // TODO translate
MAKE_WORD_TRANSLATION(format_cmd, "factory reset EMS-ESP", "EMS-ESP auf Werkseinstellungen zurücksetzen", "", "", "", "", "", "", "", "továrenske nastavenie EMS-ESP", "tovární nastavení EMS-ESP") // TODO translate
MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Beobachte eingehende Telegramme", "inkomende telegrammen bekijken", "", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları", "guardare i telegrammi in arrivo", "sledovať prichádzajúce telegramy", "sledovat příchozí telegramy") // TODO translate
MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT", "publikovat vše do MQTT") // TODO translate
MAKE_WORD_TRANSLATION(system_info_cmd, "show system info", "Zeige Systeminformationen", "toon systeemstatus", "", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému", "zobrazit informace o systému") // TODO translate
MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplanelemente", "activeer tijdschema item", "", "aktywuj wybrany harmonogram", "", "", "program öğesini etkinleştir", "abilitare l'elemento programmato", "povoliť položku plánovania", "povolit položku plánování") // TODO translate
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS", "nastaviť vlastnú hodnotu na ems", "nastavit vlastní hodnotu na ems") // TODO translate
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď", "získat odpověď") // TODO translate
MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "Zugabe einer Menge kalten Wassers", "", "", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody", "poslat studenou vodu") // TODO translate
MAKE_WORD_TRANSLATION(message_cmd, "send a message", "Eine Nachricht senden", "", "", "", "", "", "", "", "poslať správu", "odeslat zprávu") // TODO translate
MAKE_WORD_TRANSLATION(values_cmd, "list all values", "Liste alle Werte auf", "", "", "", "", "", "", "", "vypísať všetky hodnoty", "vypsat všechny hodnoty") // TODO translate
MAKE_WORD_TRANSLATION(showertimer_cmd, "enable shower timer", "aktiviere Duschzeitmessung", "", "", "", "", "", "", "", "povoliť časovač sprchovania", "") // TODO translate
MAKE_WORD_TRANSLATION(showeralert_cmd, "enable shower alert", "aktiviere Duschzeitwarnung", "", "", "", "", "", "", "", "povoliť upozornenie na sprchu", "") // TODO translate
MAKE_WORD_TRANSLATION(info_cmd, "list all values (verbose)", "Liste aller Werte", "lijst van alle waardes", "lista alla värden", "wyświetl wszystkie wartości", "Viser alle verdier", "", "Tüm değerleri listele", "elenca tutti i valori", "zobraziť všetky hodnoty", "vypsat všechny hodnoty (podrobně)") // TODO translate
MAKE_WORD_TRANSLATION(commands_cmd, "list all commands", "Liste aller Kommandos", "lijst van alle commando's", "lista alla kommandon", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi", "zobraziť všetky príkazy", "vypsat všechny příkazy") // TODO translate
MAKE_WORD_TRANSLATION(entities_cmd, "list all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "lista all entiteter", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity", "vypsat všechny entity") // TODO translate
MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "skicka ett telegram", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram", "odeslat telegram") // TODO translate
MAKE_WORD_TRANSLATION(read_cmd, "send read request", "", "", "skicka en läsförfrågan", "", "", "", "", "", "odoslať žiadosť o prečítanie", "") // TODO translate
MAKE_WORD_TRANSLATION(setiovalue_cmd, "set I/O value", "Setze Werte E/A", "instellen standaardwaarde", "sätt ett I/O-värde", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io", "nastavit hodnotu I/O") // TODO translate
MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Protokollebene", "aanpassen log niveau", "ändra logg-nivå", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir", "cambia livello registrazione", "zmeniť úroveň protokolu", "změnit úroveň protokolování") // TODO translate
MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Aktualisiere alle EMS-Werte", "Verversen alle EMS waardes", "uppdatera alla EMS-värden", "odśwież wszystkie wartości EMS", "oppfrisk alle EMS verdier", "", "Bütün EMS değerlerini yenile", "aggiornare tutti i valori EMS", "obnoviť všetky hodnoty EMS", "aktualizovat všechny EMS hodnoty") // TODO translate
MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "opnieuw opstarten", "starta om EMS-ESP", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "redémarrer EMS-ESP", "EMS-ESPyi yeniden başlat", "riavvia EMS-ESP", "reštart EMS-ESP", "restartovat EMS-ESP") // TODO translate
MAKE_WORD_TRANSLATION(format_cmd, "factory reset EMS-ESP", "EMS-ESP auf Werkseinstellungen zurücksetzen", "fabriksåterställ EMS-ESP", "", "", "", "", "", "", "továrenske nastavenie EMS-ESP", "tovární nastavení EMS-ESP") // TODO translate
MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Beobachte eingehende Telegramme", "inkomende telegrammen bekijken", "visa inkommande telegram", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları", "guardare i telegrammi in arrivo", "sledovať prichádzajúce telegramy", "sledovat příchozí telegramy") // TODO translate
MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "publicera allt till MQTT", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT", "publikovat vše do MQTT") // TODO translate
MAKE_WORD_TRANSLATION(system_info_cmd, "show system info", "Zeige Systeminformationen", "toon systeemstatus", "visa systeminformation", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému", "zobrazit informace o systému") // TODO translate
MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplanelemente", "activeer tijdschema item", "aktivera schemalagt objekt", "aktywuj wybrany harmonogram", "", "", "program öğesini etkinleştir", "abilitare l'elemento programmato", "povoliť položku plánovania", "povolit položku plánování") // TODO translate
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "sätt ett eget värde i EMS", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS", "nastaviť vlastnú hodnotu na ems", "nastavit vlastní hodnotu na ems") // TODO translate
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "hämta svar", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď", "získat odpověď") // TODO translate
MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "Zugabe einer Menge kalten Wassers", "", "sckicka en liten mängd kallvatten", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody", "poslat studenou vodu") // TODO translate
MAKE_WORD_TRANSLATION(message_cmd, "send a message", "Eine Nachricht senden", "", "skicka ett meddelande", "", "", "", "", "", "poslať správu", "odeslat zprávu") // TODO translate
MAKE_WORD_TRANSLATION(values_cmd, "list all values", "Liste alle Werte auf", "", "lista alla värden", "", "", "", "", "", "vypísať všetky hodnoty", "vypsat všechny hodnoty") // TODO translate
MAKE_WORD_TRANSLATION(system_cmd, "system setting", "System Einstellung", "", "systeminställning", "", "", "", "", "", "vypísať všetky hodnoty", "vypsat všechny hodnoty") // TODO translate
MAKE_WORD_TRANSLATION(showertimer_cmd, "enable shower timer", "aktiviere Duschzeitmessung", "", "aktivera duschtimer", "", "", "", "", "", "povoliť časovač sprchovania", "") // TODO translate
MAKE_WORD_TRANSLATION(showeralert_cmd, "enable shower alert", "aktiviere Duschzeitwarnung", "", "aktivera duschvarning", "", "", "", "", "", "povoliť upozornenie na sprchu", "") // TODO translate
// tags
MAKE_WORD_TRANSLATION(tag_hc1, "hc1", "HK1", "hc1", "VK1", "OG1", "hc1", "hc1", "ID1", "hc1", "hc1", "hc1")
@@ -88,7 +89,7 @@ MAKE_WORD_TRANSLATION(tag_hc2, "hc2", "HK2", "hc2", "VK2", "OG2", "hc2", "hc2",
MAKE_WORD_TRANSLATION(tag_hc3, "hc3", "HK3", "hc3", "VK3", "OG3", "hc3", "hc3", "ID3", "hc3", "hc3", "hc3")
MAKE_WORD_TRANSLATION(tag_hc4, "hc4", "HK4", "hc4", "VK4", "OG4", "hc4", "hc4", "ID4", "hc4", "hc4", "hc4")
MAKE_WORD_TRANSLATION(tag_hc5, "hc5", "HK5", "hc5", "VK5", "OG5", "hc5", "hc5", "ID5", "hc5", "hc5", "hc5")
MAKE_WORD_TRANSLATION(tag_hc6, "hc6", "HK6", "hc6", "vk6", "OG6", "hc6", "hc6", "ID6", "hc6", "hc6", "hc6")
MAKE_WORD_TRANSLATION(tag_hc6, "hc6", "HK6", "hc6", "VK6", "OG6", "hc6", "hc6", "ID6", "hc6", "hc6", "hc6")
MAKE_WORD_TRANSLATION(tag_hc7, "hc7", "HK7", "hc7", "VK7", "OG7", "hc7", "hc7", "ID7", "hc7", "hc7", "hc7")
MAKE_WORD_TRANSLATION(tag_hc8, "hc8", "HK8", "hc8", "VK8", "OG8", "hc8", "hc8", "ID8", "hc8", "hc8", "hc8")
MAKE_WORD_TRANSLATION(tag_dhw1, "dhw", "WWK", "dhw", "VVK", "CWU", "dhw", "ecs", "SKS", "dhw", "TÚV", "TUV")
@@ -120,6 +121,11 @@ MAKE_WORD_TRANSLATION(tag_hs14, "hs14", "hs14", "hs14", "VK14", "ŹC14", "hs14",
MAKE_WORD_TRANSLATION(tag_hs15, "hs15", "hs15", "hs15", "VK15", "ŹC15", "hs15", "hs15", "hs15", "hs15", "hs15", "hs15")
MAKE_WORD_TRANSLATION(tag_hs16, "hs16", "hs16", "hs16", "VK16", "ŹC16", "hs16", "hs16", "hs16", "hs16", "hs16", "hs16")
MAKE_WORD_TRANSLATION(tag_hcx, "hc<n>", "HK<n>", "hc<n>", "VK<n>", "OG<n>", "hc<n>", "hc<n>", "ID<n>", "hc<n>", "hc<n>", "hc<n>")
MAKE_WORD_TRANSLATION(tag_dhwx, "dhw[n]", "WWK[n]", "dhw[n]", "VVK[n]", "CWU[n]", "dhw[n]", "ecs[n]", "SKS[n]", "dhw[n]", "TÚV[n]", "TUV[n]")
MAKE_WORD_TRANSLATION(tag_ahsx, "ahs<n>", "AHQ<n>", "ahs<n>", "AVK<n>", "AŹC<n>", "ahs<n>", "ahs<n>", "ahs<n>", "ahs<n>", "ahs<n>", "ahs<n>")
MAKE_WORD_TRANSLATION(tag_hsx, "hs<n>", "HQ<n>", "hs<n>", "VK<n>", "ŹC<n>", "hs<n>", "hs<n>", "hs<n>", "hs<n>", "hs<n>", "hs<n>")
// General
MAKE_WORD_TRANSLATION(on, "on", "an", "aan", "", "włączono", "", "on", "ık", "on", "zap", "zap")
MAKE_WORD_TRANSLATION(off, "off", "aus", "uit", "av", "wyłączono", "av", "off", "kapalı", "off", "vyp", "vyp")
@@ -169,10 +175,10 @@ MAKE_WORD_TRANSLATION(extern, "extern", "extern", "extern", "extern", "zewnętrz
MAKE_WORD_TRANSLATION(intern, "intern", "intern", "intern", "intern", "wewnętrzny", "intern", "interne", "", "interno", "interný", "interní")
MAKE_WORD_TRANSLATION(lower, "lower", "niedriger", "lager", "lägre", "mniejszy", "nedre", "inférieur", "daha düşük", "basso", "nízky", "nízký")
MAKE_WORD_TRANSLATION(error, "error", "Fehler", "error", "Fel", "błąd", "feil", "erreur", "Hata", "errore", "error", "chyba")
MAKE_WORD_TRANSLATION(history, "history", "Fehlerspeicher", "", "", "historia", "", "", "", "", "história", "historie") // TODO translate
MAKE_WORD_TRANSLATION(message, "message", "Meldung", "melding", "meddelande", "komunikat", "melding", "message", "mesajı", "messaggio", "správa", "zpráva") // TODO translate
MAKE_WORD_TRANSLATION(history, "history", "Fehlerspeicher", "", "historik", "historia", "", "", "", "", "história", "historie") // TODO translate
MAKE_WORD_TRANSLATION(message, "message", "Meldung", "melding", "meddelande", "komunikat", "melding", "message", "mesajı", "messaggio", "správa", "zpráva")
MAKE_WORD_TRANSLATION(na, "n/a", "n/a", "n/a", "n/a", "nd.", "n/a", "n/c", "mevcut değil", "n/a", "n/a", "n/a")
MAKE_WORD_TRANSLATION(inverted, "inverted", "invertiert", "", "", "odwrócony", "", "", "", "", "invertovaný", "invertovaný") // TODO translate
MAKE_WORD_TRANSLATION(inverted, "inverted", "invertiert", "", "inverterad", "odwrócony", "", "", "", "", "invertovaný", "invertovaný") // TODO translate
// boiler
MAKE_WORD_TRANSLATION(time, "time", "Zeit", "tijd", "Tid", "godzina", "tid", "heure", "zaman", "ora", "čas", "čas")
@@ -193,16 +199,16 @@ MAKE_WORD_TRANSLATION(layeredbuffer, "layered buffer", "Schichtspeicher", "gelaa
MAKE_WORD_TRANSLATION(maintenance, "maintenance", "Wartung", "onderhoud", "Underhåll", "przegląd", "vedlikehold", "maintenance", "bakım", "servizio", "údržba", "údržba")
MAKE_WORD_TRANSLATION(heating, "heating", "Heizen", "verwarmen", "Uppvärmning", "ogrzewanie", "oppvarming", "chauffage", "ısıtma", "riscaldamento", "kúrenie", "topení")
MAKE_WORD_TRANSLATION(cooling, "cooling", "Kühlen", "koelen", "Kyler", "chłodzenie", "kjøling", "refroidissement", "soğuma", "raffreddamento", "chladenie", "chlazení")
MAKE_WORD_TRANSLATION(heatandcool, "heating & cooling", "Heizen & Kühlen", "verwarmen & koelen", "Uppvärmning & Kyler", "ogrzewanie i chłodzenie", "", "", "ısıtma & soğutma", "", "kúrenie a chladenie", "topení a chlazení") // TODO translate
MAKE_WORD_TRANSLATION(heatandcool, "heating & cooling", "Heizen & Kühlen", "verwarmen & koelen", "Uppvärmning & Kyla", "ogrzewanie i chłodzenie", "", "", "ısıtma & soğutma", "", "kúrenie a chladenie", "topení a chlazení") // TODO translate
MAKE_WORD_TRANSLATION(disinfecting, "disinfecting", "Desinfizieren", "desinfecteren", "Desinficerar", "dezynfekcja termiczna", "desinfisering", "désinfection", "dezenfeksiyon", "disinfezione", "dezinfekcia", "dezinfekce")
MAKE_WORD_TRANSLATION(no_heat, "no heat", "keine Wärme", "geen warmte", "Ingen värme", "brak ciepła", "ingen varme", "pas de chauffage", "ısınma yok", "nessun calore", "žiadne teplo", "bez tepla")
MAKE_WORD_TRANSLATION(heatrequest, "heat request", "Wärmeanforderung", "verwarmingsverzoek", "Värmeförfrågan", "zapotrzebowanie na ciepło", "varmeforespørsel", "demande de chauffage", "ısınma ihtiyacı", "richiesta calore", "požiadavka na teplo", "požadavek na teplo")
MAKE_WORD_TRANSLATION(valve, "valve", "Ventil", "klep", "Ventil", "zawór", "ventil", "valve", "vana", "valvola", "ventil", "ventil")
MAKE_WORD_TRANSLATION(proportional, "proportional", "proportional", "proportioneel", "", "proporcjonalny", "proposjonal", "", "oransal", "proporzionale", "proporcionálne", "proporcionální") // TODO translate
MAKE_WORD_TRANSLATION(deltaP1, "deltaP-1", "deltaP-1", "deltaP-1", "", "delta P-1", "deltaP-1", "", "deltaP-1", "deltaP-1", "deltaP-1", "deltaP-1") // TODO translate
MAKE_WORD_TRANSLATION(deltaP2, "deltaP-2", "deltaP-2", "deltaP-2", "", "delta P-2", "deltaP-2", "", "deltaP-2", "deltaP-2", "deltaP-2", "deltaP-2") // TODO translate
MAKE_WORD_TRANSLATION(deltaP3, "deltaP-3", "deltaP-3", "deltaP-3", "", "delta P-3", "deltaP-3", "", "deltaP-3", "deltaP-3", "deltaP-3", "deltaP-3") // TODO translate
MAKE_WORD_TRANSLATION(deltaP4, "deltaP-4", "deltaP-4", "deltaP-4", "", "delta P-4", "deltaP-4", "", "deltaP-4", "deltaP-4", "deltaP-4", "deltaP-4") // TODO translate
MAKE_WORD_TRANSLATION(proportional, "proportional", "proportional", "proportioneel", "proportionell", "proporcjonalny", "proposjonal", "", "oransal", "proporzionale", "proporcionálne", "proporcionální") // TODO translate
MAKE_WORD_TRANSLATION(deltaP1, "deltaP-1", "deltaP-1", "deltaP-1", "deltaP-1", "delta P-1", "deltaP-1", "", "deltaP-1", "deltaP-1", "deltaP-1", "deltaP-1") // TODO translate
MAKE_WORD_TRANSLATION(deltaP2, "deltaP-2", "deltaP-2", "deltaP-2", "deltaP-2", "delta P-2", "deltaP-2", "", "deltaP-2", "deltaP-2", "deltaP-2", "deltaP-2") // TODO translate
MAKE_WORD_TRANSLATION(deltaP3, "deltaP-3", "deltaP-3", "deltaP-3", "deltaP-3", "delta P-3", "deltaP-3", "", "deltaP-3", "deltaP-3", "deltaP-3", "deltaP-3") // TODO translate
MAKE_WORD_TRANSLATION(deltaP4, "deltaP-4", "deltaP-4", "deltaP-4", "deltaP-4", "delta P-4", "deltaP-4", "", "deltaP-4", "deltaP-4", "deltaP-4", "deltaP-4") // TODO translate
MAKE_WORD_TRANSLATION(pressure1, "150mbar", "150mbar", "150mbar", "150mbar", "150mbar", "150mbar", "150mbar", "150mbar", "150mbar", "150mbar", "150mbar") // TODO translate
MAKE_WORD_TRANSLATION(pressure2, "200mbar", "200mbar", "200mbar", "200mbar", "200mbar", "200mbar", "200mbar", "200mbar", "200mbar", "200mbar", "200mbar") // TODO translate
MAKE_WORD_TRANSLATION(pressure3, "250mbar", "250mbar", "250mbar", "250mbar", "250mbar", "250mbar", "250mbar", "250mbar", "250mbar", "250mbar", "250mbar") // TODO translate
@@ -214,6 +220,7 @@ MAKE_WORD_TRANSLATION(pressure6, "400mbar", "400mbar", "400mbar", "400mbar", "40
MAKE_WORD_TRANSLATION(none, "none", "keine", "geen", "ingen", "brak", "ingen", "aucun", "hiçbiri", "nessuno", "žiadny", "žádné")
MAKE_WORD_TRANSLATION(hot_water, "hot water", "Warmwasser", "warm water", "varmvatten", "c.w.u.", "varmtvann", "eau chaude", "sıcak su", "acqua calda", "horúca voda", "horká voda")
MAKE_WORD_TRANSLATION(pool, "pool", "Pool", "zwembad", "pool", "basen", "basseng", "piscine", "havuz", "piscina", "bazén", "bazén")
MAKE_WORD_TRANSLATION(pool_heating, "pool heating", "Pool beheizen", "", "", "", "", "", "", "", "ohrev bazéna", "")
MAKE_WORD_TRANSLATION(outside_temp_alt, "outside temperature alt.", "Außentemp. alternativ", "alternatieve buitentemperatuur", "Alternativ utomhustemp.", "temp. zewn. alternat.", "alternativ utendørstemp.", "température extérieure alternative", "alternatif dış sıcaklık", "temperatura esterna alternativa", "vonkajšia teplota altern.", "venkovní teplota alt.")
MAKE_WORD_TRANSLATION(outside_temp_par, "outside temperature parallel", "Außentemp. parallel", "buitentemperatuur parallel", "Parallell utomhustemp.", "temp. zewn. równoległa", "parallell utendørstemp.", "température extérieure parallèle", "paralel dış sıcaklık", "temperatura esterna parallela", "paralelne s vonkajšou teplotou", "paralelně s venkovní teplotou")
MAKE_WORD_TRANSLATION(hp_preferred, "heatpump preferred", "Wärmepumpe bevorzugt", "voorkeur warmtepomp", "Värmepump föredraget", "preferowana pompa ciepła", "varmepumpe prioritert", "pompe à chaleur préférée", "tercih edilen pompa", "pompa di calore preferita", "preferované tepelné čerpadlo", "preferované tepelné čerpadlo")
@@ -221,8 +228,9 @@ MAKE_WORD_TRANSLATION(boiler_only, "boiler only", "nur Kessel", "uitsluitend cv
MAKE_WORD_TRANSLATION(reduced_output, "reduced output", "Reduzierte Leistung", "gereduceerde output", "Reducerad produktion", "zmniejszona wydajność", "redusert ytelse", "sortie réduite", "düşürülmüş çıkış", "riduzione uscita", "znížený výkon", "snížený výkon")
MAKE_WORD_TRANSLATION(switchoff, "switch off hp", "WP ausschalten", "WP uitschakelen", "Värmepump avstängd", "wyłącz pompę ciepła", "slå av varmepumpe", "éteindre la PAC", "ısı pompasını kapat", "spegnimento pompa calore", "vypnúť tep. čerpadlo", "vypnout tepelné čerpadlo")
MAKE_WORD_TRANSLATION(perm, "perm. reduced", "perm. reduziert", "permanent gereduceerd", "Permanent reducerad", "stale zmniejszona wydajność", "permanent redusert", "réduction permanente", "sürekli azaltılmış", "riduzione permanente", "trvalo znížené", "trvale sníženo")
MAKE_WORD_TRANSLATION(heat_ww, "heating & dhw", "Heizen & Warmwasser", "", "", "ogrzewanie i c.w.u.", "", "", "", "", "kúrenie a TÚV", "topení a TUV")
MAKE_WORD_TRANSLATION(cool_defrost, "cooling & defrost", "Kühlen & Abtauen", "", "", "chłodzenie i odladzanie", "", "", "", "", "chladenie a rozmrazovanie", "chlazení a odmrazování")
MAKE_WORD_TRANSLATION(heat_ww, "heating & dhw", "Heizen & Warmwasser", "", "Uppvärmning & Varmvatten", "ogrzewanie i c.w.u.", "", "", "", "", "kúrenie a TÚV", "topení a TUV")
MAKE_WORD_TRANSLATION(cool_defrost, "cooling & defrost", "Kühlen & Abtauen", "", "Kyla & Avfrostning", "chłodzenie i odladzanie", "", "", "", "", "chladenie a rozmrazovanie", "chlazení a odmrazování")
MAKE_WORD_TRANSLATION(compressor_alarm, "compressor alarm", "Alarm Kompressor", "", "", "", "", "", "", "", "alarm kompresora", "")
// thermostat
MAKE_WORD_TRANSLATION(seltemp, "selTemp", "Solltemperatur", "doeltemperatuur", "Börtemperatur", "temperatura zadana", "innstilt temperatur", "consigne température", "ayarlanmış sıcaklık", "temperatura di consegna", "zadaná teplota", "zvolená teplota")
@@ -243,10 +251,12 @@ MAKE_WORD_TRANSLATION(french, "french", "Französisch", "Frans", "Franska", "fra
MAKE_WORD_TRANSLATION(italian, "italian", "Italienisch", "Italiaans", "Italienska", "włoski", "italiensk", "italien", "İtalyanca", "Italiano", "taliansky", "italština")
MAKE_WORD_TRANSLATION(high, "high", "hoch", "hoog", "Hög", "wysoki", "høy", "haut", "yüksek", "alto", "vysoký", "vysoký")
MAKE_WORD_TRANSLATION(low, "low", "niedrig", "laag", "Låg", "niski", "lav", "bas", "düşük", "basso", "nízky", "nízký")
MAKE_WORD_TRANSLATION(curve, "heatingcurve", "Heizkurve", "", "", "krzywa grzania", "", "", "", "", "vykurovacia krivka", "křivka topení") // TODO translate
MAKE_WORD_TRANSLATION(curve, "heatingcurve", "Heizkurve", "", "värmekurva", "krzywa grzania", "", "", "", "", "vykurovacia krivka", "křivka topení") // TODO translate
MAKE_WORD_TRANSLATION(radiator, "radiator", "Heizkörper", "radiator", "Radiator", "grzejniki", "radiator", "radiateur", "radyatör", "radiatore", "radiátor", "radiátor")
MAKE_WORD_TRANSLATION(convector, "convector", "Konvektor", "convector", "Konvektor", "konwektory", "konvektor", "convecteur", "convector", "convettore", "konvektor", "konvektor")
MAKE_WORD_TRANSLATION(floor, "floor", "Fussboden", "vloer", "Golv", "podłoga", "gulv", "sol", "yer", "pavimento", "podlaha", "podlaha")
MAKE_WORD_TRANSLATION(roomflow, "roomflow", "Raum Fluß", "", "Rumsflöde", "", "", "", "", "", "prúdenie miestnosti", "") // TODO translate
MAKE_WORD_TRANSLATION(roomload, "roomload", "Raum Bedarf", "", "Rumsbehov", "", "", "", "", "", "izbová zaťaž", "") // TODO translate
MAKE_WORD_TRANSLATION(summer, "summer", "Sommer", "zomer", "Sommar", "lato", "sommer", "été", "yaz", "estate", "leto", "léto")
MAKE_WORD_TRANSLATION(winter, "winter", "Winter", "winter", "Vinter", "zima", "vinter", "hiver", "kış", "inverno", "zima", "zima")
MAKE_WORD_TRANSLATION(outdoor, "outdoor", "Außen", "buiten", "Utomhus", "temp. zewnętrzna", "utendørs", "extérieur", "dış", "esterno", "vonku", "venkovní")
@@ -264,7 +274,7 @@ MAKE_WORD_TRANSLATION(day, "day", "Tag", "dag", "Dag", "dzień", "dag", "jour",
MAKE_WORD_TRANSLATION(holiday, "holiday", "Urlaub", "vakantie", "Helgdag", "urlop", "ferie", "vacances", "tatil", "vacanza", "dovolenka", "dovolená")
MAKE_WORD_TRANSLATION(reduce, "reduce", "reduziert", "gereduceerd", "Reducera", "zredukowany", "redusere", "réduit", "düşür", "riduzione", "znížený", "snížený")
MAKE_WORD_TRANSLATION(noreduce, "no reduce", "unreduziert", "niet gereduceerd", "oreducerad", "bez redukcji", "ingen reduksjon", "pas de réduction", "düşürme", "non ridurre", "bez zníženia", "nesnížený")
MAKE_WORD_TRANSLATION(offset, "offset", "Anhebung", "offset", "Förskutning", "przesunięcie", "kompensasjon", "offset", "kompansasyon", "offset", "ofset", "offset")
MAKE_WORD_TRANSLATION(offset, "offset", "Anhebung", "offset", "Förskjutning", "przesunięcie", "kompensasjon", "offset", "kompansasyon", "offset", "ofset", "offset")
MAKE_WORD_TRANSLATION(design, "design", "Auslegung", "ontwerp", "Design", "projekt", "design", "design", "tasarım", "disegno", "design", "design")
MAKE_WORD_TRANSLATION(minflow, "min flow", "min. Durchfluss", "min. doorstroom", "Min. flöde", "minimalny przepływ", "min. strømming", "flux min.", "minimum akış", "flusso minimo", "min. prietok", "minimální průtok")
MAKE_WORD_TRANSLATION(maxflow, "max flow", "max. Durchfluss", "max. doorstroom", "Max. flöde", "maksymalny przepływ", "maks. strømming", "flux max.", "maksimum akış", "flusso massimo", "max. prietok", "maximální průtok")
@@ -279,11 +289,11 @@ MAKE_WORD_TRANSLATION(smoke_temperature, "smoke temperature", "Abgastemperatur",
MAKE_WORD_TRANSLATION(weather_compensated, "weather compensated", "Wetter kompensiert", "weer gecompenseerd", "Väderkompenserad", "skompensow. pogodą", "værkompensert", "compensation par l'extérieur", "hava durumuna göre dengelenmiş", "acqua compensata", "kompenzácia počasia", "dle venkovní teploty")
MAKE_WORD_TRANSLATION(outside_basepoint, "outside basepoint", "Basispunkt Außentemp.", "buiten basispunt", "Utomhus baspunkt", "temp. zewn. z pkt. pocz.", "utendørs basispunkt", "point de base temp. ext.", "dış hava sıcaklığı taban noktası", "basepoint esterno", "vonkajší základný bod", "venkovní teplota s patním bodem")
MAKE_WORD_TRANSLATION(functioning_mode, "functioning mode", "Funktionsweise", "functiemodus", "Driftläge", "tryb pracy", "driftsmodus", "mode de fonctionnement", "işletme konumu", "modalità di funzionamento", "funkčný režim", "provozní režim")
MAKE_WORD_TRANSLATION(unmixed, "unmixed", "ungemischt", "", "", "niezmieszany", "", "", "", "", "nezmiešaný", "nemíchaný") // TODO translate
MAKE_WORD_TRANSLATION(unmixedIPM, "unmixed IPM", "ungemischt IPM", "", "", "niezmieszany IPM", "", "", "", "", "nezmiešaný IPM", "nemíchaný IPM") // TODO translate
MAKE_WORD_TRANSLATION(mixed, "mixed IPM", "gemischt IPM", "", "", "zmieszany IPM", "", "", "", "", "zmiešaný IPM", "míchaný IPM") // TODO translate
MAKE_WORD_TRANSLATION(level, "level", "Level", "", "", "", "", "", "", "", "úroveň", "úroveň") // TODO translate
MAKE_WORD_TRANSLATION(absolute, "absolute", "Absolut", "", "", "", "", "", "", "", "absolútny", "absolutní") // TODO translate
MAKE_WORD_TRANSLATION(unmixed, "unmixed", "ungemischt", "", "oshuntad", "niezmieszany", "", "", "", "", "nezmiešaný", "nemíchaný") // TODO translate
MAKE_WORD_TRANSLATION(unmixedIPM, "unmixed IPM", "ungemischt IPM", "", "oshuntad IPM", "niezmieszany IPM", "", "", "", "", "nezmiešaný IPM", "nemíchaný IPM") // TODO translate
MAKE_WORD_TRANSLATION(mixed, "mixed IPM", "gemischt IPM", "", "shuntad IPM", "zmieszany IPM", "", "", "", "", "zmiešaný IPM", "míchaný IPM") // TODO translate
MAKE_WORD_TRANSLATION(level, "level", "Level", "", "nivå", "", "", "", "", "", "úroveň", "úroveň") // TODO translate
MAKE_WORD_TRANSLATION(absolute, "absolute", "Absolut", "", "absolut", "", "", "", "", "", "absolútny", "absolutní") // TODO translate
// mixer
MAKE_WORD_TRANSLATION(stopped, "stopped", "gestoppt", "gestopt", "stoppad", "zatrzymany", "stoppet", "arrêté", "durdu", "fermato", "zastavený", "zastavené")
@@ -297,19 +307,19 @@ MAKE_WORD_TRANSLATION(cyl1, "cyl 1", "Zyl_1", "Cil 1", "Cyl 1", "cyl 1", "cyl 1"
MAKE_WORD_TRANSLATION(cyl2, "cyl 2", "Zyl_2", "Cil 2", "Cyl 2", "cyl 2", "cyl 2", "cyl 2", "cly 1", "Cil 2", "cyl 2", "")
// ventilation
MAKE_WORD_TRANSLATION(demand, "demand", "Bedarf", "vereist", "", "zapotrzebowanie", "", "", "talep", "richiesta", "požiadavka", "") // TODO translate
MAKE_WORD_TRANSLATION(intense, "intense", "Intensiv", "intensief", "", "intensywne", "", "", "yoğun", "intensivo", "intenzívne", "") // TODO translate
MAKE_WORD_TRANSLATION(sleep, "sleep", "Einschlafen", "slaapmodus", "", "sen", "", "", "uyku", "notturno", "spiace", "") // TODO translate
MAKE_WORD_TRANSLATION(partymode, "party", "Party", "party", "", "impreza", "", "", "parti", "festa", "párty režim", "") // TODO translate
MAKE_WORD_TRANSLATION(fireplace, "fireplace", "Kamin", "haard", "", "kominek", "", "", "şömine", "camino", "krb", "") // TODO translate
MAKE_WORD_TRANSLATION(demand, "demand", "Bedarf", "vereist", "behov", "zapotrzebowanie", "", "", "talep", "richiesta", "požiadavka", "") // TODO translate
MAKE_WORD_TRANSLATION(intense, "intense", "Intensiv", "intensief", "intensiv", "intensywne", "", "", "yoğun", "intensivo", "intenzívne", "") // TODO translate
MAKE_WORD_TRANSLATION(sleep, "sleep", "Einschlafen", "slaapmodus", "sova", "sen", "", "", "uyku", "notturno", "spiace", "") // TODO translate
MAKE_WORD_TRANSLATION(partymode, "party", "Party", "party", "party", "impreza", "", "", "parti", "festa", "párty režim", "") // TODO translate
MAKE_WORD_TRANSLATION(fireplace, "fireplace", "Kamin", "haard", "Kamin", "kominek", "", "", "şömine", "camino", "krb", "") // TODO translate
// MQTT Discovery - this is special device entity for 'climate'
MAKE_TRANSLATION(haclimate, "haclimate", "mqtt discovery current room temperature", "Discovery aktuelle Raumtemperatur", "Discovery huidige kamertemperatuur", "", "termostat w HA", "HA Avlest temp", "", "Güncel osa sıcaklığı", "verifica temperatura ambiente attuale", "mqtt discovery aktuálna teplota v miestnosti", "mqtt discovery aktuální pokojová teplota") // TODO translate
MAKE_TRANSLATION(haclimate, "haclimate", "mqtt discovery current room temperature", "Discovery aktuelle Raumtemperatur", "Discovery huidige kamertemperatuur", "MQTT Discovery för aktuell rumstemperatur", "termostat w HA", "HA Avlest temp", "", "Güncel osa sıcaklığı", "verifica temperatura ambiente attuale", "mqtt discovery aktuálna teplota v miestnosti", "mqtt discovery aktuální pokojová teplota") // TODO translate
// Entity translations: tag, mqtt, en, de, nl, sv, pl, no, fr, tr, it, sk, cz
// Boiler
MAKE_TRANSLATION(reset, "reset", "reset", "Reset", "Reset", "Nollställ", "kasowanie komunikatu", "nullstill", "reset", "Sıfırla", "Reset", "reset", "reset")
MAKE_TRANSLATION(forceHeatingOff, "heatingoff", "force heating off", "Heizen abschalten", "", "", "wymuś wyłączenie grzania", "", "", "", "", "vynútené vypnutie kúrenia", "vynutit vypnutí vytápění") // TODO translate
MAKE_TRANSLATION(reset, "reset", "reset", "Reset", "Reset", "Återställ", "kasowanie komunikatu", "nullstill", "reset", "Sıfırla", "Reset", "reset", "reset")
MAKE_TRANSLATION(forceHeatingOff, "heatingoff", "force heating off", "Heizen abschalten", "", "Uppvärmning avstängd", "wymuś wyłączenie grzania", "", "", "", "", "vynútené vypnutie kúrenia", "vynutit vypnutí vytápění") // TODO translate
MAKE_TRANSLATION(wwtapactivated, "tapactivated", "turn on/off", "Durchlauferhitzer aktiv", "zet aan/uit", "på/av", "system przygotowywania", "Varmtvann active", "ecs activée", "aç/kapa", "commuta on/off", "zapnúť/vypnúť", "zapnout/vypnout")
MAKE_TRANSLATION(oilPreHeat, "oilpreheat", "oil preheating", "Ölvorwärmung", "Olie voorverwarming", "Förvärmning olja", "podgrzewanie oleju", "oljeforvarming", "préchauffage de l'huile", "Yakıt Ön ısıtma devrede", "preriscaldamento olio", "predohrev oleja", "předehřev oleje")
MAKE_TRANSLATION(heatingActive, "heatingactive", "heating active", "Heizen aktiv", "Verwarming actief", "Uppvärmning aktiv", "c.o. aktywne", "oppvarming aktiv", "chauffage actif", "ısıtma devrede", "riscaldamento attivo", "vykurovanie aktívne", "Vytápění aktivní")
@@ -328,7 +338,7 @@ MAKE_TRANSLATION(boilTemp, "boiltemp", "actual boiler temperature", "Kesseltempe
MAKE_TRANSLATION(exhaustTemp, "exhausttemp", "exhaust temperature", "Abgastemperatur", "Uitlaattemperatuur", "Avgastemperatur", "temperatura spalin", "røykgasstemp", "température des gaz d'échappement", "baca sıcaklığı", "temperatura di scarico", "teplota výfukových plynov", "teplota spalin")
MAKE_TRANSLATION(burnGas, "burngas", "gas", "Gas", "Gas", "Gas", "gaz", "gass", "gaz", "gaz", "Gas", "plyn", "plyn")
MAKE_TRANSLATION(burnGas2, "burngas2", "gas stage 2", "Gas Stufe 2", "gas fase 2", "Gas Fas 2", "gaz 2 stopień", "gass fase 2", "gaz état 2", "gaz 2.seviye", "gas fase 2", "plynový stupeň 2", "plyn stupeň 2")
MAKE_TRANSLATION(flameCurr, "flamecurr", "flame current", "Flammenstrom", "Vlammenstroom", "Lågström", "prąd palnika", "flammetemp", "courrant de flamme", "alev akımı", "corrente di fiamma", "prúd plameňa", "proud plamene") // TODO translate
MAKE_TRANSLATION(flameCurr, "flamecurr", "flame current", "Flammenstrom", "Vlammenstroom", "Lågström", "prąd palnika", "flammetemp", "courrant de flamme", "alev akımı", "corrente di fiamma", "prúd plameňa", "proud plamene")
MAKE_TRANSLATION(heatingPump, "heatingpump", "heating pump", "Heizungspumpe", "Verwarmingspomp", "Värmepump", "pompa ciepła", "varmepumpe", "pompe à chaleur", "ısı pompası", "pompa di calore", "tepelné čerpadlo", "Čerpadlo topení")
MAKE_TRANSLATION(fanWork, "fanwork", "fan", "Gebläse", "Ventilator", "Fläkt", "wentylator", "vifte", "ventilateur", "fan", "Ventilatore", "ventilátor", "ventilátor")
MAKE_TRANSLATION(ignWork, "ignwork", "ignition", "Zündung", "Ontsteking", "Tändning", "zapłon", "tenning", "ignition", "ateşleme", "accensione", "zapálenie", "zapalování")
@@ -355,7 +365,7 @@ MAKE_TRANSLATION(heatStarts, "heatstarts", "burner starts heating", "Brennerstar
MAKE_TRANSLATION(UBAuptime, "ubauptime", "total UBA operating time", "Anlagengesamtlaufzeit", "totale looptijd branderautomaat (UBA)", "Total Tid", "łączny czas pracy układu sterowania", "totaltid", "durée de fonctionnement totale de l'appareil (UBA)", "kazanın toplam işletme süresi", "Tempo di funzionamento totale del sistema", "Celkový čas chodu systému", "celkový provozní čas UBA")
MAKE_TRANSLATION(lastCode, "lastcode", "last error code", "Letzter Fehler", "Laatste foutcode", "Senaste Felkod", "ostatni błąd", "siste feilkode", "dernier code d'erreur", "son hata kodu", "ultimo codice errore", "posledný chybový kód", "poslední kód chyby")
MAKE_TRANSLATION(serviceCode, "servicecode", "service code", "Statusmeldung", "Statuscode", "Servicekod", "kod serwisowy", "servicekode", "code de service", "servis kodu", "codice messaggio di stato", "servisný kód", "servisní kód")
MAKE_TRANSLATION(serviceCodeNumber, "servicecodenumber", "service code number", "Statusmeldungsnummer", "Status codenummer", "Servicekod", "numer kodu serwisowego", "servicekodenummer", "numéro du code de service", "servis kod numarası", "numero del messaggio di stato", "servisný kód - číslo", "číslo servisního kódu")
MAKE_TRANSLATION(serviceCodeNumber, "servicecodenumber", "service code number", "Statusmeldungsnummer", "Status codenummer", "Servicekodnummer", "numer kodu serwisowego", "servicekodenummer", "numéro du code de service", "servis kod numarası", "numero del messaggio di stato", "servisný kód - číslo", "číslo servisního kódu")
MAKE_TRANSLATION(maintenanceMessage, "maintenancemessage", "maintenance message", "Wartungsmeldung", "Onderhoudsmelding", "Servicemeddelande", "komunikat przeglądu", "vedlikeholdsmelding", "message de maintenance", "bakım mesajı", "messaggio di manutenzione", "správa o údržbe", "zpráva o údržbě")
MAKE_TRANSLATION(maintenanceDate, "maintenancedate", "next maintenance date", "Wartungsdatum", "Onderhoudsdatum", "Datum nästa Service", "termin następnego przeglądu", "vedlikeholdsdato", "prochaine date de maintenance", "bakım tarihi", "prossima data di manutenzione", "dátum ďalšej údržby", "datum další údržby")
MAKE_TRANSLATION(maintenanceType, "maintenance", "maintenance scheduled", "Wartungsplan", "Onderhoud gepland", "Underhall schemlagt", "rodzaj przeglądu", "vedlikeholdstype", "maintenance prévue", "planlı bakım", "manutenzione programmata", "plánovaná údržba", "naplánovaná údržba")
@@ -364,20 +374,20 @@ MAKE_TRANSLATION(emergencyOps, "emergencyops", "emergency operation", "Notbetrie
MAKE_TRANSLATION(emergencyTemp, "emergencytemp", "emergency temperature", "Notfalltemperatur", "Noodtemperatuur", "Nöddrift temperatur", "temperatura w trybie awaryjnym", "nødtemperatur", "température d'urgence", "acil durum sıcaklığı", "temperatura di emergenza", "núdzová teplota", "nouzová teplota")
MAKE_TRANSLATION(pumpMode, "pumpmode", "boiler pump mode", "Kesselpumpenmodus", "Ketelpomp modus", "", "tryb pracy pompy kotła", "pumpemodus", "", "pompa modu", "modalità pompa caldaia", "režim kotlového čerpadla", "režim čerpadla kotle") // TODO translate
MAKE_TRANSLATION(pumpCharacter, "pumpcharacter", "boiler pump characteristic", "Charakteristik der Kesselpumpe", "karakteristiek ketelpomp", "pannpumpsegenskaper", "charakterystyka pompy kotłowej", "kjelepumpekarakteristikk", "caractéristique de la pompe de la chaudière", "gazan nasosy", "caratteristica della pompa della caldaia", "charakteristika kotlového čerpadla", "charakteristika čerpadla kotle") // TODO translate
MAKE_TRANSLATION(pumpOnTemp, "pumpontemp", "pump logic temperature", "Pumpenlogiktemperatur", "", "", "", "", "", "", "", "teplota logiky čerpadla", "") // TODO translate
MAKE_TRANSLATION(headertemp, "headertemp", "low loss header", "Hydr. Weiche", "open verdeler", "", "sprzęgło hydrauliczne", "", "bouteille de déc. hydr.", "isı bloğu gidiş suyu sıc.", "comp. idr.", "nízkostratová hlavica", "") // TODO translate
MAKE_TRANSLATION(heatblock, "heatblock", "heating block", "Wärmezelle", "Aanvoertemp. warmtecel", "", "blok grzewczy", "", "départ corps de chauffe", "Hid.denge kabı sıcaklığı", "mandata scamb. pr.", "vykurovací blok", "blok topení") // TODO translate
MAKE_TRANSLATION(pumpOnTemp, "pumpontemp", "pump logic temperature", "Pumpenlogiktemperatur", "", "Pumplogiktemperatur", "", "", "", "", "", "teplota logiky čerpadla", "") // TODO translate
MAKE_TRANSLATION(headertemp, "headertemp", "low loss header", "Hydr. Weiche", "open verdeler", "Fördelare", "sprzęgło hydrauliczne", "", "bouteille de déc. hydr.", "isı bloğu gidiş suyu sıc.", "comp. idr.", "nízkostratová hlavica", "") // TODO translate
MAKE_TRANSLATION(heatblock, "heatblock", "heating block", "Wärmezelle", "Aanvoertemp. warmtecel", "Värmeblock", "blok grzewczy", "", "départ corps de chauffe", "Hid.denge kabı sıcaklığı", "mandata scamb. pr.", "vykurovací blok", "blok topení") // TODO translate
MAKE_TRANSLATION(curveOn, "curveon", "heatingcurve on", "Heizkurve an", "", "", "", "", "", "", "", "vykurovacia krivka zapnutá", "topná křivka zapnutá") // TODO translate
MAKE_TRANSLATION(curveBase, "curvebase", "heatingcurve base", "Heizkurve Basis", "", "", "", "", "", "", "", "základňa vykurovacej krivky", "základ topné křivky") // TODO translate
MAKE_TRANSLATION(curveEnd, "curveend", "heatingcurve end", "Heizkurve Ende", "", "", "", "", "", "", "", "koniec vykurovacej krivky", "konec topné křivky") // TODO translate
MAKE_TRANSLATION(curveOn, "curveon", "heatingcurve on", "Heizkurve an", "", "Värmekurva På", "", "", "", "", "", "vykurovacia krivka zapnutá", "topná křivka zapnutá") // TODO translate
MAKE_TRANSLATION(curveBase, "curvebase", "heatingcurve base", "Heizkurve Basis", "", "Värmekurva Bas", "", "", "", "", "", "základňa vykurovacej krivky", "základ topné křivky") // TODO translate
MAKE_TRANSLATION(curveEnd, "curveend", "heatingcurve end", "Heizkurve Ende", "", "Värmekurva Slut", "", "", "", "", "", "koniec vykurovacej krivky", "konec topné křivky") // TODO translate
// heatpump/compress specific
MAKE_TRANSLATION(upTimeTotal, "uptimetotal", "heatpump total uptime", "Gesamtbetriebszeit Wärmepumpe", "", "", "łączny czas pracy pompy ciepła", "", "", "", "", "celková doba prevádzky tepelného čerpadla", "celková doba provozu tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(upTimeTotal, "uptimetotal", "heatpump total uptime", "Gesamtbetriebszeit Wärmepumpe", "", "Total tid värmepump", "łączny czas pracy pompy ciepła", "", "", "", "", "celková doba prevádzky tepelného čerpadla", "celková doba provozu tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(upTimeControl, "uptimecontrol", "total operating time heat", "Gesamtbetriebszeit Heizen", "Totale bedrijfstijd", "Total tid uppvärmning", "łączny czas generowania ciepła", "total driftstid", "durée totale de fonctionnement chauffage", "ısınma toplam işletme süresi", "Tempo di funzionamento totale riscaldamento", "celkový prevádzkový čas tepla", "celková provozní doba topení")
MAKE_TRANSLATION(upTimeCompHeating, "uptimecompheating", "operating time compressor heating", "Betriebszeit Kompressor heizen", "Bedrijfstijd compressor verwarmingsbedrijf", "Total tid kompressor uppvärmning", "łączny czas ogrzewania (sprężarka)", "totaltid kompressor", "durée de fonctionnement compresseur chauffage", "ısı pompası ısınma işletme süresi", "tempo di funzionamento del compressore riscaldamento", "prevádzková doba vykurovania kompresora", "provozní doba kompresoru pro topení")
MAKE_TRANSLATION(upTimeCompCooling, "uptimecompcooling", "operating time compressor cooling", "Betriebszeit Kompressor kühlen", "Bedrijfstijd compressor koelbedrijf", "Total tid kompressor kyla", "łączny czas chłodzenia (sprężarka)", "Total tid kompressor kjøling", "durée de fonctionnement compresseur refroidissement", "ısı pompası soğuma işletme süresi", "tempo di funzionamento del compressore raffreddamento", "doba prevádzky chladenia kompresora", "provozní doba kompresoru pro chlazení")
MAKE_TRANSLATION(upTimeCompWw, "uptimecomp", "operating time compressor", "Betriebszeit Kompressor", "Bedrijfstijd compressor", "Total tid kompressor", "łączny czas grzania c.w.u. (sprężarka)", "Total tid kompressor", "durée de fonctionnement compresseur", "ısı pompası sıcak kullanım suyu işletme süresi", "tempo di funzionamento del compressore", "prevádzková doba kompresora", "provozní doba kompresoru")
MAKE_TRANSLATION(upTimeCompWw, "uptimecomp", "operating time compressor", "Betriebszeit Kompressor", "Bedrijfstijd compressor", "Total tid kompressor", "łączny czas grzania c.w.u. (sprężarka)", "Total tid kompressor varmvatten", "durée de fonctionnement compresseur", "ısı pompası sıcak kullanım suyu işletme süresi", "tempo di funzionamento del compressore", "prevádzková doba kompresora", "provozní doba kompresoru")
MAKE_TRANSLATION(upTimeCompPool, "uptimecomppool", "operating time compressor pool", "Betriebszeit Kompressor Pool", "Bedrijfstijd compressor voor zwembadbedrijf", "Total tid kompressor pool", "łączny czas podgrzewania basenu (sprężarka)", "Total tid kompressor basseng", "durée de fonctionnement compresseur piscine", "ısı pompası havuz işletme süresi", "tempo di funzionamento del compressore piscina", "prevádzková doba kompresorového bazéna", "provozní doba kompresoru pro bazén")
MAKE_TRANSLATION(totalCompStarts, "totalcompstarts", "total compressor control starts", "Gesamtkompressorstarts ", "Totaal compressorstarts", "Kompressorstarter Totalt", "liczba załączeń sprężarki", "kompressorstarter totalt", "nombre démarrages total contrôle compresseur", "ısı pompası kontrolü toplam başlatma", "avvii totali del compressore", "spustí sa celkové riadenie kompresora", "celkový počet startů řízení kompresoru")
MAKE_TRANSLATION(heatingStarts, "heatingstarts", "heating control starts", "Heizungsregelungstarts", "Starts verwarmingsbedrijf", "Kompressorstarter Uppvärmning", "liczba załączeń ogrzewania", "kompressorstarter oppvarming", "démarrages contrôle chauffage", "ısıtma kontrolü toplam başlatma", "avvii riscaldamento", "ovládanie vykurovania sa spustí", "počet startů řízení topení")
@@ -386,35 +396,35 @@ MAKE_TRANSLATION(poolStarts, "poolstarts", "pool control starts", "Poolsteuerung
MAKE_TRANSLATION(nrgConsTotal, "nrgconstotal", "total energy consumption", "Gesamtenergieverbrauch", "Energieverbrauch gesamt", "Energiförbrukning totalt", "energia pobrana (sumarycznie)", "energiforbruk totalt", "consommation totale énergie", "toplam enerji tüketimi", "totale energia consumata", "celková spotreba energie", "celková spotřeba energie")
MAKE_TRANSLATION(nrgConsCompTotal, "nrgconscomptotal", "total energy consumption compressor", "Gesamtenergieverbrauch Kompressor", "Energieverbruik compressor totaal", "Energiförbrukning kompressor", "energia pobrana przez sprężarkę", "energiforbruk kompressor", "consommation totale énergie compresseur", "ısı pompası toplam enerji tüketimi", "totale energia consumata compressore", "kompresor s celkovou spotrebou energie", "celková spotřeba energie kompresoru")
MAKE_TRANSLATION(nrgConsCompHeating, "nrgconscompheating", "energy consumption compressor heating", "Energieverbrauch Kompressor heizen", "Energieverbruik compressor verwarmingsbedrijf", "Energiförbrukning uppvärmning", "energia pobrana przez sprężarkę na ogrzewanie", "energiforbruk oppvarming", "consommation énergie compresseur chauffage", "ısı pompası ısıtma toplam enerji tüketimi", "consumo energia compressore riscaldamento", "spotreba energie vykurovanie kompresorom", "spotřeba energie kompresoru pro topení")
MAKE_TRANSLATION(nrgConsCompWw, "nrgconscomp", "energy consumption compressor", "Energieverbrauch Kompressor", "Energieverbruik compressor", "Energiförbrukning", "energia pobrana przez sprężarkę na c.w.u.", "energiforbruk", "consommation énergie compresseur", "ısı pompası sıcak kullanım suyu toplam enerji tüketimi", "consumo energia compressore", "kompresor spotreby energie", "spotřeba energie kompresoru")
MAKE_TRANSLATION(nrgConsCompWw, "nrgconscomp", "energy consumption compressor", "Energieverbrauch Kompressor", "Energieverbruik compressor", "Energiförbrukning varmvatten", "energia pobrana przez sprężarkę na c.w.u.", "energiforbruk", "consommation énergie compresseur", "ısı pompası sıcak kullanım suyu toplam enerji tüketimi", "consumo energia compressore", "kompresor spotreby energie", "spotřeba energie kompresoru")
MAKE_TRANSLATION(nrgConsCompCooling, "nrgconscompcooling", "energy consumption compressor cooling", "Energieverbrauch Kompressor kühlen", "Energieverbruik compressor koelbedrijf", "Energiförbrukning kyla", "energia pobrana przez sprężarkę na chłodzenie", "energiforbruk kjøling", "consommation énergie compresseur refroidissement", "ısı pompası soğutma toplam enerji tüketimi", "consumo energia compressore raffreddamento", "spotreba energie kompresorové chladenie", "spotřeba energie kompresoru pro chlazení")
MAKE_TRANSLATION(nrgConsCompPool, "nrgconscomppool", "energy consumption compressor pool", "Energieverbrauch Kompressor Pool", "Energiebedrijf compressor zwembadbedrijf", "Energiförbrukning pool", "energia pobrana przez sprężarkę na podgrzewanie basenu", "energiforbruk basseng", "consommation énergie compresseur piscine", "ısı pompası havuz toplam enerji tüketimi", "consumo energia compressore piscina", "spotreba energie kompresorový bazén", "spotřeba energie kompresoru pro bazén")
MAKE_TRANSLATION(nrgSuppTotal, "nrgsupptotal", "total energy supplied", "gesamte Energieabgabe", "Totaal opgewekte energie", "Genererad energi", "energia oddana (sumarycznie)", "tilført energi", "énergie totale fournie", "sağlanan toplam enerji", "totale energia fornita", "celková dodaná energia", "celková dodaná energie")
MAKE_TRANSLATION(nrgSuppHeating, "nrgsuppheating", "total energy supplied heating", "gesamte Energieabgabe heizen", "Opgewekte energie verwarmingsbedrijf", "Genererad energi Uppvärmning", "energia oddana na ogrzewanie", "tilført energi oppvarming", "énergie totale fournie chauffage", "ısıtma sağlanan toplam enerji", "energia totale fornita - riscaldamento", "celková dodaná energia na vykurovanie", "celková dodaná energie pro topení")
MAKE_TRANSLATION(nrgSuppWw, "nrgsupp", "total energy warm supplied", "gesamte Energieabgabe Wärme", "Opgewekte energie", "Genererad energi", "energia oddana na c.w.u.", "tilført energi", "énergie chaude totale fournie", "sıcak kullanım suyu sağlanan toplam enerji", "totale energia calorica fornita", "celková dodaná teplá energia", "celková dodaná teplá energie")
MAKE_TRANSLATION(nrgSuppWw, "nrgsupp", "total energy warm supplied", "gesamte Energieabgabe Wärme", "Opgewekte energie", "Genererad energi varmvatten", "energia oddana na c.w.u.", "tilført energi", "énergie chaude totale fournie", "sıcak kullanım suyu sağlanan toplam enerji", "totale energia calorica fornita", "celková dodaná teplá energia", "celková dodaná teplá energie")
MAKE_TRANSLATION(nrgSuppCooling, "nrgsuppcooling", "total energy supplied cooling", "gesamte Energieabgabe kühlen", "Opgewekte energie koelbedrijf", "Genererad energi Kyla", "energia oddana na chłodzenie", "Tillført energi kjøling", "énergie totale fournie refroidissement", "soğutma sağlanan toplam enerji", "energia totale fornita - raffreddamento", "chladenie s celkovou dodanou energiou", "celková dodaná energie pro chlazení")
MAKE_TRANSLATION(nrgSuppPool, "nrgsupppool", "total energy supplied pool", "gesamte Energieabgabe Pool", "Opgewekte energie zwembadbedrijf", "Genererad energi Pool", "energia oddana na podgrzewanie basenu", "tilført energi basseng", "énergie totale fournie piscine", "havuz sağlanan toplam enerji", "totale di energia fornita- piscina", "celkový bazén dodanej energie", "celková dodaná energie pro bazén")
MAKE_TRANSLATION(auxElecHeatNrgConsTotal, "auxelecheatnrgconstotal", "total aux elec. heater energy consumption", "Energieverbrauch el. Zusatzheizung", "Totaal energieverbruik electrisch verwarmingselement", "Energiförbrukning Eltillkott", "energia pobrana przez grzałki", "energiforbruk varmekolbe", "consommation totale énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı toplam enerji tüketimi", "consumo energetico riscaldamento elettrico supplementare", "celková spotreba energie prídavného elektrického ohrievača", "celková spotřeba energie pomocného el. ohřívače")
MAKE_TRANSLATION(auxElecHeatNrgConsHeating, "auxelecheatnrgconsheating", "aux elec. heater energy consumption heating", "Energieverbrauch el. Zusatzheizung Heizen", "Energieverbruik electrisch verwarmingselement voor verwarmingsbedrijf", "Energiförbrukning Eltillskott Uppvärmning", "energia pobrana przez grzałki na ogrzewanie", "energiforbruk varmekolbe oppvarming", "consommation énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı ısınma toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario", "pomocný elektrický ohrievač spotreba energie vykurovanie", "spotřeba energie pomocného el. ohřívače pro topení")
MAKE_TRANSLATION(auxElecHeatNrgConsWw, "auxelecheatnrgcons", "aux elec. heater energy consumption", "Energieverbrauch el. Zusatzheizung", "Energieverbruik electrisch verwarmingselement voor", "Energiförbrukning Eltillskott", "energia pobrana przez grzałki na c.w.u.", "energiförbruk varmekolbe", "consommation énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı sıcak kullanım suyu toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario", "spotreba energie pomocného elektrického ohrievača", "spotřeba energie pomocného el. ohřívače")
MAKE_TRANSLATION(auxElecHeatNrgConsWw, "auxelecheatnrgcons", "aux elec. heater energy consumption", "Energieverbrauch el. Zusatzheizung", "Energieverbruik electrisch verwarmingselement voor", "Energiförbrukning Eltillskott Varmvatten", "energia pobrana przez grzałki na c.w.u.", "energiförbruk varmekolbe", "consommation énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı sıcak kullanım suyu toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario", "spotreba energie pomocného elektrického ohrievača", "spotřeba energie pomocného el. ohřívače")
MAKE_TRANSLATION(auxElecHeatNrgConsPool, "auxelecheatnrgconspool", "aux elec. heater energy consumption pool", "Energieverbrauch el. Zusatzheizung Pool", "Energieverbruik electrisch verwarmingselement voor zwembadbedrijf", "Energiförbrukning Eltillskott Pool", "energia pobrana przez grzałki na podgrzewanie basenu", "energiforbruk el. tilleggsvarme basseng", "consommation énergie electrique auxiliaire chauffage piscine", "ilave elektrikli ısıtıcı havuz toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario piscina", "bazén spotreby energie pomocného elektrického ohrievača", "spotřeba energie pomocného el. ohřívače pro bazén")
MAKE_TRANSLATION(hpCompOn, "hpcompon", "hp compressor", "WP Kompressor", "WP compressor", "VP Kompressor", "sprężarka pompy ciepła", "vp kompressor", "compresseur pompe à chaleur", "hp ısı pompası", "compressore pompa calore", "hp kompresor", "hp kompresor")
MAKE_TRANSLATION(hpCompOn, "hpcompon", "hp compressor", "WP Kompressor", "WP compressor", "VP Kompressor", "sprężarka pompy ciepła", "vp kompressor", "compresseur pompe à chaleur", "hp ısı pompası", "compressore pompa calore", " kompresor", " kompresor")
MAKE_TRANSLATION(coolingOn, "coolingon", "cooling on", "Kühlung an", "koelbedrijf", "Kyla", "chłodzenie włączone", "kjøling", "refroidissement", "soğutma", "", "chladenie", "chlazení zapnuto") // TODO translate
// MAKE_TRANSLATION(hpHeatingOn, "hpheatingon", "hp heating", "WP Heizen", "WP verwarmingsbedrijf", "VP Uppvärmning", "pompa ciepła, ogrzewanie", "vp oppvarmning", "hp ısınıyor", "riscaldamento pompa calore", "vykurovanie hp", "topení hp") // TODO translate
// MAKE_TRANSLATION(hpCoolingOn, "hpcoolingon", "hp cooling", "WP Kühlen", "WP koelbedrijf", "VP Kyla", "pompa ciepła, chłodzenie", "vp kjøling", "hp soğuyor", "raffreddamento pompa calore", "chladenie hp", "chlazení hp") // TODO translate
// MAKE_TRANSLATION(hpWwOn, "hpdhwon", "hp", "WP", "WP", "VP", "pompa ciepła", "vp", "pompe à chaleur", "hp", "pompa calore", "hp", "hp")
// MAKE_TRANSLATION(hpPoolOn, "hppoolon", "hp pool", "WP Pool", "WP zwembadbedrijf", "VP Pool", "pompa ciepła, podgrzewanie basenu", "vp basseng", "tuzlu su pompası hızı", "pompa calore piscina", "hp bazén", "hp bazén") // TODO translate
// MAKE_TRANSLATION(hpHeatingOn, "hpheatingon", "hp heating", "WP Heizen", "WP verwarmingsbedrijf", "VP Uppvärmning", "pompa ciepła, ogrzewanie", "vp oppvarmning", "hp ısınıyor", "riscaldamento pompa calore", "vykurovanie hp", "topení hp")
// MAKE_TRANSLATION(hpCoolingOn, "hpcoolingon", "hp cooling", "WP Kühlen", "WP koelbedrijf", "VP Kyla", "pompa ciepła, chłodzenie", "vp kjøling", "hp soğuyor", "raffreddamento pompa calore", "chladenie hp", "chlazení hp")
// MAKE_TRANSLATION(hpWwOn, "hpdhwon", "hp", "WP", "WP", "VP Varmvatten", "pompa ciepła", "vp", "pompe à chaleur", "hp", "pompa calore", "hp", "hp")
// MAKE_TRANSLATION(hpPoolOn, "hppoolon", "hp pool", "WP Pool", "WP zwembadbedrijf", "VP Pool", "pompa ciepła, podgrzewanie basenu", "vp basseng", "tuzlu su pompası hızı", "pompa calore piscina", "hp bazén", "hp bazén")
MAKE_TRANSLATION(hpBrinePumpSpd, "hpbrinepumpspd", "brine pump speed", "Solepumpendrehzahl", "Snelheid pekelpomp", "Hastighet Brine-pump", "wysterowanie pompy glikolu", "hastighet brine-pumpe", "vitesse pompe à saumure", "ısı pompası hızı", "velocità pompa sbrinamento", "rýchlosť čerpadla soľanky", "rychlost čerpadla solanky")
MAKE_TRANSLATION(hpCompSpd, "hpcompspd", "compressor speed", "Kompressordrehzahl", "Snelheid compressor", "Kompressorhastighet", "wysterowanie sprężarki", "kompressorhastighet", "vitesse du compresseur", "sirkülasyon pompası hızı", "velocità compressore", "rýchlosť kompresora", "rychlost kompresoru")
MAKE_TRANSLATION(hpCircSpd, "hpcircspd", "circulation pump speed", "Zirkulationspumpendrehzahl", "Snelheid circulatiepomp", "Hastighet Cirkulationspump", "wysterowanie pompy obiegu grzewczego", "hastighet sirkulationspumpe", "vitesse pompe à circulation", "evaporatör tuzlu su giişi", "velocità pompa circolazione", "otáčky obehového čerpadla", "rychlost oběhového čerpadla")
MAKE_TRANSLATION(hpBrineIn, "hpbrinein", "brine in/evaporator", "Sole in/Verdampfer", "pekel in/verdamper", "Brine in (förangare)", "temperatura glikolu na wejściu kolektora (TB0)", "brine in/fordamper", "entrée saumure/évaporateur", "kondenser tuzlu su çıkışı", "salamoia nell evaporatore", "soľanka v/výparník", "solanka in/evaporátor")
MAKE_TRANSLATION(hpBrineIn, "hpbrinein", "brine in/evaporator", "Sole in/Verdampfer", "pekel in/verdamper", "Brine in (förångare)", "temperatura glikolu na wejściu kolektora (TB0)", "brine in/fordamper", "entrée saumure/évaporateur", "kondenser tuzlu su çıkışı", "salamoia nell evaporatore", "soľanka v/výparník", "solanka in/evaporátor")
MAKE_TRANSLATION(hpBrineOut, "hpbrineout", "brine out/condenser", "Sole aus/Kondensator", "pekel uit/condensor", "Brine ut (kondensor)", "temperatura glikolu na wyjściu kolektora (TB1)", "Brine ut/kondensor", "sortie saumure/condenseur", "anahtar valfi", "salamoia nell uscita evaporatore", "výstup soľanky/kondenzátor", "solanka out/kondenzátor")
MAKE_TRANSLATION(hpSwitchValve, "hpswitchvalve", "switch valve", "Schaltventil", "schakelklep", "Växelventil", "zawór przełączający", "skifteventil", "valve de commutation", "ısı pompası aktivitesi", "valvola commutazione pompa di calore", "prepínací ventil", "přepínací ventil")
MAKE_TRANSLATION(hpActivity, "hpactivity", "compressor activity", "Kompressoraktivität", "Compressoractiviteit", "Kompressoraktivitet", "pompa ciepła, aktywność sprężarki", "kompressoraktivitet", "hp ısı pompası", "attività compressore", "činnosť kompresora", "činnost kompresoru") // TODO translate
MAKE_TRANSLATION(hpActivity, "hpactivity", "compressor activity", "Kompressoraktivität", "Compressoractiviteit", "Kompressoraktivitet", "pompa ciepła, aktywność sprężarki", "kompressoraktivitet", "hp ısı pompası", "attività compressore", "činnosť kompresora", "činnost kompresoru")
MAKE_TRANSLATION(hpMaxPower, "hpmaxpower", "compressor max power", "max. Kompressorleistung", "", "", "maksymalna wydajność sprężarki", "", "", "", "", "max výkon kompresora", "maximální výkon kompresoru") // TODO translate
MAKE_TRANSLATION(pvMaxComp, "pvmaxcomp", "pv compressor max power", "PV max. Kompressorleistung", "", "", "maksymalna wydajność sprężarki", "", "", "", "", "pv max výkon kompresora", "maximální výkon PV kompresoru") // TODO translate
MAKE_TRANSLATION(hpMaxPower, "hpmaxpower", "compressor max power", "max. Kompressorleistung", "", "Max. Kompressoreffekt", "maksymalna wydajność sprężarki", "", "", "", "", "max výkon kompresora", "maximální výkon kompresoru") // TODO translate
MAKE_TRANSLATION(pvMaxComp, "pvmaxcomp", "pv compressor max power", "PV max. Kompressorleistung", "", "PV Max. Kompressoreffekt", "maksymalna wydajność sprężarki", "", "", "", "", "pv max výkon kompresora", "maximální výkon PV kompresoru") // TODO translate
MAKE_TRANSLATION(hpPower, "hppower", "compressor power output", "Kompressorleistung", "Compressorvermogen", "Kompressoreffekt", "moc wyjściowa sprężarki", "kompressoreffekt", "puissance de sortie compresseur", "ısı pompası güç çıkışı", "potenza uscita compressore", "výkon kompresora", "výstupní výkon kompresoru")
MAKE_TRANSLATION(hpTc0, "hptc0", "heat carrier return (TC0)", "Kältemittelrücklauf (TC0)", "Koudemiddel retour (TC0)", "Värmebärare Retur (TC0)", "temperatura nośnika ciepła na powrocie (TC0)", "kjølemiddel retur (TC0)", "retour caloporteur (TC0)", "sıcak su dönüşü (TC0)", "ritorno del refrigerante (TC0)", "návrat nosiča tepla (TC0)", "návrat teplonosné látky (TC0)")
MAKE_TRANSLATION(hpTc1, "hptc1", "heat carrier forward (TC1)", "Kältemittelvorlauf (TC1)", "Koudemiddel aanvoer (TC1)", "Värmebärare Framledning (TC1)", "temperatura nośnika ciepła pierwotna (TC1)", "kjølemiddel tur (TC1)", "avance caloporteur (TC1)", "sıcak su çıkışı (TC1)", "flusso di refrigerante (TC1)", "nosič tepla vpred (TC1)", "předání teplonosné látky (TC1)")
@@ -428,8 +438,8 @@ MAKE_TRANSLATION(hpTr7, "hptr7", "refrigerant temperature gas side (condenser in
MAKE_TRANSLATION(hpTl2, "hptl2", "air inlet temperature (TL2)", "Außenlufteintrittstemperatur (TL2)", "Temperatuur luchtinlaat (TL2)", "Luftintagstemperatur (TL2)", "temperatura wlotu powietrza (TL2)", "luftinntakstemperatur (TL2)", "température entrée air (TL2)", "hava giriş sıcaklığı (TL2)", "temperatura ingresso aria (TL2)", "teplota prívodu vzduchu (TL2)", "teplota vzduchu na vstupu (TL2)")
MAKE_TRANSLATION(hpPl1, "hppl1", "low pressure side temperature (PL1)", "Niederdrucktemperatur (PL1)", "Temperatuur lage drukzijde (PL1)", "Temperatur Lågtryckssidan (PL1)", "temperatura po stronie niskiego ciśnienia (PL1)", "temperatur lavtrykksiden (PL1)", "température côté basse pression (PL1)", "düşük basınç tarafı sıcaklığı (PL1)", "temperatura lato bassa pressione (PL1)", "teplota na strane nízkeho tlaku (PL1)", "teplota na nízkotlaké straně (PL1)")
MAKE_TRANSLATION(hpPh1, "hpph1", "high pressure side temperature (PH1)", "Hochdrucktemperatur (PH1)", "Temperatuur hoge drukzijde (PH1)", "Temperatur Högtryckssidan (PH1)", "temperatura po stronie wysokiego ciśnienia (PH1)", "Temperatur Høytrykksiden (PH1)", "température côté bhauteasse pression (PH1)", "yüksek basınç tarafı sıcaklığı (PH1)", "temperatura lato alta pressione (PH1)", "teplota na strane vysokého tlaku (PH1)", "teplota na vysokotlaké straně (PH1)")
MAKE_TRANSLATION(hpTa4, "hpta4", "drain pan temp (TA4)", "Kondensatorwanne (TA4)", "Temperatuur condensorafvoerbak (TA4)", " (TA4)", "temperatura ociekacza (TA4)", "kondens temperatur (TA4)", " (TA4)", "tahliye sıcaklığı (TA4)", "temperatura condensatore (TA4)", "teplota vypúšťacej misky (TA4)", "teplota odvodňovací vany (TA4)") // TODO translate
MAKE_TRANSLATION(hpTw1, "hptw1", "reservoir temp (TW1)", "DHW Reservoir (TW1)", "(TW1)", "(TW1)", "temperatura zbiornika (TW1)", "(TW1)", "(TW1)", "(TW1)", "(TW1)", "teplota zásobníka (TW1)", "teplota v nádrži (TW1)") // TODO translate
MAKE_TRANSLATION(hpTa4, "hpta4", "drain pan temp (TA4)", "Kondensatorwanne (TA4)", "Temperatuur condensorafvoerbak (TA4)", " (TA4)", "temperatura ociekacza (TA4)", "kondenstråg temperatur (TA4)", " (TA4)", "tahliye sıcaklığı (TA4)", "temperatura condensatore (TA4)", "teplota vypúšťacej misky (TA4)", "teplota odvodňovací vany (TA4)") // TODO translate
MAKE_TRANSLATION(hpTw1, "hptw1", "reservoir temp (TW1)", "DHW Reservoir (TW1)", "(TW1)", "Varmvattentank Temperatur (TW1)", "temperatura zbiornika (TW1)", "(TW1)", "(TW1)", "(TW1)", "(TW1)", "teplota zásobníka (TW1)", "teplota v nádrži (TW1)") // TODO translate
MAKE_TRANSLATION(hpInput1, "hpin1", "input 1 state", "Status Eingang 1", "Status input 1", "Status Ingång 1", "stan wejścia 1", "status inggang 1", "état entrée 1", "giriş 1 durumu", "stato ingresso 1", "stav vstupu 1", "stav vstupu 1")
MAKE_TRANSLATION(hpInput2, "hpin2", "input 2 state", "Status Eingang 2", "Status input 2", "Status Ingång 2", "stan wejścia 2", "status inggang 2", "état entrée 2", "giriş 2 durumu", "stato ingresso 2", "stav vstupu 2", "stav vstupu 2")
@@ -439,81 +449,81 @@ MAKE_TRANSLATION(hpIn1Opt, "hpin1opt", "input 1 options", "Einstellung Eingang 1
MAKE_TRANSLATION(hpIn2Opt, "hpin2opt", "input 2 options", "Einstellung Eingang 2", "Instelling input 2", "Inställningar Ingång 2", "opcje wejścia 2", "innstillinger inngang 2", "options entrée 2", "giriş 2 seçenekleri", "impostazioni ingresso 2", "možnosti vstupu 2", "možnosti vstupu 2")
MAKE_TRANSLATION(hpIn3Opt, "hpin3opt", "input 3 options", "Einstellung Eingang 3", "Instelling input 3", "Inställningar Ingång 3", "opcje wejścia 3", "innstillinger inngang 3", "options entrée 3", "giriş 3 seçenekleri", "impostazioni ingresso 3", "možnosti vstupu 3", "možnosti vstupu 3")
MAKE_TRANSLATION(hpIn4Opt, "hpin4opt", "input 4 options", "Einstellung Eingang 4", "Instelling input 4", "Inställningar Ingång 4", "opcje wejścia 4", "innstillinger inngang 4", "options entrée 4", "giriş 4 seçenekleri", "impostazioni ingresso 4", "možnosti vstupu 4", "možnosti vstupu 4")
MAKE_TRANSLATION(maxHeatComp, "maxheatcomp", "heat limit compressor", "Heizstab Limit mit Kompressor", "heat limit compressor", "heat limit compressor", "ograniczenie mocy sprężarki", "max varmegrense kompressor", "limite chaleur compresseur", "ısı pompası ısıtma sınırı", "limite riscaldamento compressore", "tepelný limit kompresora", "tepelný limit kompresoru")
MAKE_TRANSLATION(maxHeatHeat, "maxheatheat", "heat limit heating", "Heizstab Limit Leistung", "heat limit heating", "heat limit heating", "ograniczenie mocy w trybie ogrzewania", "maks varmegrense oppvarming", "limite chaleur chauffage", "ısınma ısıtma sınırı", "limite calore riscaldamento", "vyhrievanie limitu tepla", "tepelný limit topení")
MAKE_TRANSLATION(maxHeatDhw, "maxheat", "heat limit", "Heizstab Limit für WW", "heat limit", "heat limit", "ograniczenie mocy w trybie c.w.u.", "varmegrense", "limite chaleur", "sıcak kullanım suyu ısınma sınırı", "limite calore", "tepelný limit", "tepelný limit")
MAKE_TRANSLATION(maxHeatComp, "maxheatcomp", "heat limit compressor", "Heizstab Limit mit Kompressor", "heat limit compressor", "Max. Värmegräns Kompressor", "ograniczenie mocy sprężarki", "max varmegrense kompressor", "limite chaleur compresseur", "ısı pompası ısıtma sınırı", "limite riscaldamento compressore", "tepelný limit kompresora", "tepelný limit kompresoru")
MAKE_TRANSLATION(maxHeatHeat, "maxheatheat", "heat limit heating", "Heizstab Limit Leistung", "Max, Värmegräns Uppvärmning", "heat limit heating", "ograniczenie mocy w trybie ogrzewania", "maks varmegrense oppvarming", "limite chaleur chauffage", "ısınma ısıtma sınırı", "limite calore riscaldamento", "vyhrievanie limitu tepla", "tepelný limit topení")
MAKE_TRANSLATION(maxHeatDhw, "maxheat", "heat limit", "Heizstab Limit für WW", "heat limit", "Max. Värmegräns Varmvatten", "ograniczenie mocy w trybie c.w.u.", "varmegrense", "limite chaleur", "sıcak kullanım suyu ısınma sınırı", "limite calore", "tepelný limit", "tepelný limit")
MAKE_TRANSLATION(auxHeaterOff, "auxheateroff", "disable aux heater", "Zusatzheizer deaktivieren", "Bijverwarming uitsc", "Blockera eltillskott", "wyłącz dogrzewacz", "deaktiver tilleggsvarme", "Désactiver chauff. d'app", "ilave ısıtıcıyı kapat", "disattivare i riscaldatori addizionali", "vypnúť pomocný ohrievač", "zakázat pomocné topení")
MAKE_TRANSLATION(auxHeaterStatus, "auxheaterstatus", "aux heater status", "Zusatzheizerstatus", "Bijverwarming", "Eltillskott Status", "status dogrzewacza", "status el. tillegsvarme", "Chauffage auxiliaire", "ilave ısıtıcı durumu", "stato riscaldatori addizionali", "stav pomocného ohrievača", "stav pomocného topení")
MAKE_TRANSLATION(auxHeaterLevel, "auxheaterlevel", "aux heater level", "Zusatzheizer", "Bijverwarming", "Eltillskott", "dogrzewacza", "el. tillegsvarme", "Chauffage auxiliaire", "ilave ısıtıcı durumu", "riscaldatori addizionali", "pomocného ohrievača", "pomocného topení")
MAKE_TRANSLATION(auxHeaterOnly, "auxheateronly", "aux heater only", "nur Zusatzheizer", "Alleen bijverwarming", "Eltillskott Enbart", "tylko dogrzewacz", "kun el tilleggsvarme", "Que chauffage auxiliaire", "sadece ilave ısıtıvcı", "solo riscaldatori addizionali", "iba pomocný ohrievač", "pouze pomocné topení")
MAKE_TRANSLATION(auxHeaterDelay, "auxheaterdelay", "aux heater on delay", "Zusatzheizer verzögert ein", "Bijverw. vertraagd aan", "Eltillskottfördröjning på", "opóźnienie włączenia dogrzewacza", "Tilleggsvarmer forsinket på", "Chauff app tempo marche", "ilave ısıtıcı beklemede", "ritardo riscaldatori addizionali", "oneskorenie prídavného ohrievača", "zpoždění zapnutí pomocného topení")
MAKE_TRANSLATION(silentMode, "silentmode", "silent mode", "Silentmodus", "Stiller gebruik", "Tyst läge", "tryb cichy", "stille modus", "Fct silencieux", "sessiz mod", "modalità silenziosa", "tichý režim", "tichý režim")
MAKE_TRANSLATION(minTempSilent, "mintempsilent", "min outside temp for silent mode", "Minimale Außentemperatur Silentmodus", "Stiller gebruik min. buitentemp", "Tyst läge min temp", "minimalna temperatura zewnętrzna dla trybu cichego", "atille modus min temp", "Fct silencieux: Temp. extérieure min.", "sessiz mod için min. dış ortam sıcaklığı", "modalità silenziosa temperatura esterna minima", "min. vonkajšia teplota pre tichý režim", "minimální venkovní teplota pro tichý režim")
MAKE_TRANSLATION(silentMode, "silentmode", "silent mode", "Silentmodus", "Stiller gebruik", "Tyst drift", "tryb cichy", "stille modus", "Fct silencieux", "sessiz mod", "modalità silenziosa", "tichý režim", "tichý režim")
MAKE_TRANSLATION(minTempSilent, "mintempsilent", "min outside temp for silent mode", "Minimale Außentemperatur Silentmodus", "Stiller gebruik min. buitentemp", "Tyst drift min temp", "minimalna temperatura zewnętrzna dla trybu cichego", "atille modus min temp", "Fct silencieux: Temp. extérieure min.", "sessiz mod için min. dış ortam sıcaklığı", "modalità silenziosa temperatura esterna minima", "min. vonkajšia teplota pre tichý režim", "minimální venkovní teplota pro tichý režim")
MAKE_TRANSLATION(tempParMode, "tempparmode", "outside temp parallel mode", "Heizstab Parallelbetrieb", "Buitentemp. parallelbedr", "Parallelläge Utomhustemp.", "maksymalna temperatura zewnętrzna dla dogrzewacza", "", "Temp. ext. fct parallèle", "paralel mod dış ortam sıcaklığı", "modalità parallela temperatura esterna", "paralelný režim mimo teploty", "venkovní teplota pro paralelní režim") // TODO translate
MAKE_TRANSLATION(auxHeatMixValve, "auxheatmix", "aux heater mixing valve", "Mischventil Zusatzheizer", "Bijverwarming menger", "Eltilskott Blandarventil", "mieszacz dogrzewacza", "eltilskudd blandeventil", "Chauffage auxiliaire mélangeur", "ilave ısıtıcı karışım vanası", "miscela riscaldatori addizionali", "zmiešavací ventil pomocného ohrievača", "směšovací ventil pomocného topení")
MAKE_TRANSLATION(hpHystHeat, "hphystheat", "on/off hyst heat", "Schalthysterese Heizen", "Aan/uit-hysteresis in verw. bedrijf", "Hstereses Uppvärm.", "histereza wł./wył. ogrzewania", "På/av hysterese Oppvar.", "Hystérésis Marche en mode chauffage", "ısıtma gecikmesi", "isteresi di commutazione riscaldamento", "zapnutie/vypnutie hyst ohrevu", "hystereze zapnutí/vypnutí pro topení")
MAKE_TRANSLATION(hpHystCool, "hphystcool", "on/off hyst cool", "Schalthysterese Kühlen", "Aan/uit-hysteresis in koelbedrijf", "Hystereses Kyla", "histereza wł./wył. chłodzenia", "hystrese kjøling", "Hystérésis Marche en mode refroidissement", "soğutma gecikmesi", "isteresi di commutazione raffreddamento", "zapnutie/vypnutie hyst chladenia", "hystereze zapnutí/vypnutí pro chlazení")
MAKE_TRANSLATION(hpHystPool, "hphystpool", "on/off hyst pool", "Schalthysterese Pool", "an/uit-hysteresis in zwembadbedri", "Hystereses Pool", "histereza wł./wył. podgrzewania basenu", "hystrese basseng", "Hystérésis Marche en mode piscine", "havuz gecikmesi", "isteresi di commutazione piscina", "zapnutie/vypnutie hyst bazénu", "hystereze zapnutí/vypnutí pro bazén")
MAKE_TRANSLATION(tempDiffHeat, "tempdiffheat", "temp diff TC3/TC0 heat", "Temp.diff. TC3/TC0 Heizen", "Temp.vers. TC3/TC0 verw", "Delta(T) TC3/TC0 Uppvärm.", "różnica temperatur TC3/TC0 w trakcie ogrzewania", "temp. diff. TC3/TC0 oppvarm", "Delta T TC3/TC0 Chauff", "TC3-TC0 ısıtma sıcaklık farkı", "Delta T riscaldamento TC3/TC0", "teplotný rozdiel TC3/TC0 tepla", "rozdíl teplot TC3/TC0 pro topení")
MAKE_TRANSLATION(tempDiffCool, "tempdiffcool", "temp diff TC3/TC0 cool", "Temp.diff. TC3/TC0 Kühlen", "Temp.vers. TC3/TC0 koel.", "Delta(T) TC3/TC0 Kyla", "różnica temperatur TC3/TC0 w trakcie chłodzenia", "temp. diff. TC3/TC0 kjøling", "Delta T TC3/TC0 Refroid.", "TC3-TC0 soğutma sıcaklık farkı", "Delta T raffreddamento TC3/TC0", "teplotný rozdiel TC3/TC0 chladenie", "rozdíl teplot TC3/TC0 pro chlazení")
MAKE_TRANSLATION(silentFrom, "silentfrom", "silent mode from", "Silentmodus Start", "Start stille modus", "", "początek trybu cichego", "stillemodus starter", "", "sessiz mod başlangıcı", "avvio della modalità silenziosa", "tichý režim od", "tichý režim od") // TODO translate
MAKE_TRANSLATION(silentTo, "silentto", "silent mode to", "Silentmodus Ende", "Einde stille modus", "", "koniec trybu cichego", "komfortmodus av", "", "sessiz mod bitişi", "spegnere modalità silenziosa", "tichý režim do", "tichý režim do") // TODO translate
MAKE_TRANSLATION(auxHeatMixValve, "auxheatmix", "aux heater mixing valve", "Mischventil Zusatzheizer", "Bijverwarming menger", "Eltillskott Blandarventil", "mieszacz dogrzewacza", "eltilskudd blandeventil", "Chauffage auxiliaire mélangeur", "ilave ısıtıcı karışım vanası", "miscela riscaldatori addizionali", "zmiešavací ventil pomocného ohrievača", "směšovací ventil pomocného topení")
MAKE_TRANSLATION(hpHystHeat, "hphystheat", "on/off hyst heat", "Schalthysterese Heizen", "Aan/uit-hysteresis in verw. bedrijf", "Hysteres Uppvärmning", "histereza wł./wył. ogrzewania", "På/av hysterese Oppvar.", "Hystérésis Marche en mode chauffage", "ısıtma gecikmesi", "isteresi di commutazione riscaldamento", "zapnutie/vypnutie hyst ohrevu", "hystereze zapnutí/vypnutí pro topení")
MAKE_TRANSLATION(hpHystCool, "hphystcool", "on/off hyst cool", "Schalthysterese Kühlen", "Aan/uit-hysteresis in koelbedrijf", "Hysteres Kyla", "histereza wł./wył. chłodzenia", "hystrese kjøling", "Hystérésis Marche en mode refroidissement", "soğutma gecikmesi", "isteresi di commutazione raffreddamento", "zapnutie/vypnutie hyst chladenia", "hystereze zapnutí/vypnutí pro chlazení")
MAKE_TRANSLATION(hpHystPool, "hphystpool", "on/off hyst pool", "Schalthysterese Pool", "an/uit-hysteresis in zwembadbedri", "Hysteres Pool", "histereza wł./wył. podgrzewania basenu", "hystrese basseng", "Hystérésis Marche en mode piscine", "havuz gecikmesi", "isteresi di commutazione piscina", "zapnutie/vypnutie hyst bazénu", "hystereze zapnutí/vypnutí pro bazén")
MAKE_TRANSLATION(tempDiffHeat, "tempdiffheat", "temp diff TC3/TC0 heat", "Temp.diff. TC3/TC0 Heizen", "Temp.vers. TC3/TC0 verw", "Temperaturskillnad TC3/TC0 Uppvärmning", "różnica temperatur TC3/TC0 w trakcie ogrzewania", "temp. diff. TC3/TC0 oppvarm", "Delta T TC3/TC0 Chauff", "TC3-TC0 ısıtma sıcaklık farkı", "Delta T riscaldamento TC3/TC0", "teplotný rozdiel TC3/TC0 tepla", "rozdíl teplot TC3/TC0 pro topení")
MAKE_TRANSLATION(tempDiffCool, "tempdiffcool", "temp diff TC3/TC0 cool", "Temp.diff. TC3/TC0 Kühlen", "Temp.vers. TC3/TC0 koel.", "Temperaturskillnad TC3/TC0 Kyla", "różnica temperatur TC3/TC0 w trakcie chłodzenia", "temp. diff. TC3/TC0 kjøling", "Delta T TC3/TC0 Refroid.", "TC3-TC0 soğutma sıcaklık farkı", "Delta T raffreddamento TC3/TC0", "teplotný rozdiel TC3/TC0 chladenie", "rozdíl teplot TC3/TC0 pro chlazení")
MAKE_TRANSLATION(silentFrom, "silentfrom", "silent mode from", "Silentmodus Start", "Start stille modus", "Tyst drift starttid", "początek trybu cichego", "stillemodus starter", "", "sessiz mod başlangıcı", "avvio della modalità silenziosa", "tichý režim od", "tichý režim od") // TODO translate
MAKE_TRANSLATION(silentTo, "silentto", "silent mode to", "Silentmodus Ende", "Einde stille modus", "Tyst drift stopptid", "koniec trybu cichego", "komfortmodus av", "", "sessiz mod bitişi", "spegnere modalità silenziosa", "tichý režim do", "tichý režim do") // TODO translate
MAKE_TRANSLATION(wwComfOffTemp, "comfoff", "comfort switch off", "Komfort Ausschalttemp.", "Comfort Uitschakeltemp.", "Komfortläge avstängingstemp.", "temperatura wyłączania w trybie komfort", "eco modus utkoblingstem", "Confort Temp. d'arrêt", "konfor kapalı", "spegnimento modalità comfort", "komfortné vypnutie", "komfortní vypnutí")
MAKE_TRANSLATION(wwEcoOffTemp, "ecooff", "eco switch off", "ECO Ausschalttemp.", "Eco Uitschakeltemp.", "Ekoläge avstängningstemp.", "temperatura wyłączania w trybie eko", "Øko avstengningstemp.", "Eco Temp. d'arrêt", "eko kapalı", "spegnimento modalità ECO", "eko vypínač", "eko vypnutí")
MAKE_TRANSLATION(wwEcoPlusOffTemp, "ecoplusoff", "eco+ switch off", "ECO+ Ausschalttemp.", "Eco+ Uitschakeltemp.", "Eko+ avstängningstemp.", "temperatura wyłączania w trybie eko+", "Øko+ avstengningstemp.", "Eco+ Temp. d'arrêt", "eko+ kapalı", "spegnimento modalità ECO+", "eko+ vypnutie", "eko+ vypnutí")
MAKE_TRANSLATION(wwComfDiffTemp, "comfdiff", "comfort diff", "Komfort Differenztemp.", "", "", "", "", "", "", "", "Komfortný rozdiel teploty", "komfortní rozdíl") // TODO translate
MAKE_TRANSLATION(wwEcoDiffTemp, "ecodiff", "eco diff", "ECO Differenztemp.", "", "", "", "", "", "", "", "ECO rozdiel teploty", "eko rozdíl") // TODO translate
MAKE_TRANSLATION(wwEcoPlusDiffTemp, "ecoplusdiff", "eco+ diff", "ECO+ Differenztemp.", "", "", "", "", "", "", "", "ECO+ rozdiel teploty", "eko+ rozdíl") // TODO translate
MAKE_TRANSLATION(wwComfStopTemp, "comfstop", "comfort stop temp", "Komfort Stopptemp.", "", "", "", "", "", "", "", "komfortná stop teplota", "komfortní teplota vypnutí") // TODO translate
MAKE_TRANSLATION(wwEcoStopTemp, "ecostop", "eco stop temp", "ECO Stopptemp.", "", "", "", "", "", "", "", "ECO stop teplota", "eko teplota vypnutí") // TODO translate
MAKE_TRANSLATION(wwEcoPlusStopTemp, "ecoplusstop", "eco+ stop temp", "ECO+ Stopptemp.", "", "", "", "", "", "", "", "ECO+ stop teplota", "eko+ teplota vypnutí") // TODO translate
MAKE_TRANSLATION(wwComfOffTemp, "comfoff", "comfort switch off", "Komfort Ausschalttemp.", "Comfort Uitschakeltemp.", "Komfort frånkopplingstemperatur", "temperatura wyłączania w trybie komfort", "eco modus utkoblingstem", "Confort Temp. d'arrêt", "konfor kapalı", "spegnimento modalità comfort", "komfortné vypnutie", "komfortní vypnutí")
MAKE_TRANSLATION(wwEcoOffTemp, "ecooff", "eco switch off", "ECO Ausschalttemp.", "Eco Uitschakeltemp.", "Eko frånkopplingstemperatur", "temperatura wyłączania w trybie eko", "Øko avstengningstemp.", "Eco Temp. d'arrêt", "eko kapalı", "spegnimento modalità ECO", "eko vypínač", "eko vypnutí")
MAKE_TRANSLATION(wwEcoPlusOffTemp, "ecoplusoff", "eco+ switch off", "ECO+ Ausschalttemp.", "Eco+ Uitschakeltemp.", "Eko+ frånkopplingstemperatur", "temperatura wyłączania w trybie eko+", "Øko+ avstengningstemp.", "Eco+ Temp. d'arrêt", "eko+ kapalı", "spegnimento modalità ECO+", "eko+ vypnutie", "eko+ vypnutí")
MAKE_TRANSLATION(wwComfDiffTemp, "comfdiff", "comfort diff", "Komfort Differenztemp.", "", "Komfort temperaturskillnad", "", "", "", "", "", "Komfortný rozdiel teploty", "komfortní rozdíl") // TODO translate
MAKE_TRANSLATION(wwEcoDiffTemp, "ecodiff", "eco diff", "ECO Differenztemp.", "", "Eko temperaturskillnad", "", "", "", "", "", "ECO rozdiel teploty", "eko rozdíl") // TODO translate
MAKE_TRANSLATION(wwEcoPlusDiffTemp, "ecoplusdiff", "eco+ diff", "ECO+ Differenztemp.", "", "Eko+ temperaturskillnad", "", "", "", "", "", "ECO+ rozdiel teploty", "eko+ rozdíl") // TODO translate
MAKE_TRANSLATION(wwComfStopTemp, "comfstop", "comfort stop temp", "Komfort Stopptemp.", "", "Komfort stopptemperatur", "", "", "", "", "", "komfortná stop teplota", "komfortní teplota vypnutí") // TODO translate
MAKE_TRANSLATION(wwEcoStopTemp, "ecostop", "eco stop temp", "ECO Stopptemp.", "", "Eko stopptemperatur", "", "", "", "", "", "ECO stop teplota", "eko teplota vypnutí") // TODO translate
MAKE_TRANSLATION(wwEcoPlusStopTemp, "ecoplusstop", "eco+ stop temp", "ECO+ Stopptemp.", "", "Eko+ stopptemperatur", "", "", "", "", "", "ECO+ stop teplota", "eko+ teplota vypnutí") // TODO translate
MAKE_TRANSLATION(auxHeatMode, "auxheatrmode", "aux heater mode", "Zusatzheizungsmodus", "Modus bijverwarmer", "", "tryb pracy dogrzewacza po blokadzie z Zakładu Energetycznego", "tilleggsvarmer modus", "", "ilave ısıtıcı modu", "modalità riscaldatore addizionale", "režim pomocného ohrievača", "režim pomocného topení") // TODO translate
MAKE_TRANSLATION(auxMaxLimit, "auxmaxlimit", "aux heater max limit", "Zusatzheizer max. Grenze", "Bijverwarmer grensinstelling maximaal", "", "dogrzewacz, maksymalny limit", "tillegsvarme maksgrense", "ilave ısıtıcı maks limit", "limite massimo riscaldatore addizionale", "maximálny limit pomocného ohrievača", "maximální limit pomocného topení") // TODO translate
MAKE_TRANSLATION(auxLimitStart, "auxlimitstart", "aux heater limit start", "Zusatzheizer Grenze Start", "Bijverwarmer grens voor start", "", "dogrzewacz, początek ograniczenia", "tillegsvarme startgrense", "ilave ısıtıcı limir başlangıcı", "avvio limite massimo riscaldatore addizionale", "spustenie limitu pomocného ohrievača", "startovací limit pomocného topení") // TODO translate
MAKE_TRANSLATION(manDefrost, "mandefrost", "manual defrost", "Manuelle Enteisung", "Handmatige ontdooicyclus", "", "ręczne odladzanie", "manuell avisning", "", "manuel buz çözme", "sbrinamento manuale", "manuálne odmrazovanie", "ruční odmrazování") // TODO translate
MAKE_TRANSLATION(pvCooling, "pvcooling", "cooling only with PV", "Kühlen nur mit PV", "Koelen alleen met solar PV", "", "chłodzenie tylko z PV", "kjøling med solpanel", "", "sadece PV ile soğutma", "solo raffrescamento con solare", "Chladenie len s FV", "chlazení pouze s FV") // TODO translate
MAKE_TRANSLATION(hpCircPumpWw, "hpcircpump", "circulation pump available during dhw", "Zirkulation möglich bei WW-Bereitung", "Circulatiepomp WP beschikbaar tijdens ww", "", "pompa cyrkulacji dostępna w trakcie c.w.u.", "sirkulasjonspumpe tilgjengelig under varmtvann", "", "SKS esnasında sirkülasyon pompasu uygun", "pompa di circolazione disponibile durante ACS", "obehové čerpadlo k dispozícii počas TÚV", "oběhové čerpadlo dostupné během TUV") // TODO translate
MAKE_TRANSLATION(vp_cooling, "vpcooling", "valve/pump cooling", "Ventil/Pumpe für Kühlen", "Klep koeling", "", "zawór/pompa chłodzenia", "varmepumpe kjøling", "", "vana/pompa soğuyor", "valvola/pompa raffrescamento", "chladenie ventilu/čerpadla", "ventil/čerpadlo chlazení") // TODO translate
MAKE_TRANSLATION(VC0valve, "vc0valve", "VC0 valve", "VC0 Ventil", "Klep VC0", "", "zawór VC0", "vc0 ventil", "", "VC0 vana", "valvola VC0", "VC0 ventil", "ventil VC0") // TODO translate
MAKE_TRANSLATION(primePump, "primepump", "primary heatpump", "Hauptpumpe", "Hoofdpomp", "", "główna pompa ciepła", "primærpumpe", "", "ana ısı pompası", "pompa principale riscaldamento", "primárne tepelné čerpadlo", "primární tepelný čerpadlo") // TODO translate
MAKE_TRANSLATION(primePumpMod, "primepumpmod", "primary heatpump modulation", "Modulation Hauptpumpe", "Modulatie hoofdpomp", "", "wysterowanie głównej pompy ciepła", "primærpumpelast", "", "ana ısı pompası modülasyon", "pompa principale modulazione riscaldamento", "primárna modulácia tepelného čerpadla", "modulace primárního tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(hp3wayValve, "hp3way", "3-way valve", "3-Wege-Ventil", "3-weg klep", "", "zawór 3-drogowy pompy ciepła", "3-veisventil", "", "3 yollu vana", "valvola 3-vie", "3-cestný ventil", "3-cestný ventil") // TODO translate
MAKE_TRANSLATION(hp4wayValve, "hp4way", "4-way valve (VR4)", "4-Wege-Ventil (VR4)", "4-weg klep (VR4)", "(VR4)", "zawór 4-drogowy pompy ciepła (VR4)", "4-veisventil (VR4)", "(VR4)", "4 yollu vana (VR4)", "valvola 4-vie (VR4)", "4-cestný ventil (VR4)", "4-cestný ventil (VR4)") // TODO translate
MAKE_TRANSLATION(elHeatStep1, "elheatstep1", "el. heater step 1", "El. Heizer Stufe 1", "Electrische bijverwarmer niveau 1", "", "dogrzewacz poziom 1", "el-kolbe steg 1", "", "el.ısıtıcı adım 1", "riscaldatore elettrico livello 1", "krok 1 elektrického ohrievača", "elektrický ohřívač stupeň 1") // TODO translate
MAKE_TRANSLATION(elHeatStep2, "elheatstep2", "el. heater step 2", "El. Heizer Stufe 2", "Electrische bijverwarmer niveau 2", "", "dogrzewacz poziom 2", "el-kolbe steg 2", "", "el.ısıtıcı adım 2", "riscaldatore elettrico livello 2", "krok 2 elektrického ohrievača", "elektrický ohřívač stupeň 2") // TODO translate
MAKE_TRANSLATION(elHeatStep3, "elheatstep3", "el. heater step 3", "El. Heizer Stufe 3", "Electrische bijverwarmer niveau 3", "", "dogrzewacz poziom 3", "el-kolbe steg 3", "", "el.ısıtıcı adım 3", "riscaldatore elettrico livello 3", "krok 3 elektrického ohrievača", "elektrický ohřívač stupeň 3") // TODO translate
MAKE_TRANSLATION(wwAlternatingOper, "alternatingop", "alternating operation", "Wechselbetrieb", "Wisselbedrijf ww", "", "praca naprzemienna", "alternativ drift", "", "sıcak kullanım suyu alternatif işletim", "funzionamento alternato", "striedavá prevádzka", "střídavý provoz") // TODO translate
MAKE_TRANSLATION(wwAltOpPrioHeat, "altopprioheat", "prioritise heating during dhw", "Heizen bevorzugt vor WW", "Proriteit verwarming boven ww", "", "czas na ogrzewanie w trakcie c.w.u", "prioritert oppvarmning", "", "sıcak kullanım suyu esnasında ısıtmayı öne al", "dare la priorità al riscaldamento durante l'ACS", "Uprednostniť ohrev počas TÚV", "prioritizace vytápění během TUV") // TODO translate
MAKE_TRANSLATION(wwAltOpPrioWw, "altopprio", "prioritise dhw during heating", "bevorzugt vor Heizen", "Prioriteit ww boven verwarming", "", "czas na c.w.u w trakcie ogrzewania", "prioritert varmtvann", "", "ısıtma esnasında sıcak kullanım suyunu öne al", "dare priorità all'acqua calda durante il riscaldamento", "uprednostniť TÚV počas ohrevu", "prioritizace TUV během vytápění") // TODO translate
MAKE_TRANSLATION(hpEA0, "hpea0", "condensate reservoir heating (EA0)", "Heizung Kondensatwanne (EA0)", "", "", "ogrzewanie zbiornika kondensatu (EA0)", "", "", "", "", "ohrievanie zásobníka kondenzátu (EA0)", "ohřev kondenzátní nádrže (EA0)") // TODO translate
MAKE_TRANSLATION(boost, "boost", "boost mode", "Boost", "", "", "tryb wzmocnienia (boost)", "", "", "", "", "boost režim", "režim boost") // TODO translate
MAKE_TRANSLATION(boosttime, "boosttime", "boost time", "Boost-Dauer", "", "", "czas trwania wzmocnienia", "", "", "", "", "čas trvania posilnenia", "čas trvání režimu boost") // TODO translate
MAKE_TRANSLATION(hpPumpMode, "hppumpmode", "primary heatpump mode", "primärer Wärmepumpenmodus", "", "", "tryb pracy głównej pompy ciepła", "", "", "", "", "režim primárneho tepelného čerpadla", "režim primárního tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(instantstart, "instantstart", "instant start", "Sofortstart", "", "", "natychmiastowy start", "", "", "", "", "okamžité spustenie", "okamžité spuštění") // TODO translate
MAKE_TRANSLATION(heatondelay, "heatondelay", "heat-on delay", "Einschaltverzögerung Heizen", "", "", "opóźnienie włączania ogrzewania", "", "", "", "", "Oneskorenie zapnutia kúreni", "zpoždění zapnutí topení") // TODO translate
MAKE_TRANSLATION(heatoffdelay, "heatoffdelay", "heat-off delay", "Ausschaltverzögerung Heizen", "", "", "opóźnienie włączania ogrzewania", "", "", "", "", "Oneskorenie vypnutia kúrenia", "zpoždění vypnutí topení") // TODO translate
MAKE_TRANSLATION(hpSetDiffPress, "hpsetdiffpress", "set differential pressure", "Pumpensolldruck", "", "", "różnica ciśnień", "", "", "", "", "nastaviť diferenčný tlak", "nastavení rozdílového tlaku") // TODO translate
MAKE_TRANSLATION(hpFan, "fan", "fan", "Lüfter", "", "", "wentylator", "", "", "", "", "ventilátor", "ventilátor") // TODO translate
MAKE_TRANSLATION(hpShutdown, "shutdown", "shutdown", "Abschalten", "", "", "wyłączenie", "", "", "", "", "vypnutie", "vypnutí") // TODO translate
MAKE_TRANSLATION(pc0Flow, "pc0flow", "Flow PC0", "Durchfluss PC0", "", "", "", "", "", "", "", "prietok PC0", "průtok PC0") // TODO translate
MAKE_TRANSLATION(pc1Flow, "pc1flow", "Flow PC1", "Durchfluss PC1", "", "", "", "", "", "", "", "prietok PC1", "průtok PC1") // TODO translate
MAKE_TRANSLATION(pc1On, "pc1on", "PC1", "PC1", "", "", "", "", "", "", "", "PC1", "PC1") // TODO translate
MAKE_TRANSLATION(pc1Rate, "pc1rate", "PC1 rate", "PC1 Rate", "", "", "", "", "", "", "", "sadzba PC1", "míra PC1") // TODO translate
MAKE_TRANSLATION(auxHeatMode, "auxheatrmode", "aux heater mode", "Zusatzheizungsmodus", "Modus bijverwarmer", "Eltillskott Läge", "tryb pracy dogrzewacza po blokadzie z Zakładu Energetycznego", "tilleggsvarmer modus", "", "ilave ısıtıcı modu", "modalità riscaldatore addizionale", "režim pomocného ohrievača", "režim pomocného topení") // TODO translate
MAKE_TRANSLATION(auxMaxLimit, "auxmaxlimit", "aux heater max limit", "Zusatzheizer max. Grenze", "Bijverwarmer grensinstelling maximaal", "Eltillskott max begränsning", "dogrzewacz, maksymalny limit", "tillegsvarme maksgrense", "ilave ısıtıcı maks limit", "limite massimo riscaldatore addizionale", "maximálny limit pomocného ohrievača", "maximální limit pomocného topení")
MAKE_TRANSLATION(auxLimitStart, "auxlimitstart", "aux heater limit start", "Zusatzheizer Grenze Start", "Bijverwarmer grens voor start", "Eltillskott begränsningsstart", "dogrzewacz, początek ograniczenia", "tillegsvarme startgrense", "ilave ısıtıcı limir başlangıcı", "avvio limite massimo riscaldatore addizionale", "spustenie limitu pomocného ohrievača", "startovací limit pomocného topení")
MAKE_TRANSLATION(manDefrost, "mandefrost", "manual defrost", "Manuelle Enteisung", "Handmatige ontdooicyclus", "Manuell avfrostning", "ręczne odladzanie", "manuell avisning", "", "manuel buz çözme", "sbrinamento manuale", "manuálne odmrazovanie", "ruční odmrazování") // TODO translate
MAKE_TRANSLATION(pvCooling, "pvcooling", "cooling only with PV", "Kühlen nur mit PV", "Koelen alleen met solar PV", "Kyla endast med solpanel", "chłodzenie tylko z PV", "kjøling med solpanel", "", "sadece PV ile soğutma", "solo raffrescamento con solare", "Chladenie len s FV", "chlazení pouze s FV") // TODO translate
MAKE_TRANSLATION(hpCircPumpWw, "hpcircpump", "circulation pump available during dhw", "Zirkulation möglich bei WW-Bereitung", "Circulatiepomp WP beschikbaar tijdens ww", "Värmebärarpump på vid varmvattenberedning", "pompa cyrkulacji dostępna w trakcie c.w.u.", "sirkulasjonspumpe tilgjengelig under varmtvann", "", "SKS esnasında sirkülasyon pompasu uygun", "pompa di circolazione disponibile durante ACS", "obehové čerpadlo k dispozícii počas TÚV", "oběhové čerpadlo dostupné během TUV") // TODO translate
MAKE_TRANSLATION(vp_cooling, "vpcooling", "valve/pump cooling", "Ventil/Pumpe für Kühlen", "Klep koeling", "Ventil/Pump kyla", "zawór/pompa chłodzenia", "varmepumpe kjøling", "", "vana/pompa soğuyor", "valvola/pompa raffrescamento", "chladenie ventilu/čerpadla", "ventil/čerpadlo chlazení") // TODO translate
MAKE_TRANSLATION(VC0valve, "vc0valve", "VC0 valve", "VC0 Ventil", "Klep VC0", "VC0 Ventil", "zawór VC0", "vc0 ventil", "", "VC0 vana", "valvola VC0", "VC0 ventil", "ventil VC0") // TODO translate
MAKE_TRANSLATION(primePump, "primepump", "primary heatpump", "Hauptpumpe", "Hoofdpomp", "Primär Pump", "główna pompa ciepła", "primærpumpe", "", "ana ısı pompası", "pompa principale riscaldamento", "primárne tepelné čerpadlo", "primární tepelný čerpadlo") // TODO translate
MAKE_TRANSLATION(primePumpMod, "primepumpmod", "primary heatpump modulation", "Modulation Hauptpumpe", "Modulatie hoofdpomp", "Modulation Primär Pump", "wysterowanie głównej pompy ciepła", "primærpumpelast", "", "ana ısı pompası modülasyon", "pompa principale modulazione riscaldamento", "primárna modulácia tepelného čerpadla", "modulace primárního tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(hp3wayValve, "hp3way", "3-way valve", "3-Wege-Ventil", "3-weg klep", "3-vägsventil", "zawór 3-drogowy pompy ciepła", "3-veisventil", "", "3 yollu vana", "valvola 3-vie", "3-cestný ventil", "3-cestný ventil") // TODO translate
MAKE_TRANSLATION(hp4wayValve, "hp4way", "4-way valve (VR4)", "4-Wege-Ventil (VR4)", "4-weg klep (VR4)", "4-vägsventil (VR4)", "zawór 4-drogowy pompy ciepła (VR4)", "4-veisventil (VR4)", "(VR4)", "4 yollu vana (VR4)", "valvola 4-vie (VR4)", "4-cestný ventil (VR4)", "4-cestný ventil (VR4)") // TODO translate
MAKE_TRANSLATION(elHeatStep1, "elheatstep1", "el. heater step 1", "El. Heizer Stufe 1", "Electrische bijverwarmer niveau 1", "Eltillskott Steg 1", "dogrzewacz poziom 1", "el-kolbe steg 1", "", "el.ısıtıcı adım 1", "riscaldatore elettrico livello 1", "krok 1 elektrického ohrievača", "elektrický ohřívač stupeň 1") // TODO translate
MAKE_TRANSLATION(elHeatStep2, "elheatstep2", "el. heater step 2", "El. Heizer Stufe 2", "Electrische bijverwarmer niveau 2", "Eltillskott Steg 2", "dogrzewacz poziom 2", "el-kolbe steg 2", "", "el.ısıtıcı adım 2", "riscaldatore elettrico livello 2", "krok 2 elektrického ohrievača", "elektrický ohřívač stupeň 2") // TODO translate
MAKE_TRANSLATION(elHeatStep3, "elheatstep3", "el. heater step 3", "El. Heizer Stufe 3", "Electrische bijverwarmer niveau 3", "Eltillskott Steg 3", "dogrzewacz poziom 3", "el-kolbe steg 3", "", "el.ısıtıcı adım 3", "riscaldatore elettrico livello 3", "krok 3 elektrického ohrievača", "elektrický ohřívač stupeň 3") // TODO translate
MAKE_TRANSLATION(wwAlternatingOper, "alternatingop", "alternating operation", "Wechselbetrieb", "Wisselbedrijf ww", "Växeldrift", "praca naprzemienna", "alternativ drift", "", "sıcak kullanım suyu alternatif işletim", "funzionamento alternato", "striedavá prevádzka", "střídavý provoz") // TODO translate
MAKE_TRANSLATION(wwAltOpPrioHeat, "altopprioheat", "prioritise heating during dhw", "Heizen bevorzugt vor WW", "Proriteit verwarming boven ww", "Varmvattenprioritet", "czas na ogrzewanie w trakcie c.w.u", "prioritert oppvarmning", "", "sıcak kullanım suyu esnasında ısıtmayı öne al", "dare la priorità al riscaldamento durante l'ACS", "Uprednostniť ohrev počas TÚV", "prioritizace vytápění během TUV") // TODO translate
MAKE_TRANSLATION(wwAltOpPrioWw, "altopprio", "prioritise dhw during heating", "bevorzugt vor Heizen", "Prioriteit ww boven verwarming", "Värmeprioritet", "czas na c.w.u w trakcie ogrzewania", "prioritert varmtvann", "", "ısıtma esnasında sıcak kullanım suyunu öne al", "dare priorità all'acqua calda durante il riscaldamento", "uprednostniť TÚV počas ohrevu", "prioritizace TUV během vytápění") // TODO translate
MAKE_TRANSLATION(hpEA0, "hpea0", "condensate reservoir heating (EA0)", "Heizung Kondensatwanne (EA0)", "", "Värme kondenstråg (EA0)", "ogrzewanie zbiornika kondensatu (EA0)", "", "", "", "", "ohrievanie zásobníka kondenzátu (EA0)", "ohřev kondenzátní nádrže (EA0)") // TODO translate
MAKE_TRANSLATION(boost, "boost", "boost mode", "Boost", "", "Boost-läge", "tryb wzmocnienia (boost)", "", "", "", "", "boost režim", "režim boost") // TODO translate
MAKE_TRANSLATION(boosttime, "boosttime", "boost time", "Boost-Dauer", "", "Boost-tid", "czas trwania wzmocnienia", "", "", "", "", "čas trvania posilnenia", "čas trvání režimu boost") // TODO translate
MAKE_TRANSLATION(hpPumpMode, "hppumpmode", "primary heatpump mode", "primärer Wärmepumpenmodus", "", "Driftläge värmebärarpump", "tryb pracy głównej pompy ciepła", "", "", "", "", "režim primárneho tepelného čerpadla", "režim primárního tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(instantstart, "instantstart", "instant start", "Sofortstart", "", "Gränsvärde direktstart värme", "natychmiastowy start", "", "", "", "", "okamžité spustenie", "okamžité spuštění") // TODO translate
MAKE_TRANSLATION(heatondelay, "heatondelay", "heat-on delay", "Einschaltverzögerung Heizen", "", "Inkopplingsfördröjning värme", "opóźnienie włączania ogrzewania", "", "", "", "", "Oneskorenie zapnutia kúreni", "zpoždění zapnutí topení") // TODO translate
MAKE_TRANSLATION(heatoffdelay, "heatoffdelay", "heat-off delay", "Ausschaltverzögerung Heizen", "", "Frånkopplingsfördröjning värme", "opóźnienie włączania ogrzewania", "", "", "", "", "Oneskorenie vypnutia kúrenia", "zpoždění vypnutí topení") // TODO translate
MAKE_TRANSLATION(hpSetDiffPress, "hpsetdiffpress", "set differential pressure", "Pumpensolldruck", "", "VP Tryckskillnad", "różnica ciśnień", "", "", "", "", "nastaviť diferenčný tlak", "nastavení rozdílového tlaku") // TODO translate
MAKE_TRANSLATION(hpFan, "fan", "fan", "Lüfter", "", "Fläkt", "wentylator", "", "", "", "", "ventilátor", "ventilátor") // TODO translate
MAKE_TRANSLATION(hpShutdown, "shutdown", "shutdown", "Abschalten", "", "Stäng av", "wyłączenie", "", "", "", "", "vypnutie", "vypnutí") // TODO translate
MAKE_TRANSLATION(pc0Flow, "pc0flow", "Flow PC0", "Durchfluss PC0", "", "Flöde värmebärarpump", "", "", "", "", "", "prietok PC0", "průtok PC0") // TODO translate
MAKE_TRANSLATION(pc1Flow, "pc1flow", "Flow PC1", "Durchfluss PC1", "", "Flöde cirkulationspump", "", "", "", "", "", "prietok PC1", "průtok PC1") // TODO translate
MAKE_TRANSLATION(pc1On, "pc1on", "PC1", "PC1", "", "Cirkulationspump", "", "", "", "", "", "PC1", "PC1") // TODO translate
MAKE_TRANSLATION(pc1Rate, "pc1rate", "PC1 rate", "PC1 Rate", "", "Hastighet cirkulationspump", "", "", "", "", "", "sadzba PC1", "míra PC1") // TODO translate
// hybrid heatpump
MAKE_TRANSLATION(hybridStrategy, "hybridstrategy", "hybrid control strategy", "Hybrid-Steuerungsstrategie", "Hybride strategie", "Hybrid kontrollstrategi", "strategia sterowania hybrydowego", "hybrid kontrollstrategi", "stratégie contrôle hybride", "hibrit kontrol stratejisi", "strategia comtrollo ibrido", "hybridná stratégia riadenia", "strategie hybridního řízení")
MAKE_TRANSLATION(switchOverTemp, "switchovertemp", "outside switchover temperature", "Außentemperatur für Umschaltung", "Schakeltemperatuur buitentemperatuur", "Utomhus Omställningstemperatur", "zewnętrzna temperatura przełączania", "utendørstemp styring", "basculement par température extérieure", "geçiş için dış sıcaklık", "temperatura esterna per commutazione", "vonkajšia prepínacia teplota", "teplota přepnutí venku")
MAKE_TRANSLATION(energyCostRatio, "energycostratio", "energy cost ratio", "Energie-/Kostenverhältnis", "Energiekostenratio", "Energi/Kostnads-förhållande", "współczynnik energia/koszt", "energi/kostnads forhold", "ratio coût énergie", "enerji maliyet oranı", "rapporto energia/costo", "pomer nákladov na energiu", "poměr nákladů na energii")
MAKE_TRANSLATION(fossileFactor, "fossilefactor", "fossile energy factor", "Energiefaktor Fossil", "Energiefactor fossiele brandstof", "Energifaktor fossilenergi", "udział energii z paliw kopalnych", "energifaktor fossilenergi", "facteur énergie fossile", "fosil yakıt faktörü", "fattore energia fossile", "faktor fosílnej energie", "faktor fosilní energie")
MAKE_TRANSLATION(electricFactor, "electricfactor", "electric energy factor", "Energiefaktor elektrisch", "Energiefactor electrisch", "Elektrisk energifaktor", "udział energii elektrycznej", "elektrisk energifaktor", "facteur énergie électrique", "elektrik enerjisi faktörü", "fattore energia elettrica", "faktor elektrickej energie", "faktor elektrické energie")
MAKE_TRANSLATION(fossileFactor, "fossilefactor", "fossile energy factor", "Energiefaktor Fossil", "Energiefactor fossiele brandstof", "Faktor fossilenergi", "udział energii z paliw kopalnych", "energifaktor fossilenergi", "facteur énergie fossile", "fosil yakıt faktörü", "fattore energia fossile", "faktor fosílnej energie", "faktor fosilní energie")
MAKE_TRANSLATION(electricFactor, "electricfactor", "electric energy factor", "Energiefaktor elektrisch", "Energiefactor electrisch", "Faktor elenergi", "udział energii elektrycznej", "elektrisk energifaktor", "facteur énergie électrique", "elektrik enerjisi faktörü", "fattore energia elettrica", "faktor elektrickej energie", "faktor elektrické energie")
MAKE_TRANSLATION(delayBoiler, "delayboiler", "delay boiler support", "Verzögerungsoption", "Vertragingsoptie", "Fördröjningsoption", "opcja opóźnienia", "Fördörjningsoption", "option retardement chaudière", "kazan desteğini ötele", "opzione ritardo caldaia", "oneskorená podpora kotla", "zpoždění podpory kotle")
MAKE_TRANSLATION(tempDiffBoiler, "tempdiffboiler", "temp diff boiler support", "Temperaturdifferenzoption", "Verschiltemperatuuroptie", "Temperaturskillnadsoption", "opcja różnicy temperatur", "temperatursforskjell kjele", "option différence température", "sıcaklık farkı kazan desteği", "opzione differenza temperatura", "možnosť rozdielu teplôt", "teplotní rozdíl pro podporu kotle")
MAKE_TRANSLATION(lowNoiseMode, "lownoisemode", "low noise mode", "Geräuscharmer Betrieb", "Stil bedrijf", "Tyst läge", "tryb cichy", "stillemodus", "mode faible bruit", "düşük ses modu", "modalità a basso rumore", "režim nízkej hlučnosti", "režim nízkého hluku")
MAKE_TRANSLATION(lowNoiseStart, "lownoisestart", "low noise starttime", "Start geräuscharmer Betrieb", "Start stil bedrijf", "Tyst läge starttid", "początek trybu cichego", "stille modu starttid", "heure démarrage faible bruit", "düşük ses başlangıç", "ora di avvio a basso rumore", "nízka hlučnosť spustenia", "čas začátku nízkého hluku")
MAKE_TRANSLATION(lowNoiseStop, "lownoisestop", "low noise stoptime", "Stopp geräuscharmer Betrieb", "Stop stil bedrijf", "Tyst läge stopptid", "koniec trybu cichego", "stille modus stopptid", "heure arrêt faible bruit", "düşük ses bitiş", "ora di arresto funzionamento silenzioso", "doba zastavenia s nízkou hlučnosťou", "čas konce nízkého hluku")
MAKE_TRANSLATION(lowNoiseMode, "lownoisemode", "low noise mode", "Geräuscharmer Betrieb", "Stil bedrijf", "Tyst drift", "tryb cichy", "stillemodus", "mode faible bruit", "düşük ses modu", "modalità a basso rumore", "režim nízkej hlučnosti", "režim nízkého hluku")
MAKE_TRANSLATION(lowNoiseStart, "lownoisestart", "low noise starttime", "Start geräuscharmer Betrieb", "Start stil bedrijf", "Tyst drift starttid", "początek trybu cichego", "stille modu starttid", "heure démarrage faible bruit", "düşük ses başlangıç", "ora di avvio a basso rumore", "nízka hlučnosť spustenia", "čas začátku nízkého hluku")
MAKE_TRANSLATION(lowNoiseStop, "lownoisestop", "low noise stoptime", "Stopp geräuscharmer Betrieb", "Stop stil bedrijf", "Tyst drift stopptid", "koniec trybu cichego", "stille modus stopptid", "heure arrêt faible bruit", "düşük ses bitiş", "ora di arresto funzionamento silenzioso", "doba zastavenia s nízkou hlučnosťou", "čas konce nízkého hluku")
MAKE_TRANSLATION(energyPriceGas, "energypricegas", "energy price gas", "Energiepreis Gas", "Energieprijs gas", "Gaspris", "cena energii z gazu", "energipris gass", "prix énergie gaz", "gaz enerjisi fiyatı", "prezzo energia gas", "cena energie plyn", "cena energie plyn")
MAKE_TRANSLATION(energyPriceEl, "energypriceel", "energy price electric", "Energiepreis Eletrizität", "energieprijs electriciteit", "Elpris", "cena energii elektrycznej", "strømpris", "prix énergie électrique", "elektrik enerjisi fiyatı", "prezzo energia elettrica", "cena elektrickej energie", "cena energie elektřina")
MAKE_TRANSLATION(energyPricePV, "energyfeedpv", "feed in PV", "PV-Einspeisevergütung", "PV teruglevertarief", "PV Energi", "cena energii PV", "strømpris PV", "alimentation PV", "giren güneş enerjisi", "energia fotovoltaico", "Výkupná cena FV", "přetok FV")
@@ -539,11 +549,11 @@ MAKE_TRANSLATION(valveReturn, "valvereturn", "return valve", "Rückflussventil",
MAKE_TRANSLATION(aPumpMod, "apumpmod", "alternative hs pump modulation", "Alternative WE-Pumpenmodulation", "Alternatieve warmtebron pomp modulatie", "Alternativ Pumpmodulering Värmekälla", "modulacja pompy alternatywnego źródła ciepła", "alternativ pumpemodulering varmekilde", "modulation alternative pompe hs", "alternatif ısı kaynağı pompa modülasyonu", "pompa modulazione alternativa hs", "alternatívna modulácia čerpadla hs", "alternativní modulace čerpadla hs")
MAKE_TRANSLATION(heatSource, "heatsource", "alternative heating active", "Alternativer Wärmeerzeuger aktiv", "Alternatieve warmtebron aktief", "Alternativ Värmekälla aktiv", "aktywne alternatywne źródło ciepła", "alternativ varmekilde aktiv", "chauffage alternatif actif", "alternatif ısınma devrede", "riscaldamento alternativo attivo", "alternatívne kúrenie aktívne", "alternativní vytápění aktivní")
MAKE_TRANSLATION(aPump, "apump", "alternative hs pump", "Alternative WE-Pumpe", "Alternatieve warmtebron pomp", "Alternativ Pump Värmekälla", "pompy alternatywnego źródła ciepła", "alternativ pumpe varmekilde", "alternative pompe hs", "alternatif ısı kaynağı pompası", "pompa alternativa hs", "alternatívne čerpadlo hs", "alternativní čerpadlo hs")
MAKE_TRANSLATION(burner, "burner", "burner", "Brenner", "Brander", "", "palnik", "", "", "kazan", "bruciatore", "horák", "hořák") // TODO translate
MAKE_TRANSLATION(heatRequest, "heatrequest", "heat request", "Wärmeanforderung", "Warmtevraag", "", "zapotrzebowanie na ciepło", "varmeforespørsel", "", "ısı talebi", "richiesta calore", "požiadavka na teplo", "požadavek na teplo") // TODO translate
MAKE_TRANSLATION(blockRemain, "blockremain", "remaining blocktime", "verbleibende Sperrzeit", "Resterende bloktijd", "", "czas do końca blokady", "gjenstående blokkeringstid", "", "kalan blok süresi", "tempo di blocco rimanente", "zostávajúci čas blokovania", "zbývající doba blokace") // TODO translate
MAKE_TRANSLATION(blockRemainWw, "blockremaindhw", "remaining blocktime dhw", "verbleibende Sperrzeit WW", "Resterende bloktijd ww", "", "czas do końca blokady c.w.u.", "gjenværende blokkeringstid bereder", "", "kalan sıcak kullanım suyu blok süresi", "tempo di blocco rimanente ACS", "zostávajúci čas blokovania TÚV", "zbývající doba blokace TUV") // TODO translate
MAKE_TRANSLATION(flueGasTemp, "fluegastemp", "flue gas temperature", "Abgastemperatur", "Rookafvoertemperatuur", "", "temperatura spalin", "røykgasstemperatur", "", "baca gazı sıcaklığı", "temperatura gas di scarico", "teplota spalín", "teplota spalin") // TODO translate
MAKE_TRANSLATION(burner, "burner", "burner", "Brenner", "Brander", "Brännare", "palnik", "", "", "kazan", "bruciatore", "horák", "hořák") // TODO translate
MAKE_TRANSLATION(heatRequest, "heatrequest", "heat request", "Wärmeanforderung", "Warmtevraag", "Värmebehov", "zapotrzebowanie na ciepło", "varmeforespørsel", "", "ısı talebi", "richiesta calore", "požiadavka na teplo", "požadavek na teplo") // TODO translate
MAKE_TRANSLATION(blockRemain, "blockremain", "remaining blocktime", "verbleibende Sperrzeit", "Resterende bloktijd", "Återstående blockeringstid", "czas do końca blokady", "gjenstående blokkeringstid", "", "kalan blok süresi", "tempo di blocco rimanente", "zostávajúci čas blokovania", "zbývající doba blokace") // TODO translate
MAKE_TRANSLATION(blockRemainWw, "blockremaindhw", "remaining blocktime dhw", "verbleibende Sperrzeit WW", "Resterende bloktijd ww", "Återstående blockeringstid varmvatten", "czas do końca blokady c.w.u.", "gjenværende blokkeringstid bereder", "", "kalan sıcak kullanım suyu blok süresi", "tempo di blocco rimanente ACS", "zostávajúci čas blokovania TÚV", "zbývající doba blokace TUV") // TODO translate
MAKE_TRANSLATION(flueGasTemp, "fluegastemp", "flue gas temperature", "Abgastemperatur", "Rookafvoertemperatuur", "Rökgastemperatur", "temperatura spalin", "røykgasstemperatur", "", "baca gazı sıcaklığı", "temperatura gas di scarico", "teplota spalín", "teplota spalin") // TODO translate
MAKE_TRANSLATION(vr2Config, "vr2config", "vr2 configuration", "VR2 Konfiguration", "VR2 configuratie", "VR2 Konfiguration", "konfiguracja VR2", "vr2 konfigurasjon", "configuration vr2", "vr2 ayarı", "configurazione VR2", "konfigurácia vr2", "konfigurace VR2")
MAKE_TRANSLATION(ahsActivated, "ahsactivated", "alternate heat source activation", "Alt. Wärmeerzeuger aktiviert", "Altenatieve warmtebron geactiveerd", "Alternativ värmekälla aktivering", "aktywacja alternatywnego źródła ciepła", "alternativ varmekilde aktivering", "activation source chaleur alternative", "alternatif ısı kaynağı devrede", "attivazione fonte di calore alternativa", "aktivácia alternatívneho zdroja tepla", "aktivace alternativního zdroje tepla")
@@ -562,48 +572,48 @@ MAKE_TRANSLATION(blockHyst, "blockhyst", "hyst. for boiler block", "Hysterese Sp
MAKE_TRANSLATION(releaseWait, "releasewait", "boiler release wait time", "Wartezeit Kesselfreigabe", "Wachttijd ketel vrijgave", "Väntetid Frisläppning", "czas oczekiwania na zwolnienie kotła", "kjele frigjøringsventetid", "temps attente libération chaudière", "kazan tahliyesi bekleme süresi", "tempo di attesa sblocco caldaia", "doba čakania na uvoľnenie kotla", "doba čekání na uvolnění kotle")
// energy
MAKE_TRANSLATION(nrgTotal, "nrgtotal", "total energy", "Gesamtenergie", "", "", "całkowita energia", "", "", "", "", "celková energia", "celková energie") // TODO translate
MAKE_TRANSLATION(nrgHeat, "nrgheat", "energy heating", "Energie Heizen", "", "", "energia na ogrzewanie", "", "", "ısıtma enerjisi", "energia vykurovania", "energetické vykurovanie", "energie pro vytápění") // TODO translate
MAKE_TRANSLATION(nrgCool, "nrgcool", "energy cooling", "Energie Kühlen", "", "", "", "", "", "", "", "energia chladenia", "energie pro chlazení") // TODO translate
MAKE_TRANSLATION(nrgWw, "nrg", "energy", "Energie", "", "", "energia", "", "", "sıcak kullanım suyu enerjisi", "", "energia", "energie") // TODO translate
MAKE_TRANSLATION(nrgHeat2, "nrgheat2", "energy heating 2", "Energie Heizen 2", "", "", "energia na ogrzewanie 2", "", "", "ısıtma enerjisi 2", "", "energia vykurovania 2", "energie pro vytápění 2") // TODO translate
MAKE_TRANSLATION(nrgWw2, "nrg2", "energy 2", "Energie 2", "", "", "energia 2", "", "", "sıcak kullanım suyu enerjisi 2", "", "energia 2", "energie 2") // TODO translate
MAKE_TRANSLATION(nomPower, "nompower", "nominal Power", "Brennerleistung", "", "", "moc nominalna", "", "", "nominal güç", "", "nominálny výkon", "nominální výkon") // TODO translate
MAKE_TRANSLATION(meterTotal, "metertotal", "meter total", "Gesamtmessung", "", "", "licznik całkowity", "", "", "", "", "počítadlo celkom", "počítadlo celkem") // TODO translate
MAKE_TRANSLATION(meterComp, "metercomp", "meter compressor", "Messung Kompressor", "", "", "licznik sprężarki", "", "", "", "", "počítadlo kompresor", "počítadlo kompresoru") // TODO translate
MAKE_TRANSLATION(meterEHeat, "metereheat", "meter e-heater", "Messung E-Heizer", "", "", "licznik dogrzewacza", "", "", "", "", "počítadlo e-ohrievača", "počítadlo elektrického topení") // TODO translate
MAKE_TRANSLATION(meterHeat, "meterheat", "meter heating", "Messung Heizen", "", "", "licznik ogrzewania", "", "", "", "", "počítadlo kúrenia", "počítadlo vytápění") // TODO translate
MAKE_TRANSLATION(meterCool, "metercool", "meter cooling", "Messung Kühlen", "", "", "", "", "", "", "", "počítadlo chladenia", "počítadlo chlazení") // TODO translate
MAKE_TRANSLATION(meterWw, "meter", "meter", "Messung", "", "", "licznik", "", "", "", "", "počítadlo", "počítadlo") // TODO translate
MAKE_TRANSLATION(gasMeterHeat, "gasmeterheat", "gas meter heating", "Gaszähler Heizen", "", "", "licznik gazu na ogrzewanie", "", "", "", "", "počítadlo plynu kúrenia", "počítadlo plynu pro vytápění") // TODO translate
MAKE_TRANSLATION(gasMeterWw, "gasmeter", "gas meter", "Gaszähler", "", "", "licznik gazu", "", "", "", "", "počítadlo plynu", "počítadlo plynu") // TODO translate
MAKE_TRANSLATION(hpCurrPower, "hpcurrpower", "compressor current power", "akt. Kompressorleistung", "", "", "", "", "", "", "", "aktuálny výkon kompresoru", "aktuální výkon kompresoru") // TODO translate
MAKE_TRANSLATION(hpPowerLimit, "hppowerlimit", "power limit", "Leistungsgrenze", "", "", "", "", "", "", "", "obmedzenie výkonu", "omezení výkonu") // TODO translate
MAKE_TRANSLATION(powerReduction, "powerreduction", "power reduction", "Leistungsverringerung", "", "", "", "", "", "", "", "obmedzenie výkonu", "omezení výkonu") // TODO translate
MAKE_TRANSLATION(fuelHeat, "fuelheat", "fuel consuption heating", "Verbrauch Heizen", "", "", "", "", "", "", "", "obmedzenie výkonu", "omezení výkonu") // TODO translate
MAKE_TRANSLATION(fuelDhw, "fueldhw", "fuel consuption", "Verbrauch", "", "", "", "", "", "", "", "", "") // TODO translate
MAKE_TRANSLATION(elHeat, "elheat", "el. consuption heating", "el. Verbrauch Heizen", "", "", "", "", "", "", "", "", "") // TODO translate
MAKE_TRANSLATION(elDhw, "eldhw", "el consuption", "el. Verbrauch", "", "", "", "", "", "", "", "", "") // TODO translate
MAKE_TRANSLATION(elGenHeat, "elgenheat", "el. generation heating", "el. Erzeugung Heizen", "", "", "", "", "", "", "", "", "") // TODO translate
MAKE_TRANSLATION(elGenDhw, "elgendhw", "el generation", "el. Erzeugung", "", "", "", "", "", "", "", "", "") // TODO translate
MAKE_TRANSLATION(nrgTotal, "nrgtotal", "total energy", "Gesamtenergie", "", "Avgiven energi totalt", "całkowita energia", "", "", "", "", "celková energia", "celková energie") // TODO translate
MAKE_TRANSLATION(nrgHeat, "nrgheat", "energy heating", "Energie Heizen", "", "Avgiven energi värme", "energia na ogrzewanie", "", "", "ısıtma enerjisi", "energia vykurovania", "energetické vykurovanie", "energie pro vytápění") // TODO translate
MAKE_TRANSLATION(nrgCool, "nrgcool", "energy cooling", "Energie Kühlen", "", "Avgiven energi kyla", "", "", "", "", "", "energia chladenia", "energie pro chlazení") // TODO translate
MAKE_TRANSLATION(nrgWw, "nrg", "energy", "Energie", "", "Avgiven energi varmvatten", "energia", "", "", "sıcak kullanım suyu enerjisi", "", "energia", "energie") // TODO translate
MAKE_TRANSLATION(nrgHeat2, "nrgheat2", "energy heating 2", "Energie Heizen 2", "", "Avgiven energi värme 2", "energia na ogrzewanie 2", "", "", "ısıtma enerjisi 2", "", "energia vykurovania 2", "energie pro vytápění 2") // TODO translate
MAKE_TRANSLATION(nrgWw2, "nrg2", "energy 2", "Energie 2", "", "", "energia 2", "", "Avgiven energi varmvatten 2", "sıcak kullanım suyu enerjisi 2", "", "energia 2", "energie 2") // TODO translate
MAKE_TRANSLATION(nomPower, "nompower", "nominal Power", "Brennerleistung", "", "Nominell effekt", "moc nominalna", "", "", "nominal güç", "", "nominálny výkon", "nominální výkon") // TODO translate
MAKE_TRANSLATION(meterTotal, "metertotal", "meter total", "Gesamtmessung", "", "Förbrukad energi totalt", "licznik całkowity", "", "", "", "", "počítadlo celkom", "počítadlo celkem") // TODO translate
MAKE_TRANSLATION(meterComp, "metercomp", "meter compressor", "Messung Kompressor", "", "Förbrukad energi kompressor", "licznik sprężarki", "", "", "", "", "počítadlo kompresor", "počítadlo kompresoru") // TODO translate
MAKE_TRANSLATION(meterEHeat, "metereheat", "meter e-heater", "Messung E-Heizer", "", "Förbrukad energi eltillskott", "licznik dogrzewacza", "", "", "", "", "počítadlo e-ohrievača", "počítadlo elektrického topení") // TODO translate
MAKE_TRANSLATION(meterHeat, "meterheat", "meter heating", "Messung Heizen", "", "Förbrukad energi värme", "licznik ogrzewania", "", "", "", "", "počítadlo kúrenia", "počítadlo vytápění") // TODO translate
MAKE_TRANSLATION(meterCool, "metercool", "meter cooling", "Messung Kühlen", "", "Förbrukad energi kyla", "", "", "", "", "", "počítadlo chladenia", "počítadlo chlazení") // TODO translate
MAKE_TRANSLATION(meterWw, "meter", "meter", "Messung", "", "", "licznik", "", "Förbrukad energi varmvatten", "", "", "počítadlo", "počítadlo") // TODO translate
MAKE_TRANSLATION(gasMeterHeat, "gasmeterheat", "gas meter heating", "Gaszähler Heizen", "", "Gasförbrukning värme", "licznik gazu na ogrzewanie", "", "", "", "", "počítadlo plynu kúrenia", "počítadlo plynu pro vytápění") // TODO translate
MAKE_TRANSLATION(gasMeterWw, "gasmeter", "gas meter", "Gaszähler", "", "Gasförbrukning varmvatten", "licznik gazu", "", "", "", "", "počítadlo plynu", "počítadlo plynu") // TODO translate
MAKE_TRANSLATION(hpCurrPower, "hpcurrpower", "compressor current power", "akt. Kompressorleistung", "", "Kompressoreffekt", "", "", "", "", "", "aktuálny výkon kompresoru", "aktuální výkon kompresoru") // TODO translate
MAKE_TRANSLATION(hpPowerLimit, "hppowerlimit", "power limit", "Leistungsgrenze", "", "Begränsning kompressoreffekt", "", "", "", "", "", "obmedzenie výkonu", "omezení výkonu") // TODO translate
MAKE_TRANSLATION(powerReduction, "powerreduction", "power reduction", "Leistungsverringerung", "", "Reducerad kompressoreffekt", "", "", "", "", "", "obmedzenie výkonu", "omezení výkonu") // TODO translate
MAKE_TRANSLATION(fuelHeat, "fuelheat", "fuel consumption heating", "Verbrauch Heizen", "", "Bränsleförbrukning värme", "", "", "", "", "", "obmedzenie výkonu", "omezení výkonu") // TODO translate
MAKE_TRANSLATION(fuelDhw, "fueldhw", "fuel consumption", "Verbrauch", "", "Bränsleförbrukning varmvatten", "", "", "", "", "", "spotreba paliva", "") // TODO translate
MAKE_TRANSLATION(elHeat, "elheat", "el. consumption heating", "el. Verbrauch Heizen", "", "Elförbrukning värme", "", "", "", "", "", "el. spotreba kúrenie", "") // TODO translate
MAKE_TRANSLATION(elDhw, "eldhw", "el. consumption", "el. Verbrauch", "", "Elförbrukning varmvatten", "", "", "", "", "", "el. spotreba", "") // TODO translate
MAKE_TRANSLATION(elGenHeat, "elgenheat", "el. generation heating", "el. Erzeugung Heizen", "", "Elgenerering värme", "", "", "", "", "", "el. generovanie kúrenia", "") // TODO translate
MAKE_TRANSLATION(elGenDhw, "elgendhw", "el generation", "el. Erzeugung", "", "Elgenerering varmvatten", "", "", "", "", "", "el. generovanie", "") // TODO translate
// HIU
MAKE_TRANSLATION(netFlowTemp, "netflowtemp", "heat network flow temp", "Systemvorlauftemperatur", "Netto aanvoertemperatuur", "", "temp. zasilania sieci cieplnej", "", "", "ısıtma şebekesi akış derecesi", "temperatura di mandata della rete di riscaldamento", "teplota prívodu tepelnej siete", "teplota přívodu tepelné sítě") // TODO translate
// MAKE_TRANSLATION(cwFlowRate, "cwflowrate", "cold water flow rate", "Kaltwasser Durchfluss", "Stroomsnelheid koud water", "", "przepływ zimnej wody", "", "", "soğuk su akış hızı", "portata acqua fredda", "prietok studenej vody", "průtok studené vody") // TODO translate
MAKE_TRANSLATION(keepWarmTemp, "keepwarmtemp", "keep warm temperature", "Warmhaltetemperatur", "Warmhoudtemperatuur", "", "", "temperatura podtrzymywania ciepła", "", "sıcaklığı koruma derecesi", "mantenere la temperatura calda", "udržať teplú teplotu", "udržovací teplota") // TODO translate
MAKE_TRANSLATION(heatValve, "heatvalve", "heating valve", "Heizungsventil", "", "", "zawór ogrzewania", "", "", "", "", "vykurovací ventil", "ventil pro vytápění") // TODO translate
MAKE_TRANSLATION(wwValve, "dhwvalve", "valve", "Ventil", "", "", "zawór", "", "", "", "", "ventil", "ventil") // TODO translate
MAKE_TRANSLATION(netFlowTemp, "netflowtemp", "heat network flow temp", "Systemvorlauftemperatur", "Netto aanvoertemperatuur", "Temperatur fjärrvärmenät", "temp. zasilania sieci cieplnej", "", "", "ısıtma şebekesi akış derecesi", "temperatura di mandata della rete di riscaldamento", "teplota prívodu tepelnej siete", "teplota přívodu tepelné sítě") // TODO translate
// MAKE_TRANSLATION(cwFlowRate, "cwflowrate", "cold water flow rate", "Kaltwasser Durchfluss", "Stroomsnelheid koud water", "Kallvattensflöde", "przepływ zimnej wody", "", "", "soğuk su akış hızı", "portata acqua fredda", "prietok studenej vody", "průtok studené vody") // TODO translate
MAKE_TRANSLATION(keepWarmTemp, "keepwarmtemp", "keep warm temperature", "Warmhaltetemperatur", "Warmhoudtemperatuur", "", "Varmhållningstemperatur", "temperatura podtrzymywania ciepła", "", "sıcaklığı koruma derecesi", "mantenere la temperatura calda", "udržať teplú teplotu", "udržovací teplota") // TODO translate
MAKE_TRANSLATION(heatValve, "heatvalve", "heating valve", "Heizungsventil", "", "Ventil uppvärmning", "zawór ogrzewania", "", "", "", "", "vykurovací ventil", "ventil pro vytápění") // TODO translate
MAKE_TRANSLATION(wwValve, "dhwvalve", "valve", "Ventil", "", "Ventil varmvatten", "zawór", "", "", "", "", "ventil", "ventil") // TODO translate
// the following are dhw for the boiler and automatically tagged with 'dhw'
MAKE_TRANSLATION(wwSelTemp, "seltemp", "selected temperature", "gewählte Temperatur", "Geselecteerd temperatuur", "Vald Temperatur", "temperatura wyższa/komfort", "valgt temperatur", "température sélectionnée", "seçili sıcaklık", "temperatura selezionata", "zvolená teplota", "nastavená teplota")
MAKE_TRANSLATION(wwSelTempLow, "seltemplow", "selected lower temperature", "ausgewählte untere Temperatur", "Onderste streeftemperatuur", "Vald lägstatemperatur", "temperatura niższa/eko", "valgt nedre temperatur", "température basse sélectionnée", "seçili düşük sıcaklık", "bassa temperatura selezionata", "zvolená nižšia teplota", "redukovaná teplota")
MAKE_TRANSLATION(wwSelTempEco, "tempecoplus", "selected eco+ temperature", "ausgewählte ECO+ Temperatur", "eco+ streeftemperatuur", "eco+ lägstatemperatur", "temperatura niższa/eko+", "valgt eco+ temperatur", "température eco+ sélectionnée", "seçili eco+ sıcaklık", "eco+ temperatura selezionata", "zvolená teplota eco+", "Eco+ teplota")
MAKE_TRANSLATION(wwSelTempOff, "seltempoff", "selected temperature for off", "ausgewählte Temperatur bei AUS", "Streeftemperatuur bij UIT", "Vald tempereatur för AV", "temperatura gdy grzanie wyłączone", "valgt tempereatur for av", "température sélectionnée pour arrêt", "kapanma için seçili sıcaklık", "temperatura selezionata per spegnimento", "zvolená teplota pre vypnutie", "teplota pro vypnutí")
MAKE_TRANSLATION(wwSelTempEco, "tempecoplus", "selected eco+ temperature", "ausgewählte ECO+ Temperatur", "eco+ streeftemperatuur", "Eco+ lägstatemperatur", "temperatura niższa/eko+", "valgt eco+ temperatur", "température eco+ sélectionnée", "seçili eco+ sıcaklık", "eco+ temperatura selezionata", "zvolená teplota eco+", "Eco+ teplota")
MAKE_TRANSLATION(wwSelTempOff, "seltempoff", "selected temperature for off", "ausgewählte Temperatur bei AUS", "Streeftemperatuur bij UIT", "Vald temperatur för AV", "temperatura gdy grzanie wyłączone", "valgt tempereatur for av", "température sélectionnée pour arrêt", "kapanma için seçili sıcaklık", "temperatura selezionata per spegnimento", "zvolená teplota pre vypnutie", "teplota pro vypnutí")
MAKE_TRANSLATION(wwOneTime, "onetime", "one time charging", "Einmalladung", "Buffer eenmalig laden", "Engångsladdning", "jednorazowa dodatkowa ciepła woda", "engangsoppvarming", "charge unique", "tek seferlik doldurma", "carica singola", "jednorazové nabíjanie", "jednorázové nabíjení")
MAKE_TRANSLATION(wwSelTempSingle, "seltempsingle", "single charge temperature", "Einmalladungstemperatur", "Streeftemperatuur enkele lading", "Temperatur Engångsladdning", "temperatura dodatkowej ciepłej wody", "temp engangsoppvarming", "température charge unique", "tek şarj sıcaklığı", "temperatura singolaa carica", "teplota na jedno nabitie", "teplota jednorázového nabíjení")
MAKE_TRANSLATION(wwOneTimeKey, "onetimekey", "one time key function", "Einmalladungstaste", "Knop voor eenmalig laden buffer", "Engångsfunktion", "przycisk jednorazowego ogrzania", "engangsknapp varme", "fonction touche unique", "tek seferlik doldurma fonksiyonu", "pulsante funzione singola", "jednorazová kľúčová funkcia", "jednorázová funkce klíče")
MAKE_TRANSLATION(wwCylMiddleTemp, "cylmiddletemp", "cylinder middle temperature (TS3)", "Speichertemperatur Mitte", "Buffer temperatuur midden", "Cylinder Temperatur Mitten (TS3)", "temperatura środka cylindra (TS3)", "vanntank midten temperatur (TS3)", "température moyenne ballon (TS3)", "Silindir orta sıcaklığı", "temperatura centrale accumulo (TS3)", "stredná teplota valca (TS3)", "teplota středu zásobníku (TS3)")
MAKE_TRANSLATION(wwSetTemp, "settemp", "set temperature", "Solltemperatur", "Streeftemperatuut", "Börtempertur", "temperatura zadana", "innstilt temperatur", "régler température", "hedef sıcaklık", "imposta temperatura", "nastavená teplota", "nastavená teplota")
MAKE_TRANSLATION(wwSetTemp, "settemp", "set temperature", "Solltemperatur", "Streeftemperatuut", "Börtemperatur", "temperatura zadana", "innstilt temperatur", "régler température", "hedef sıcaklık", "imposta temperatura", "nastavená teplota", "nastavená teplota")
MAKE_TRANSLATION(wwType, "type", "type", "Typ", "type", "Typ", "typ", "type", "type", "tip", "tipo", "typ", "typ")
MAKE_TRANSLATION(wwComfort, "comfort", "comfort", "Komfort", "Comfort", "Komfort", "komfort", "komfort", "confort", "konfor", "Comfort", "komfort", "komfort")
MAKE_TRANSLATION(wwComfort1, "comfort1", "comfort mode", "Komfort-Modus", "Comfort modus", "Komfortläge", "tryb komfortu", "komfort modus", "mode confort", "konfor modu", "modalità comfort", "komfortný režim", "komfortní režim")
@@ -617,7 +627,7 @@ MAKE_TRANSLATION(wwCurTemp, "curtemp", "current intern temperature", "aktuelle i
MAKE_TRANSLATION(wwCurTemp2, "curtemp2", "current extern temperature", "aktuelle externe Temperatur", "Huidige externe temperatuur", "Extern Temperatur", "temperatura wypływu", "gjeldende ekstern temperaur", "température externe actuelle", "güncel dış sıcaklık", "temperatura esterna attuale", "aktuálna vonkajšia teplota", "aktuální venkovní teplota")
MAKE_TRANSLATION(wwCurFlow, "curflow", "current tap water flow", "aktueller Durchfluss", "Hudige warmwater doorstroming", "Aktuellt tappvattenflöde", "aktualny przepływ", "gjeldende tappevannshastighet", "débit actuel eau robinet", "güncel musluk suyu akışı", "portata corrente dell'acqua del rubinetto", "aktuálny prietok vody z vodovodu", "aktuální průtok TUV")
MAKE_TRANSLATION(wwStorageTemp1, "storagetemp1", "storage intern temperature", "interne Speichertemperatur", "Interne buffertemperatuur", "Beredare Intern Temperatur", "temperatura wewnątrz zasobnika", "intern temperatur bereder", "température interne stockage", "depo iç sıcaklığı", "temperatura di conservazione interna", "interná teplota skladovania", "vnitřní teplota zásobníku")
MAKE_TRANSLATION(wwStorageTemp2, "storagetemp2", "storage extern temperature", "externe Speichertemperatur", "Externe buffertemperatuur", "Beredare Extern Tempereatur", "temperatura na wyjściu zasobnika", "ekstern temperatur bereder", "température externe stockage", "depo dış sıcaklığı", "temperatura di conservazione esterna", "vonkajšia teplota skladovania", "venkovní teplota zásobníku")
MAKE_TRANSLATION(wwStorageTemp2, "storagetemp2", "storage extern temperature", "externe Speichertemperatur", "Externe buffertemperatuur", "Beredare Extern Temperatur", "temperatura na wyjściu zasobnika", "ekstern temperatur bereder", "température externe stockage", "depo dış sıcaklığı", "temperatura di conservazione esterna", "vonkajšia teplota skladovania", "venkovní teplota zásobníku")
MAKE_TRANSLATION(wwActivated, "activated", "activated", "aktiviert", "geactiveerd", "Aktiverad", "system przygotowywania c.w.u.", "aktivert", "activé", "devreye girdi", "attivato", "aktivovaný", "aktivováno")
MAKE_TRANSLATION(wwDisinfecting, "disinfecting", "disinfecting", "Desinfizieren", "Desinfectie", "Desinficerar", "dezynfekcja termiczna", "desinfiserer", "désinfection", "dezenfekte ediliyor", "disinfezione", "dezinfekcia", "dezinfekce")
MAKE_TRANSLATION(wwDisinfectionTemp, "disinfectiontemp", "disinfection temperature", "Desinfektionstemperatur", "Desinfectietemperatuur", "Desinfektionstemperatur", "temperatura dezynfekcji termicznej", "desinfeksjonstemperatur", "température désinfection", "dezenfeksiyon sıcaklığı", "temperatura disinfezione", "teplota dezinfekcie", "dezinfekční teplota")
@@ -627,21 +637,21 @@ MAKE_TRANSLATION(wwRecharging, "recharging", "recharging", "Nachladen", "herlade
MAKE_TRANSLATION(wwTempOK, "tempok", "temperature ok", "Temperatur ok", "Temperatuur OK", "Temperatur OK", "temperatura OK", "temperatur ok!", "température ok", "sıcaklık tamam", "Temperatura OK", "teplota ok", "teplota v pořádku")
MAKE_TRANSLATION(wwActive, "active", "active", "aktiv", "Actief", "Aktiv", "aktywna", "aktiv", "actif", "devrede", "attivo", "aktívny", "aktivní")
MAKE_TRANSLATION(ww3wayValve, "3wayvalve", "3-way valve active", "3-Wege-Ventil aktiv", "3-wegklep actief", "Trevägsventil aktiv", "zawór 3-drogowy aktywny", "aktiv trevisventil", "vanne 3 voies active", "3 yollu vana", "valvola 3-vie", "3-cestný ventil aktívny", "aktivní 3-cestný ventil")
MAKE_TRANSLATION(wwMixerTemp, "mixertemp", "mixer temperature", "Mischertemperatur", "Mixertemperatuur", "Blandningsventil-tempertur", "temperatura mieszacza", "temperatur blandeventil", "température mélangeur", "karıştırıcı sıcaklığı", "temperatura miscelatore", "teplota mixéra", "teplota směšovače")
MAKE_TRANSLATION(wwMixerTemp, "mixertemp", "mixer temperature", "Mischertemperatur", "Mixertemperatuur", "Blandningsventil tempertur", "temperatura mieszacza", "temperatur blandeventil", "température mélangeur", "karıştırıcı sıcaklığı", "temperatura miscelatore", "teplota mixéra", "teplota směšovače")
MAKE_TRANSLATION(wwStarts, "starts", "starts", "Anzahl Starts", "Aantal starts", "Antal starter", "liczba załączeń", "antall starter", "démarrages", "başlıyor", "avvii", "Počet štartov", "Počet startů")
MAKE_TRANSLATION(wwStartsHp, "startshp", "starts hp", "Anzahl Starts WP", "", "", "", "", "", "", "", "Počet spustení hp", "") // TODO translate
MAKE_TRANSLATION(wwStartsHp, "startshp", "starts hp", "Anzahl Starts WP", "", "Antal starter VP", "", "", "", "", "", "Počet spustení ", "") // TODO translate
MAKE_TRANSLATION(wwWorkM, "workm", "active time", "aktive Zeit", "Actieve tijd", "Aktiv Tid", "czas aktywności", "driftstid", "temps actif", "aktif zaman", "tempo attivo", "aktívny čas", "aktivní čas")
MAKE_TRANSLATION(wwHystOn, "hyston", "hysteresis on temperature", "Einschalttemperaturdifferenz", "Inschakeltemperatuurverschil", "Hysteres PÅ-temperatur", "histereza załączania", "innkoblingstemperaturforskjell", "hystérésis température allumage", "çalışma sıcaklığı farkı", "differenza di temperatura di accensione", "hysterézia teploty", "hystereze zapnutí")
MAKE_TRANSLATION(wwHystOff, "hystoff", "hysteresis off temperature", "Ausschalttemperaturdifferenz", "Uitschakeltemperatuurverschil", "Hysteres AV-temperatur", "histereza wyłączania", "utkoblingstemperaturforskjell", "hystérésis température extinction", "kapatma sıcaklığı farkı", "differenza di temperatura di spegnimento", "teplota hysterézie", "hystereze vypnutí")
MAKE_TRANSLATION(wwProgMode, "progmode", "program", "Programmmodus", "Programma", "Program", "program", "program", "programme", "program", "Programma", "program", "program")
MAKE_TRANSLATION(wwCircProg, "circprog", "circulation program", "Zirkulationsprogramm", "Circulatieprogramma", "Cirkulationsprogram", "program cyrkulacji c.w.u.", "sirkulationsprogram", "programme circulation", "sirkülasyon programı", "programma circolazione", "obehový program", "program oběhu")
MAKE_TRANSLATION(wwMaxTemp, "maxtemp", "maximum temperature", "maximale Temperatur", "maximale temperatuur", "maximal Temperatur", "temperatura maksymalna", "maksimal temperatur", "température max", "maksimum sıcaklık", "temperatura massima", "maximálna teplota", "maximální teplota")
MAKE_TRANSLATION(wwSolarTemp, "solartemp", "solar boiler temperature", "Solarkesseltemperatur", "Zonneboiler temperatuur", "Solpanel Temp", "temperatura zasobnika solarnego", "solpaneltemp", "température chaudière solaire", "güneş enerjisi kazan sıcaklığı", "temperatura pannello solare", "teplota solárneho kotla", "teplota solárního kotle")
MAKE_TRANSLATION(wwSolarTemp, "solartemp", "solar boiler temperature", "Solarkesseltemperatur", "Zonneboiler temperatuur", "Solpanel Temperatur", "temperatura zasobnika solarnego", "solpaneltemp", "température chaudière solaire", "güneş enerjisi kazan sıcaklığı", "temperatura pannello solare", "teplota solárneho kotla", "teplota solárního kotle")
// mqtt values / commands
MAKE_TRANSLATION(switchtime, "switchtime", "program switchtime", "Programmschaltzeit", "Programma schakeltijd", "Program Bytestid", "program czasowy", "programbyttetid", "heure commutation programme", "program değiştirme süresi", "ora commutazione programmata", "čas prepnutia programu", "čas přepnutí programu")
MAKE_TRANSLATION(switchtime1, "switchtime1", "own1 program switchtime", "Programmschaltzeit 1", "Schakeltijd programma 1", "Program 1 Bytestid", "program przełączania 1", "byttetidprogram 1", "heure de commutation programme 1", "program1 değiştirme süresi", "ora commutazione programma 1", "vlastný 1 program prepnutia", "vlastní program 1 přepnutí")
MAKE_TRANSLATION(switchtime2, "switchtime2", "own2 program switchtime", "Programmschaltzeit 2", "Schakeltijd programma 2", "Program 2 Bytestid", "program przełączania 2", "byttetid program 2", "heure de changement programme 2", "program1 değiştirme süresi", "ora commutazione programma 2", "vlastný 2 program prepnutia", "vlastní program 2 přepnutí")
MAKE_TRANSLATION(switchtime1, "switchtime1", "own1 program switchtime", "Programmschaltzeit Eigen 1", "Schakeltijd programma 1", "Program 1 Bytestid", "program przełączania 1", "byttetidprogram 1", "heure de commutation programme 1", "program1 değiştirme süresi", "ora commutazione programma 1", "vlastný 1 program prepnutia", "vlastní program 1 přepnutí")
MAKE_TRANSLATION(switchtime2, "switchtime2", "own2 program switchtime", "Programmschaltzeit Eigen 2", "Schakeltijd programma 2", "Program 2 Bytestid", "program przełączania 2", "byttetid program 2", "heure de changement programme 2", "program1 değiştirme süresi", "ora commutazione programma 2", "vlastný 2 program prepnutia", "vlastní program 2 přepnutí")
MAKE_TRANSLATION(wwswitchtime, "switchtimeWW", "program switchtime warm water", "Programmschaltzeit Warmwasser", "Warm water programma schakeltijd", "Varmvattenprogram Bytestid", "program czasowy", "byttetid varmtvannsprogram", "heure commutation programme", "sıcak kullanıom suyu program değiştirme süresi", "Tempo di commutazione del programma", "čas prepnutia programu", "čas přepnutí program TUV")
MAKE_TRANSLATION(wwcircswitchtime, "circswitchtime", "circulation program switchtime", "Zirculationsprogramm Schaltzeit", "Schakeltijd circulatieprogramma", "Cirkulationsprogram Bytestid", "program cyrkulacji", "byttetid sirkulasjonsprogram", "heure commutation programme circulation", "sirkülasyon program değiştirme süresi", "ora commutazione programma circolazione", "čas prepnutia cirkulačného programu", "čas přepnutí programu cirkulace")
MAKE_TRANSLATION(dateTime, "datetime", "date/time", "Datum/Zeit", "Datum/Tijd", "Datum/Tid", "data i godzina", "dato/tid", "date/heure", "zaman/saat", "Data/Ora", "dátum/čas", "datum/čas")
@@ -665,9 +675,10 @@ MAKE_TRANSLATION(autodst, "autodst", "automatic change daylight saving time", "A
MAKE_TRANSLATION(preheating, "preheating", "preheating in the clock program", "Vorheizen im Zeitprogramm", "Voorverwarming in het klokprogramma", "Förvärmning i tidsprogram", "podgrzewanie w programie czasowym", "forvarming i tidsprogram", "préchauffage dans programme horloge", "saat programında ön ısıtma", "preriscaldamento nel programma orologio", "predohrev v programe hodín", "předehřev v programovaném režimu")
MAKE_TRANSLATION(offtemp, "offtemp", "temperature when mode is off", "Temperatur bei AUS", "Temperatuur bij UIT", "Temperatur Avslagen", "temperatura w trybie \"wył.\"", "temperatur avslått", "température lorsque mode désactivé", "mod kapalı iken sıcaklık", "temperatura quando la modalità è disattivata", "teplota, keď je režim vypnutý", "teplota při vypnutém režimu")
MAKE_TRANSLATION(mixingvalves, "mixingvalves", "mixing valves", "Mischventile", "Mengkleppen", "Blandningsventiler", "zawory mieszające", "blandeventiler", "vannes mélange", "karışım vanaları", "valvole miscela", "zmiešavacie ventily", "směšovací ventily")
MAKE_TRANSLATION(pvEnableWw, "pvenabledhw", "enable raise dhw", "aktiviere WW-Anhebung", "Verhoging WW activeren", "", "podwyższenie c.w.u. z PV", "aktivere hevet temperatur bereder", "", "sıcak kullanım suyu yükseltmeyi etkinleştir", "abilitare aumento ACS", "povoliť zvýšenie TÚV", "povolit zvýšení TUV") // TODO translate
MAKE_TRANSLATION(pvRaiseHeat, "pvraiseheat", "raise heating with PV", "Anhebung Heizen mit PV", "Verwarmen met PV activeren", "", "podwyższenie grzania z PV", "heve varmen med solpanel", "", "ısıtmayı G.E. İle yükselt", "Aumentare il riscaldamento con il solare", "zvýšiť kúrenie s FV", "zvýšení vytápění s FV") // TODO translate
MAKE_TRANSLATION(pvLowerCool, "pvlowercool", "lower cooling with PV", "Absenkung Kühlen mit PV", "Verlagen koeling met PV activeren", "", "obniżenie chłodzenia z PV", "nedre kjøling solpanel", "", "soğutmayı G.E. İle düşür", "Riduzione del raffreddamento con il solare", "nižšie chladenie s PV", "snížení chlazení s FV") // TODO translate
MAKE_TRANSLATION(pvEnableWw, "pvenabledhw", "enable raise dhw", "aktiviere WW-Anhebung", "Verhoging WW activeren", "Höj varmvatten med solpanel", "podwyższenie c.w.u. z PV", "aktivere hevet temperatur bereder", "", "sıcak kullanım suyu yükseltmeyi etkinleştir", "abilitare aumento ACS", "povoliť zvýšenie TÚV", "povolit zvýšení TUV") // TODO translate
MAKE_TRANSLATION(pvRaiseHeat, "pvraiseheat", "raise heating with PV", "Anhebung Heizen mit PV", "Verwarmen met PV activeren", "Höj värmen med solpanel", "podwyższenie grzania z PV", "heve varmen med solpanel", "", "ısıtmayı G.E. İle yükselt", "Aumentare il riscaldamento con il solare", "zvýšiť kúrenie s FV", "zvýšení vytápění s FV") // TODO translate
MAKE_TRANSLATION(pvLowerCool, "pvlowercool", "lower cooling with PV", "Absenkung Kühlen mit PV", "Verlagen koeling met PV activeren", "Sänk kylning med solpanel", "obniżenie chłodzenia z PV", "nedre kjøling solpanel", "", "soğutmayı G.E. İle düşür", "Riduzione del raffreddamento con il solare", "nižšie chladenie s PV", "snížení chlazení s FV") // TODO translate
MAKE_TRANSLATION(hasSolar, "solar", "solar", "Solar", "", "Solpaneler", "", "", "", "", "", "solár", "") // TODO translate
// thermostat dhw
MAKE_TRANSLATION(wwMode, "mode", "operating mode", "Betriebsart", "Modus", "Läge", "tryb pracy", "modus", "mode", "mod", "modalità", "režim", "provozní režim")
@@ -700,7 +711,8 @@ MAKE_TRANSLATION(nighttemp2, "nighttemp", "night temperature T1", "Nachttemperat
MAKE_TRANSLATION(ecotemp, "ecotemp", "eco temperature", "eco Temperatur", "Temperatuur eco", "Eko-temperatur", "temperatura w trybie eko", "øko temperatur", "température éco", "eko sıcaklık", "Temperatura eco", "eko teplota", "eko teplota")
MAKE_TRANSLATION(manualtemp, "manualtemp", "manual temperature", "manuelle Temperatur", "Temperatuur handmatig", "Temperatur Manuell", "temperatura ustawiona ręcznie", "manuell temperatur", "température manuelle", "manuel sıcaklık", "temperatura manuale", "manuálna teplota", "manuální teplota")
MAKE_TRANSLATION(tempautotemp, "tempautotemp", "temporary set temperature automode", "temporäre Solltemperatur Automatikmodus", "Streeftemperatuur automodus tijdelijk", "Temporär Aktivering av Auto-läge", "zadana temperatura w pomieszczeniu w trybie \"auto\" (tymczasowa)", "temporær valgt temp i automodus", "température temporaire mode automatique", "geçici ayarlı sıcaklık otomatik mod", "impostare temporaneamente temperatura automatica", "automatický režim dočasnej nastavenej teploty", "dočasné nastavení teploty v automatickém režimu")
MAKE_TRANSLATION(remoteseltemp, "remoteseltemp", "temporary set temperature from remote", "temporäre Solltemperatur Remote", "Temperatuur van afstandsbedieding", "Temperatur från fjärruppkoppling", "zadana zdalnie temperatura a pomieszczeniu (tymczasowa)", "temporær valgt temp fra fjernbetjening", "température temporaire depuis télécommande", "geçici ayarlı sıcaklık uzaktan", "Temperatura temporanea da remoto", "dočasne nastavená teplota z diaľkového ovládania", "dočasné nastavení teploty z dálkového ovladače")
MAKE_TRANSLATION(cooltemp, "cooltemp", "cooling temperature", "Kühltemperatur", "Temperatuur koelbedrijf", "Temperatur Kyla", "temperatura ciepła", "chłodzenie temperatur", "température kjøling", "soğuyor sıcaklık", "temperatura calore", "chladenie teplota", "chlazení teplota")
// MAKE_TRANSLATION(remoteseltemp, "remoteseltemp", "temporary set temperature from remote", "temporäre Solltemperatur Remote", "Temperatuur van afstandsbedieding", "Temperatur från fjärruppkoppling", "zadana zdalnie temperatura a pomieszczeniu (tymczasowa)", "temporær valgt temp fra fjernbetjening", "température temporaire depuis télécommande", "geçici ayarlı sıcaklık uzaktan", "Temperatura temporanea da remoto", "dočasne nastavená teplota z diaľkového ovládania", "dočasné nastavení teploty z dálkového ovladače")
MAKE_TRANSLATION(comforttemp, "comforttemp", "comfort temperature", "Komforttemperatur", "Comforttemperatuur", "Komforttemperatur", "temperatura w trybie komfort", "komforttemperatur", "température confort", "konfor sıcaklığı", "temperatura comfort", "komfortná teplota", "komfortní teplota")
MAKE_TRANSLATION(summertemp, "summertemp", "summer temperature", "Sommertemperatur", "Zomertemperatuur", "Sommartemperatur", "temperatura przełączania lato/zima", "Sommertemperatur", "température été", "yaz sıcaklığı", "temperatura estiva", "letná teplota", "letní teplota")
MAKE_TRANSLATION(designtemp, "designtemp", "design temperature", "Auslegungstemperatur", "Ontwerptemperatuur", "Design-temperatur", "temperatura projektowa", "designtemperatur", "température conception", "özel sıcaklık", "temperatura predefinita", "návrhová teplota", "dimenzovaná teplota")
@@ -715,7 +727,7 @@ MAKE_TRANSLATION(targetflowtemp, "targetflowtemp", "target flow temperature", "b
MAKE_TRANSLATION(heatingtype, "heatingtype", "heating type", "Heizungstyp", "Verwarmingstype", "Värmesystem", "system grzewczy", "varmesystem", "type chauffage", "ısıtma tipi", "tipo riscaldamento", "typ vykurovania", "typ vytápění")
MAKE_TRANSLATION(summersetmode, "summersetmode", "set summer mode", "Einstellung Sommerbetrieb", "Instelling zomerbedrijf", "Aktivera Sommarläge", "tryb lato/zima", "aktiver sommermodus", "activer mode été", "yaz modu ayarı", "Impostazione della modalità estiva", "nastaviť letný režim", "nastavit letní režim")
MAKE_TRANSLATION(hpoperatingmode, "hpoperatingmode", "heatpump operating mode", "WP Betriebsmodus", "Bedrijfsmodus warmtepomp", "Värmepump Driftläge", "tryb pracy pompy ciepła", "driftsmodus varmepumpe", "mode fonctionnement pompe à chaleur", "ısı pompası çalışma modu", "Modalità di funzionamento della pompa di calore", "prevádzkový režim tepelného čerpadla", "provozní režim tepelného čerpadla")
MAKE_TRANSLATION(hpoperatingstate, "hpoperatingstate", "heatpump operating state", "WP Betriebszustand", "Huidige modus warmtepomp", "Värmepump driftläge", "aktualny tryb pracy pompy ciepła", "driftstatus varmepumpe", "état fonctionnement pompe à chaleur", "ısı pompası çalışma durumu", "stato funzionamento pompa di calore", "prevádzkový stav tepelného čerpadla", "stav provozu tepelného čerpadla")
MAKE_TRANSLATION(hpoperatingstate, "hpoperatingstate", "heatpump operating state", "WP Betriebszustand", "Huidige modus warmtepomp", "Värmepump drifttillstånd", "aktualny tryb pracy pompy ciepła", "driftstatus varmepumpe", "état fonctionnement pompe à chaleur", "ısı pompası çalışma durumu", "stato funzionamento pompa di calore", "prevádzkový stav tepelného čerpadla", "stav provozu tepelného čerpadla")
MAKE_TRANSLATION(controlmode, "controlmode", "control mode", "Steuermodus", "Comtrolemodus", "Kontrolläge", "tryb sterowania", "kontrollmodus", "mode régulation", "kontrol modu", "modalità di controllo", "kontrolný režim", "způsob regulace")
MAKE_TRANSLATION(control, "control", "control device", "Fernsteuerung", "Afstandsbedieding", "Kontrollenhet", "sterownik", "kontrollenhet", "dispositif régulation", "kontrol cihazı", "dispositivo di comando", "ovládacie zariadenie", "řídící zařízení")
MAKE_TRANSLATION(roomsensor, "roomsensor", "room sensor", "Raumsensor", "Ruimtesensor", "Rumssensor", "czujnik temperatury pomieszczenia", "romsensor", "capteur pièce", "oda sensörü", "sensore ambiente", "izbový snímač", "čidlo místnosti")
@@ -733,11 +745,12 @@ MAKE_TRANSLATION(vacreducetemp, "vacreducetemp", "vacations off/reduce switch te
MAKE_TRANSLATION(vacreducemode, "vacreducemode", "vacations reduce mode", "Urlaub Absenkmodus", "Vakantie afschakelmodus", "Helg reduceringsläge", "redukcja w trakcie urlopu", "ferieavstengningsmodus", "mode réduction vacances", "tail düşürme modu", "modalita riduzione vacanze", "režim zníženia dovoleniek", "redukční režim během dovolené")
MAKE_TRANSLATION(nofrostmode, "nofrostmode", "nofrost mode", "Frostschutzmodus", "Vorstbeveiligingsmodus", "Frostskyddsläge", "temperatura wiodąca dla ochrony przed zamarzaniem", "frostbeskyttelsesmodus", "mode protection gel", "donma koruması modu", "Modalità protezione antigelo", "nofrost režim", "režim proti zamrznutí")
MAKE_TRANSLATION(remotetemp, "remotetemp", "room temperature from remote", "Raumtemperatur Remote", "Ruimtetemperatuur van afstandsbediening", "Rumstemperatur från fjärr", "temperatura w pomieszczeniu (z termostatu)", "romstemperatur fra fjernbetjening", "température pièce depuis télécommande", "uzaktan oda sıcaklığı", "temperatura ambiente da remoto", "izbová teplota z diaľkového ovládania", "teplota místnosti z dálkového ovladače")
MAKE_TRANSLATION(remotehum, "remotehum", "room humidity from remote", "Raumfeuchte Remote", "", "", "wilgotność w pomieszczeniu (z termostatu)", "", "", "uzaktan kumandadan oda nemi", "", "Vlhkosť v miestnosti z diaľkového ovládania", "vlhkost místnosti z dálkového ovladače") // TODO translate
MAKE_TRANSLATION(remotehum, "remotehum", "room humidity from remote", "Raumfeuchte Remote", "", "Rumsluftfuktighet från fjärr", "wilgotność w pomieszczeniu (z termostatu)", "", "", "uzaktan kumandadan oda nemi", "", "Vlhkosť v miestnosti z diaľkového ovládania", "vlhkost místnosti z dálkového ovladače") // TODO translate
MAKE_TRANSLATION(wwHolidays, "holidays", "holiday dates", "Feiertage", "Feestdagen", "Helgdagar", "dni świąteczne", "feriedager varmtvann", "dates vacances", "tatil günleri", "feste pubbliche", "sviatočné termíny", "data pro dovolenou")
MAKE_TRANSLATION(wwVacations, "vacations", "vacation dates", "Urlaubstage", "Vakantiedagen", "Semesterdatum Varmvatten", "dni urlopowe", "ferie dato varmtvann", "dates vacances", "izin günleri", "date vacanze", "termíny dovolenky", "data pro prázdniny")
MAKE_TRANSLATION(holidays, "holidays", "holiday dates", "Feiertage", "Feestdagen", "Helgdatum", "święta", "helligdager", "dates vacances", "tatil günleri", "date feste pubbliche", "sviatočné termíny", "data pro dovolenou")
MAKE_TRANSLATION(vacations, "vacations", "vacation dates", "Urlaubstage", "Vakantiedagen", "Semesterdatum", "urlop", "feriedager", "dates vacances", "izin günleri", "date vacanze", "termíny dovolenky", "data pro prázdniny")
MAKE_TRANSLATION(vacationmode, "vacationmode", "vacation mode", "Urlaubsmodus", "", "", "", "", "", "", "", "dovolenkový režim", "") // ToDo Translate
MAKE_TRANSLATION(wwprio, "dhwprio", "dhw priority", "WW-Vorrang", "Prioriteit warm water", "Prioritera Varmvatten", "priorytet dla c.w.u.", "prioroter varmtvann", "priorité ecs", "sıcak kullanım suyu önceliği", "priorita acqua calda", "Priorita TÚV", "přednost ohřevu TUV")
MAKE_TRANSLATION(nofrostmode1, "nofrostmode1", "nofrost mode", "Frostschutz", "Vorstbeveiligingsmodus", "Frostskyddsläge", "ochrona przed zamarzaniem", "frostbeskyttelse", "mode protection gel", "donma koruması modu 1", "modalita protezione antigelo", "nofrost režim", "režim proti zamrznutí")
MAKE_TRANSLATION(reducehours, "reducehours", "duration for nighttemp", "Dauer Nachttemp.", "Duur nachtverlaging", "Timmar Nattsänkning", "czas trwania trybu nocnego", "timer nattsenkning", "durée température nuit", "gece sıcaklığı süresi", "durata temperatura notturna", "trvanie nočnej teploty", "délka trvání noční teploty")
@@ -751,21 +764,25 @@ MAKE_TRANSLATION(vacations5, "vacations5", "vacation dates 5", "Urlaubstage 5",
MAKE_TRANSLATION(vacations6, "vacations6", "vacation dates 6", "Urlaubstage 6", "Vakantiedagen 6", "Semesterdatum 6", "urlop 6", "feriedager 6", "dates vacances 6", "izin günleri 6", "date vacanze 6", "termíny dovolenky 6", "data prázdnin 6")
MAKE_TRANSLATION(vacations7, "vacations7", "vacation dates 7", "Urlaubstage 7", "Vakantiedagen 7", "Semesterdatum 7", "urlop 7", "feriedager 7", "dates vacances 7", "izin günleri 7", "date vacanze 7", "termíny dovolenky 7", "data prázdnin 7")
MAKE_TRANSLATION(vacations8, "vacations8", "vacation dates 8", "Urlaubstage 8", "Vakantiedagen 8", "Semesterdatum 8", "urlop 8", "feriedager 8", "dates vacances 8", "izin günleri 8", "date vacanze 8", "termíny dovolenky 8", "data prázdnin 8")
MAKE_TRANSLATION(absent, "absent", "absent", "Abwesend", "", "Frånvarande", "", "", "", "", "", "chýbajúci", "") // TODO translate
MAKE_TRANSLATION(redthreshold, "redthreshold", "reduction threshold", "Absenkschwelle", "", "Tröskel för sänkning", "", "", "", "", "", "zníženie tresholdu", "") // TODO translate
MAKE_TRANSLATION(solarinfl, "solarinfl", "solar influence", "Solareinfluß", "", "", "", "", "", "", "", "slnečný vplyv", "") // TODO translate
MAKE_TRANSLATION(currsolarinfl, "currsolarinfl", "curent solar influence", "akt. Solareinfluß", "", "", "", "", "", "", "", "aktuálny slnečný vplyv", "") // TODO translate
MAKE_TRANSLATION(hpmode, "hpmode", "HP Mode", "WP-Modus", "Modus warmtepomp", "", "tryb pracy pompy ciepła", "", "", "yüksek güç modu", "Modalità Termopompa", "Režim HP", "režim tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(dewoffset, "dewoffset", "dew point offset", "Taupunktdifferenz", "Offset dauwpunt", "", "przesunięcie punktu rosy", "", "", "çiğ noktası göreli", "differenza del punto di rugiada", "posun rosného bodu", "offset rosného bodu") // TODO translate
MAKE_TRANSLATION(roomtempdiff, "roomtempdiff", "room temp difference", "Raumtemperaturdifferenz", "Verschiltemperatuur kamertemp", "", "różnica temp. pomieszczenia", "", "", "oda sıcaklığı farkı", "differenza temperatura ambiente", "rozdiel izbovej teploty", "rozdíl teploty místnosti") // TODO translate
MAKE_TRANSLATION(hpminflowtemp, "hpminflowtemp", "HP min. flow temp.", "WP minimale Vorlauftemperatur", "Minimale aanvoertemperatuur WP", "", "pompa ciepła, min. temperatura przepływu", "", "yüksek güç minimum akış sıcaklığı", "temperatura minima di mandata", "VT min. teplota prietoku.", "minimální teplota přívodu tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(hpcooling, "hpcooling", "hp cooling", "WP Kühlen", "WP koelbedrijf", "VP Kyla", "pompa ciepła, chłodzenie", "vp kjøling", "hp soğuyor", "raffreddamento pompa calore", "chladenie hp", "chlazení tepelného čerpadla")
MAKE_TRANSLATION(coolstart, "coolstart", "cooling starttemp", "Kühlbetrieb ab", "", "", "", "", "", "", "", "teplota spustenia chladenia", "teplota spuštění chlazení") // TODO translate
MAKE_TRANSLATION(coolondelay, "coolondelay", "cooling on delay", "Einschaltverzögerung Kühlen", "", "", "", "", "", "", "", "oneskorenie zapnutie chladenia", "zpoždění zapnutí chlazení") // TODO translate
MAKE_TRANSLATION(cooloffdelay, "cooloffdelay", "cooling off delay", "Ausschaltverzögerung Kühlen", "", "", "", "", "", "", "", "oneskorenie vypnutie chladenia", "zpoždění vypnutí chlazení") // TODO translate
MAKE_TRANSLATION(switchProgMode, "switchprogmode", "switch program mode", "Schaltprogrammmodus", "", "", "", "", "", "", "", "prepnúť režim programu", "přepínací režim programu") // TODO translate
MAKE_TRANSLATION(hpmode, "hpmode", "HP Mode", "WP-Modus", "Modus warmtepomp", "Värmepumpsläge", "tryb pracy pompy ciepła", "", "", "yüksek güç modu", "Modalità Termopompa", "Režim ", "režim tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(dewoffset, "dewoffset", "dew point offset", "Taupunktdifferenz", "Offset dauwpunt", "Daggpunktsförskjutning", "przesunięcie punktu rosy", "", "", "çiğ noktası göreli", "differenza del punto di rugiada", "posun rosného bodu", "offset rosného bodu") // TODO translate
MAKE_TRANSLATION(roomtempdiff, "roomtempdiff", "room temp difference", "Raumtemperaturdifferenz", "Verschiltemperatuur kamertemp", "Rumstemperaturskillnad", "różnica temp. pomieszczenia", "", "", "oda sıcaklığı farkı", "differenza temperatura ambiente", "rozdiel izbovej teploty", "rozdíl teploty místnosti") // TODO translate
MAKE_TRANSLATION(hpminflowtemp, "hpminflowtemp", "HP min. flow temp.", "WP minimale Vorlauftemperatur", "Minimale aanvoertemperatuur WP", "VP min flödestemperatur", "pompa ciepła, min. temperatura przepływu", "", "yüksek güç minimum akış sıcaklığı", "temperatura minima di mandata", "VT min. teplota prietoku.", "minimální teplota přívodu tepelného čerpadla") // TODO translate
MAKE_TRANSLATION(hpcooling, "hpcooling", "hp cooling", "WP Kühlen", "WP koelbedrijf", "VP Kyla", "pompa ciepła, chłodzenie", "vp kjøling", "hp soğuyor", "raffreddamento pompa calore", "chladenie ", "chlazení tepelného čerpadla")
MAKE_TRANSLATION(coolstart, "coolstart", "cooling starttemp", "Kühlbetrieb ab", "", "Kyla starttemperatur", "", "", "", "", "", "teplota spustenia chladenia", "teplota spuštění chlazení") // TODO translate
MAKE_TRANSLATION(coolondelay, "coolondelay", "cooling on delay", "Einschaltverzögerung Kühlen", "", "Inkopplingsfördröjning Kyla", "", "", "", "", "", "oneskorenie zapnutie chladenia", "zpoždění zapnutí chlazení") // TODO translate
MAKE_TRANSLATION(cooloffdelay, "cooloffdelay", "cooling off delay", "Ausschaltverzögerung Kühlen", "", "Frånkopplingsfördröjning Kyla", "", "", "", "", "", "oneskorenie vypnutie chladenia", "zpoždění vypnutí chlazení") // TODO translate
MAKE_TRANSLATION(switchProgMode, "switchprogmode", "switch program mode", "Schaltprogrammmodus", "", "Byt programläge", "", "", "", "", "", "prepnúť režim programu", "přepínací režim programu") // TODO translate
// heatpump and RC100H
MAKE_TRANSLATION(airHumidity, "airhumidity", "relative air humidity", "relative Luftfeuchte", "Relatieve luchtvochtigheid", "Relativ Luftfuktighet", "wilgotność względna w pomieszczeniu", "luftfuktighet", "humidité relative air", "havadaki bağıl nem", "umidità relativa aria", "relatívna vlhkosť vzduchu", "relativní vlhkost vzduchu")
MAKE_TRANSLATION(dewTemperature, "dewtemperature", "dew point temperature", "Taupunkttemperatur", "Dauwpunttemperatuur", "Daggpunkt", "punkt rosy w pomieszczeniu", "duggtemperatur", "température point rosée", "çiğ noktası sıcaklığı", "temperatura del punto di rugiada", "teplota rosného bodu", "teplota rosného bodu")
MAKE_TRANSLATION(battery, "battery", "battery", "Batterie", "", "", "bateria", "", "", "", "", "batéria", "baterie") // TODO translate
MAKE_TRANSLATION(battery, "battery", "battery", "Batterie", "", "Batteri", "bateria", "", "", "", "", "batéria", "baterie") // TODO translate
// mixer
MAKE_TRANSLATION(flowSetTemp, "flowsettemp", "setpoint flow temperature", "Sollwert Vorlauftemperatur", "Streefwaarde aanvoertemperatuur", "Vald flödestemperatur", "zadana temperatura zasilania", "valgt turtemperatur", "consigne température flux", "akış sıcaklığı ayarı", "Setpoint temperatura di mandata", "požadovaná hodnota výstupnej teploty", "cílová teplota vstupní vody")
@@ -773,12 +790,12 @@ MAKE_TRANSLATION(flowTempHc, "flowtemphc", "flow temperature (TC1)", "Vorlauftem
MAKE_TRANSLATION(pumpStatus, "pumpstatus", "pump status (PC1)", "Pumpenstatus HK (PC1)", "pompstatus circuit (PC1)", "Pumpstatus (PC1)", "status pompy (PC1)", "pumpestatus (PC1)", "état pompe (PC1)", "pompa durumu (PC1)", "stato pompa (PC1)", "stav čerpadla (PC1)", "stav čerpadla (PC1)")
MAKE_TRANSLATION(mixerStatus, "valvestatus", "mixing valve actuator (VC1)", "Mischerventilposition (VC1)", "positie mixerklep (VC1)", "Shuntventil Status (VC1)", "siłownik zaworu mieszającego (VC1)", "shuntventil status (VC1)", "actionnement vanne mélangeur (VC1)", "karışım vanası aktüatörü (VC1)", "posizione valvola miscela (VC1)", "pohon zmiešavacieho ventilu (VC1)", "pohon směšovacího ventilu (VC1)")
MAKE_TRANSLATION(flowTempVf, "flowtempvf", "flow temperature in header (T0/Vf)", "Vorlauftemperatur am Verteiler (T0/Vf)", "aanvoertemperatuur verdeler (T0/Vf)", "Flödestemperatur Fördelare (T0/Vf)", "temperatura zasilania na rozdzielaczu (T0/Vf)", "turtemperatur ved fordeleren (T0/Vf)", "température départ collecteur (T0/Vf)", "başlıkta akış sıcaklığı", "Temperatura di mandata al distributore (T0/Vf)", "teplota prívodu v zberači (T0/Vf)", "teplota přívodu v hlavici (T0/Vf)")
MAKE_TRANSLATION(mixerSetTime, "valvesettime", "time to set valve", "Zeit zum einstellen des Ventils", "Inschakeltijd mengklep", "Inställningstid Ventil", "czas na ustawienie zaworu", "instillningstid ventil", "délai activation vanne", "vana ayar zamanı", "ritardo attivazione valvola", "čas na nastavenie ventilu", "čas pro nastavení ventilu")
MAKE_TRANSLATION(mixerSetTime, "valvesettime", "time to set valve", "Zeit zum einstellen des Ventils", "Inschakeltijd mengklep", "Inställningstid Shuntventil", "czas na ustawienie zaworu", "instillningstid ventil", "délai activation vanne", "vana ayar zamanı", "ritardo attivazione valvola", "čas na nastavenie ventilu", "čas pro nastavení ventilu")
// mixer pool
MAKE_TRANSLATION(poolSetTemp, "poolsettemp", "pool set temperature", "Sollwert Pooltemperatur", "Streeftemperatuur zwembad", "Pool Temperatur Börvärde", "zadana temperatura basenu", "valgt temp basseng", "température consigne piscine", "hedef havuz sıcaklığı", "temperatura nominale piscina", "nastavená teplota bazéna", "cílová teplota bazénu")
MAKE_TRANSLATION(poolTemp, "pooltemp", "pool temperature", "Pooltemperatur", "Zwembadtemperatuur", "Pooltemperatur", "temperatura basenu", "bassengtemperatur", "température piscine", "havuz sıcaklığı", "temperatura piscina", "teplota bazéna", "teplota bazénu")
MAKE_TRANSLATION(poolShuntStatus, "poolshuntstatus", "pool shunt status opening/closing", "Poolventil öffnen/schließen", "Zwembadklep status openen/sluiten", "Pool Shunt-status öppnen/stängd", "status bocznika basenu", "bassengshunt-status åpen/stengt", "état shunt piscine ouvert/fermé", "havuz şant durumu açılıyor/kapanıyor", "aprire/chiudere valvola regolazione piscina", "stav bazénového bočníka otváranie/zatváranie", "stav šoupátka bazénu (otevírání/zavírání)")
MAKE_TRANSLATION(poolShuntStatus, "poolshuntstatus", "pool shunt status opening/closing", "Poolventil öffnen/schließen", "Zwembadklep status openen/sluiten", "Pool Shunt-status öppen/stängd", "status bocznika basenu", "bassengshunt-status åpen/stengt", "état shunt piscine ouvert/fermé", "havuz şant durumu açılıyor/kapanıyor", "aprire/chiudere valvola regolazione piscina", "stav bazénového bočníka otváranie/zatváranie", "stav šoupátka bazénu (otevírání/zavírání)")
MAKE_TRANSLATION(poolShunt, "poolshunt", "pool shunt open/close (0% = pool / 100% = heat)", "Poolventil Öffnung", "Mengklep zwembad stand", "Pool Shunt Öppen/Stängd", "bocznik basenu (0% = basen / 100% = grzanie)", "bassengshunt åpen/stengt (0% = basseng / 100% = varme)", "ouverture/fermeture shunt piscine (0% = piscine / 100% = chaleur).", "havuz şant açık/kapalı (0% = havuz / 100% = ısıtma)", "valvola regolazione piscina (0% = piscina / 100% = caldo)", "pohyb bazéna otvoriť/zatvoriť (0 % = bazén / 100 % = teplo)", "šoupátko bazénu (0% = bazén / 100% = vytápění)")
MAKE_TRANSLATION(hydrTemp, "hydrTemp", "hydraulic header temperature", "Verteilertemperatur", "Temperatuur open verdeler", "Fördelartemperatur", "temperatura kolektora hydraulicznego", "Fordelertemperatur", "température collecteur hydraulique", "hidrolik başlık sıcaklığı", "temperatura del collettore", "teplota hydraulickej hlavice", "teplota hydraulické hlavice")
@@ -820,35 +837,35 @@ MAKE_TRANSLATION(m1WorkTime, "m1worktime", "differential control working time",
MAKE_TRANSLATION(energyLastHour, "energylasthour", "energy last hour", "Energie letzte Std", "Energie laatste uur", "Energi Senaste Timmen", "energia w ciągu ostatniej godziny", "energi siste time", "énergie dernière heure", "son saat enerji", "Eenergia ultima ora", "energia za poslednú hodinu", "energie za poslední hodinu")
MAKE_TRANSLATION(energyTotal, "energytotal", "total energy", "Gesamtenergie", "Totale energie", "Total Energi", "energia całkowita", "total energi", "énergie totale", "toplam enerji", "energia totale", "celková energia", "celková energie")
MAKE_TRANSLATION(energyToday, "energytoday", "total energy today", "Energie heute", "Energie vandaag", "Total Energi Idag", "energia całkowita dzisiaj", "total energi i dag", "énergie totale aujourd'hui", "bugün toplam enerji", "totale energia giornaliera", "celková energia dnes", "celková energie dnes")
MAKE_TRANSLATION(cyl3BottomTemp, "cyl3bottomtemp", "third cylinder bottom temperature (TS11)", "Speichertemperatur unten (TS11)") // TODO translate
MAKE_TRANSLATION(cylTopTemp, "cyltoptemp", "cylinder top temperature (TS10)", "Speichertemperatur oben (TS10)") // TODO translate
MAKE_TRANSLATION(transferPumpMod, "transferpumpmod", "transfer pump modulation", "Transferpumpenmodulation") // TODO translate
MAKE_TRANSLATION(transferPump, "transferpump", "transfer pump", "Transferpumpe") // TODO translate
MAKE_TRANSLATION(cyl3BottomTemp, "cyl3bottomtemp", "third cylinder bottom temperature (TS11)", "Speichertemperatur unten (TS11)", "", "Tredje Cylindertemperatur Botten (TS11)", "", "", "", "", "", "spodná teplota tretieho valca (TS11)", "") // TODO translate
MAKE_TRANSLATION(cylTopTemp, "cyltoptemp", "cylinder top temperature (TS10)", "Speichertemperatur oben (TS10)", "", "Cylindertemperatur Toppen (TS10)", "", "", "", "", "", "horná teplota valca (TS10)", "") // TODO translate
MAKE_TRANSLATION(transferPumpMod, "transferpumpmod", "transfer pump modulation", "Transferpumpenmodulation", "", "Överföringspumpmodulering", "", "", "", "", "", "modulácia prenosového čerpadla", "") // TODO translate
MAKE_TRANSLATION(transferPump, "transferpump", "transfer pump", "Transferpumpe", "", "Överföringspump", "", "", "", "", "", "prenosové čerpadlo", "") // TODO translate
// solar dhw
MAKE_TRANSLATION(wwColdTemp, "coldtemp", "cold water", "Kaltwasser", "", "", "zimna woda", "", "", "", "", "studená voda", "studená voda") // TODO translate
MAKE_TRANSLATION(wwColdTemp, "coldtemp", "cold water", "Kaltwasser", "", "kallvatten", "zimna woda", "", "", "", "", "studená voda", "studená voda") // TODO translate
MAKE_TRANSLATION(wwTemp5, "temp5", "temperature 5", "Temperatur 5", "Temperatuur 5", "Temperatur 5", "temperatura 5", "Temperatur 5", "température 5", "sıcaklık 5", "Temperatura 5", "teplota 5", "teplota 5")
MAKE_TRANSLATION(wwTemp6, "temp6", "temperature 6", "Temperatur 6", "Temperatuur 6", "Temperatur 6", "temperatura 6", "temperatur 6", "température 6", "sıcaklık 6", "Temperatura 6", "teplota 6", "teplota 6")
// MAKE_TRANSLATION(wwTemp7, "temp7", "temperature 7", "Temperatur 7", "Temperatuur 7", "Temperatur 7", "temperatura 7", "Temperatur 7", "température 7", "sıcaklık 7", "Temperatura 7", "teplota 7", "teplota 7")
MAKE_TRANSLATION(wwPump, "pump", "pump", "Pumpe", "Pomp", "Pump", "pompa", "pumpe", "pompe", "pompa", "Pompa", "čerpadlo", "čerpadlo")
MAKE_TRANSLATION(wwCircTc, "circtc", "circulation time controled", "zeitgesteuerte Zirkulation", "", "", "", "", "", "", "", "riadená doba cirkulácie", "řízený čas cirkulace") // TODO translate
MAKE_TRANSLATION(errorDisp, "errordisp", "error display", "Fehleranzeige", "", "", "wyświetlanie błędów", "", "", "", "", "zobrazenie chyby", "zobrazení chyb") // TODO translate
MAKE_TRANSLATION(deltaTRet, "deltatret", "temp. diff. return valve", "Temperaturdifferenz Rücklaufventil", "", "", "różnica temp. zaworu powrotnego", "", "", "", "", "rozdiel teplôt spätného ventilu", "rozdíl teploty zpátečního ventilu") // TODO translate
MAKE_TRANSLATION(wwCircTc, "circtc", "circulation time controled", "zeitgesteuerte Zirkulation", "", "Tidsstyrd cirkulation", "", "", "", "", "", "riadená doba cirkulácie", "řízený čas cirkulace") // TODO translate
MAKE_TRANSLATION(errorDisp, "errordisp", "error display", "Fehleranzeige", "", "Feldisplay", "wyświetlanie błędów", "", "", "", "", "zobrazenie chyby", "zobrazení chyb") // TODO translate
MAKE_TRANSLATION(deltaTRet, "deltatret", "temp. diff. return valve", "Temperaturdifferenz Rücklaufventil", "", "Temperaturskillnad returventil", "różnica temp. zaworu powrotnego", "", "", "", "", "rozdiel teplôt spätného ventilu", "rozdíl teploty zpátečního ventilu") // TODO translate
// solar dhw and mixer dhw
MAKE_TRANSLATION(wwMinTemp, "mintemp", "minimum temperature", "minimale Temperatur", "Minimale temperatuur", "Min. Temperatur", "temperatura minimalna", "min. temperatur", "température min.", "minimum sıcaklık", "temperatura minima", "minimálna teplota", "minimální teplota")
MAKE_TRANSLATION(wwRedTemp, "redtemp", "reduced temperature", "reduzierte Temperatur", "Gereduceerde temperatuur", "Reducerad Temperatur", "temperatura zredukowana", "reducert temperatur", "température réduite", "düşürülmüş sıcaklık", "temperatura ridotta", "znížená teplota", "snížená teplota")
MAKE_TRANSLATION(wwDailyTemp, "dailytemp", "daily temperature", "tägl. Temperatur", "Dagelijkse temperatuur", "Daglig temperatur", "temperatura dzienna", "dagtemperatur", "température en journée", "günlük sıcaklık", "temperatura giornaliera", "denná teplota", "denní teplota")
MAKE_TRANSLATION(wwHotTemp, "hottemp", "extra hot temperature", "sehr heiße Temperatur", "", "", "", "", "", "", "", "extra vysoká teplota", "teplota extra horká") // TODO translate
MAKE_TRANSLATION(wwHotTemp, "hottemp", "extra hot temperature", "sehr heiße Temperatur", "", "Mycket varm temperatur", "", "", "", "", "", "extra vysoká teplota", "teplota extra horká") // TODO translate
MAKE_TRANSLATION(wwKeepWarm, "keepwarm", "keep warm", "Warmhalten", "Warm houde", "Varmhållning", "utrzymywanie ciepła", "holde varmen", "maintenir chaleur", "ılık tut", "mantenimento calore", "udržovať v teple", "udržení tepla")
MAKE_TRANSLATION(wwStatus2, "status2", "status 2", "Status 2", "Status 2", "Status 2", "status 2", "status 2", "statut 2", "durum 2", "Status 2", "stav 2", "stav 2")
MAKE_TRANSLATION(wwPumpMod, "pumpmod", "pump modulation", "Pumpenmodulation", "Pompmodulatie", "Pumpmodulering", "modulacja pompy", "pumpemodulering", "modulation de pompe", "pompa modülasyonu", "modulazione pompa", "modulácia čerpadla", "modulace čerpadla")
MAKE_TRANSLATION(wwFlow, "flow", "flow rate", "Volumenstrom", "Doorstroomsnelheid", "Flöde", "przepływ", "strømningshastighet", "débit", "akış hızı", "portata flusso", "prietok", "průtok")
// MAKE_TRANSLATION(wwRetValve, "retvalve", "return valve", "Rücklauf Ventil", "", "", "", "", "", "", "", "spätný ventil", "zpětný ventil")
// MAKE_TRANSLATION(wwRetValve, "retvalve", "return valve", "Rücklauf Ventil", "", "Returventil", "", "", "", "", "", "spätný ventil", "zpětný ventil") // TODO translate
// extra mixer dhw
MAKE_TRANSLATION(wwRequiredTemp, "requiredtemp", "required temperature", "benötigte Temperatur", "Benodigde temperatuur", "Nödvändig Temperatur", "temperatura wymagana", "nødvendig temperatur", "température requise", "gerekli sıcaklık", "temperatura richiesta", "požadovaná teplota", "požadovaná teplota")
MAKE_TRANSLATION(wwDiffTemp, "difftemp", "start differential temperature", "Start Differenztemperatur", "Start differentiele temperatuur", "Start Differentialtemperatur", "start temperatury różnicowej", "start differensialtemperatur", "température différentielle de départ", "diferansiyel sıcaklık", "avvia temperatura differenziale", "začiatok diferenciálnej teploty", "rozdílová teplota pro spuštění")
MAKE_TRANSLATION(wwDiffTemp, "difftemp", "start differential temperature", "Start Differenztemperatur", "Start differentiele temperatuur", "Start Skillnadstemperatur", "start temperatury różnicowej", "start differensialtemperatur", "température différentielle de départ", "diferansiyel sıcaklık", "avvia temperatura differenziale", "začiatok diferenciálnej teploty", "rozdílová teplota pro spuštění")
MAKE_TRANSLATION(wwPumpStatus, "pumpstatus", "pump status in assigned dhw (PC1)", "Pumpenstatus des WWK (PC1)", "Pompstatus in WW circuit (PC1)", "Pumpstatus i VV-krets (PC1)", "stan pompy w obwodzie c.w.u. (PC1)", "Pumpestatus i VV-krets (PC1)", "état pompe dhw (PC1)", "Kullanım suyu devresindeki(PC1) pompa durumu", "stato pompa assegnato nel ciruito WW (PC1)", "stav čerpadla v pridelenom TÚV (PC1)", "stav čerpadla přiřazeného k TUV (PC1)")
MAKE_TRANSLATION(wwTempStatus, "tempstatus", "temperature switch in assigned dhw (MC1)", "Temperaturschalter des WWK (MC1)", "Temperatuurschakeling in WW circuit (MC1)", "Temperaturventil i VV-krets (MC1)", "temperatura w obwodzie c.w.u. (MC1)", "temperaturventil i VV-krets (MC1)", "température bascule dhw (MC1).", "atanmış sıcak su devresinde sıcaklık", "interruttore di temperatura del wwk (MC1)", "teplotný spínač v priradenej TÚV (MC1)", "přepínací teplota v přiřazeném TUV (MC1)")
MAKE_TRANSLATION(wwTemp, "temp", "current temperature", "aktuelle Temperatur", "huidige temperatuur", "Aktuell Temperatur", "temperatura c.w.u.", "aktuell temperatur", "température actuelle", "güncel sıcaklık", "temperatura attuale", "aktuálna teplota", "aktuální teplota")
@@ -890,39 +907,40 @@ MAKE_TRANSLATION(activated, "activated", "activated", "Aktiviert", "Geactiveerd"
MAKE_TRANSLATION(status, "status", "status", "Status", "Status", "Status", "status", "status", "statut", "durum", "Stato", "stav", "stav")
// RF sensor, id 0x40, telegram 0x435
MAKE_TRANSLATION(RFTemp, "rftemp", "RF room temperature sensor", "RF Raumtemperatursensor", "RF ruimtetemperatuur sensor", "RF Rumsgivare Temp", "bezprzewodowy czujnik temperatury pomieszczenia", "RF romsgiver temp", "capteur de température de pièce RF", "RF oda sıcaklık sensörü", "Sensore di temperatura ambiente RF", "RF snímač izbovej teploty", "RF senzor teploty místnosti")
MAKE_TRANSLATION(RFTemp, "rftemp", "RF room temperature sensor", "RF Raumtemperatursensor", "RF ruimtetemperatuur sensor", "RF Rumsgivare Temperatur", "bezprzewodowy czujnik temperatury pomieszczenia", "RF romsgiver temp", "capteur de température de pièce RF", "RF oda sıcaklık sensörü", "Sensore di temperatura ambiente RF", "RF snímač izbovej teploty", "RF senzor teploty místnosti")
// ventilation
MAKE_TRANSLATION(outFresh, "outfresh", "outdoor fresh air", "Außenlufttemp.", "temperatuur buitenlucht", "", "świeże powietrze z zewnątrz", "", "", "dış ortam taze hava", "aria fresca esterna", "čerstvý vzduch vonku", "venkovní čerstvý vzduch") // TODO translate
MAKE_TRANSLATION(inFresh, "infresh", "indoor fresh air", "Zulufttemp.", "temperatuur aanvoer", "", "nawiew", "", "", "iç ortam taze hava", "aria fresca interna", "čerstvý vzduch v interiéri", "vnitřní čerstvý vzduch") // TODO translate
MAKE_TRANSLATION(outEx, "outexhaust", "outdoor exhaust air", "Fortlufttemp.", "uitlaatemperatuur buiten", "", "zużyte powietrze z wewnątrz", "", "", "dış ortam egsoz", "aria di scarico esterna", "vonkajší odpadový vzduch", "venkovní odpadní vzduch") // TODO translate
MAKE_TRANSLATION(inEx, "inexhaust", "indoor exhaust air", "Ablufttemp.", "uitlaattemperatuur binnen", "", "wywiew", "", "", "iç ortam egsoz", "aria di scarico interna", "vnútorný odpadový vzduch", "vnitřní odpadní vzduch") // TODO translate
MAKE_TRANSLATION(ventMode, "ventmode", "ventilation mode", "Belüftungsmodus", "ventilatiemodus", "", "tryb wentylacji", "", "", "havalandırma modu", "modalità di ventilazione", "režim vetrania", "režim ventilace") // TODO translate
MAKE_TRANSLATION(ventInSpeed, "ventinspeed", "in blower speed", "Zuluftdrehzahl", "toerental aanvoerventilator", "", "prędkość wentylatora nawiewu", "", "", "iç fan hızı", "velocità aria di alimentazione", "rýchlosť ventilátora", "rychlost vnitřního ventilátoru") // TODO translate
MAKE_TRANSLATION(ventOutSpeed, "ventoutspeed", "out blower speed", "Abluftdrehzahl", "toerental afvoerventilator", "", "prędjkość wentylatora wywiewu", "", "", "dış fan hızı", "velocità aria di scarico", "rýchlosť výstupného ventilátora", "rychlost venkovního ventilátoru") // TODO translate
MAKE_TRANSLATION(airquality, "airquality", "air quality (voc)", "Luftqualität (VOC)", "luchtkwaliteit (VOC)", "", "jakość powietrza", "", "", "hava kalitesi(voc)", "qualità aria (VOC)", "kvalita vzduchu (voc)", "kvalita vzduchu (VOC)") // TODO translate
MAKE_TRANSLATION(outFresh, "outfresh", "outdoor fresh air", "Außenlufttemp.", "temperatuur buitenlucht", "Utelufttemperatur", "świeże powietrze z zewnątrz", "", "", "dış ortam taze hava", "aria fresca esterna", "čerstvý vzduch vonku", "venkovní čerstvý vzduch") // TODO translate
MAKE_TRANSLATION(inFresh, "infresh", "indoor fresh air", "Zulufttemp.", "temperatuur aanvoer", "Tillufttemperatur", "nawiew", "", "", "iç ortam taze hava", "aria fresca interna", "čerstvý vzduch v interiéri", "vnitřní čerstvý vzduch") // TODO translate
MAKE_TRANSLATION(outEx, "outexhaust", "outdoor exhaust air", "Fortlufttemp.", "uitlaatemperatuur buiten", "Utgående lufttemperatur", "zużyte powietrze z wewnątrz", "", "", "dış ortam egsoz", "aria di scarico esterna", "vonkajší odpadový vzduch", "venkovní odpadní vzduch") // TODO translate
MAKE_TRANSLATION(inEx, "inexhaust", "indoor exhaust air", "Ablufttemp.", "uitlaattemperatuur binnen", "Frånlufttemperatur", "wywiew", "", "", "iç ortam egsoz", "aria di scarico interna", "vnútorný odpadový vzduch", "vnitřní odpadní vzduch") // TODO translate
MAKE_TRANSLATION(ventMode, "ventmode", "ventilation mode", "Belüftungsmodus", "ventilatiemodus", "ventilationsläge", "tryb wentylacji", "", "", "havalandırma modu", "modalità di ventilazione", "režim vetrania", "režim ventilace") // TODO translate
MAKE_TRANSLATION(ventInSpeed, "ventinspeed", "in blower speed", "Zuluftdrehzahl", "toerental aanvoerventilator", "Tilluftflöde", "prędkość wentylatora nawiewu", "", "", "iç fan hızı", "velocità aria di alimentazione", "rýchlosť ventilátora", "rychlost vnitřního ventilátoru") // TODO translate
MAKE_TRANSLATION(ventOutSpeed, "ventoutspeed", "out blower speed", "Abluftdrehzahl", "toerental afvoerventilator", "Frånluftflöde", "prędkość wentylatora wywiewu", "", "", "dış fan hızı", "velocità aria di scarico", "rýchlosť výstupného ventilátora", "rychlost venkovního ventilátoru") // TODO translate
MAKE_TRANSLATION(airquality, "airquality", "air quality (voc)", "Luftqualität (VOC)", "luchtkwaliteit (VOC)", "Luftkvalitet (VOC)", "jakość powietrza", "", "", "hava kalitesi(voc)", "qualità aria (VOC)", "kvalita vzduchu (voc)", "kvalita vzduchu (VOC)") // TODO translate
MAKE_TRANSLATION(airbypass, "bypass", "bypass", "Bypass", "Bypass", "Bypass", "obejścia", "bypass", "dérivation", "baypas", "Bypass", "obtokový", "bypassový")
// EM100
MAKE_TRANSLATION(minV, "minv", "min volt.", "min. Spannung", "", "", "minimalne napięcie", "", "", "", "", "min. napätie", "minimální napětí") // TODO translate
MAKE_TRANSLATION(maxV, "maxv", "max volt.", "max. Spannung", "", "", "maksymalne napięcie", "", "", "", "", "max. napätie", "maximální napětí") // TODO translate
MAKE_TRANSLATION(minT, "mint", "min temp.", "min. Temperatur", "", "", "minimalna temperatura", "", "", "", "", "min. tepl.", "minimální teplota") // TODO translate
MAKE_TRANSLATION(maxT, "maxt", "max temp.", "max. Temperatur", "", "", "maksymalna temperatura", "", "", "", "", "max. tepl.", "maximální teplota") // TODO translate
MAKE_TRANSLATION(setPoint, "setpoint", "set temp.", "Solltemperatur", "", "", "temperatura nastawu", "", "", "", "", "pož. teplota", "nastavená teplota") // TODO translate
MAKE_TRANSLATION(setPower, "setpower", "request power", "Sollleistung", "", "", "zadana moc", "", "", "", "", "pož. výkon", "požadovaný výkon") // TODO translate
MAKE_TRANSLATION(dip, "dip", "dip switch", "DIP-Schalter", "", "", "przełącznik DIP", "", "", "", "", "dip prepínač", "přepínač DIP") // TODO translate
MAKE_TRANSLATION(outPower, "outpow", "output IO1", "Ausgang IO1", "", "", "wyjście IO1", "", "", "", "", "výstup IO1", "výstup IO1") // TODO translate
MAKE_TRANSLATION(input, "input", "input", "Eingang", "", "", "wejście", "", "", "", "", "vstup", "vstup") // TODO translate
MAKE_TRANSLATION(minV, "minv", "min volt.", "min. Spannung", "", "min. spänning", "minimalne napięcie", "", "", "", "", "min. napätie", "minimální napětí") // TODO translate
MAKE_TRANSLATION(maxV, "maxv", "max volt.", "max. Spannung", "", "max. spänning", "maksymalne napięcie", "", "", "", "", "max. napätie", "maximální napětí") // TODO translate
MAKE_TRANSLATION(minT, "mint", "min temp.", "min. Temperatur", "", "min. temperatur", "minimalna temperatura", "", "", "", "", "min. tepl.", "minimální teplota") // TODO translate
MAKE_TRANSLATION(maxT, "maxt", "max temp.", "max. Temperatur", "", "max. temperatur", "maksymalna temperatura", "", "", "", "", "max. tepl.", "maximální teplota") // TODO translate
MAKE_TRANSLATION(setPoint, "setpoint", "set temp.", "Solltemperatur", "", "Börvärde", "temperatura nastawu", "", "", "", "", "pož. teplota", "nastavená teplota") // TODO translate
MAKE_TRANSLATION(setPower, "setpower", "request power", "Sollleistung", "", "Böreffekt", "zadana moc", "", "", "", "", "pož. výkon", "požadovaný výkon") // TODO translate
MAKE_TRANSLATION(dip, "dip", "dip switch", "DIP-Schalter", "", "DIP-strömställare", "przełącznik DIP", "", "", "", "", "dip prepínač", "přepínač DIP") // TODO translate
MAKE_TRANSLATION(outPower, "outpow", "output IO1", "Ausgang IO1", "", "Utgångsffekt", "wyjście IO1", "", "", "", "", "výstup IO1", "výstup IO1") // TODO translate
MAKE_TRANSLATION(input, "input", "input", "Eingang", "", "Ingång", "wejście", "", "", "", "", "vstup", "vstup") // TODO translate
/*
// unknown fields to track (SM10), only for testing
// **** NO TRANSLATION NEEDED ****
MAKE_TRANSLATION(data11, "data11", "unknown datafield 11", "", "", "", "nieznane pole danych 11", "", "", "", "", "neznáme dátové pole 11", "")
MAKE_TRANSLATION(data12, "data12", "unknown datafield 12", "", "", "", "nieznane pole danych 12", "", "", "", "", "neznáme dátové pole 12", "")
MAKE_TRANSLATION(data8, "data8", "unknown datafield 8", "", "", "", "nieznane pole danych 8", "", "", "", "", "neznáme dátové pole 8", "")
MAKE_TRANSLATION(data0, "data0", "unknown datafield 0", "", "", "", "nieznane pole danych 0", "", "", "", "", "neznáme dátové pole 0", "")
MAKE_TRANSLATION(data1, "data1", "unknown datafield 1", "", "", "", "nieznane pole danych 1", "", "", "", "", "neznáme dátové pole 1", "")
MAKE_TRANSLATION(setting3, "setting3", "unknown setting 3", "", "", "", "nieznane ustawienie 3", "", "", "", "", "neznáme dátové pole 3", "")
MAKE_TRANSLATION(setting4, "setting4", "unknown setting 4", "", "", "", "nieznane ustawienie 4", "", "", "", "", "neznáme dátové pole 4", "")
MAKE_TRANSLATION(data11, "data11", "unknown datafield 11", "", "", "okänt datafält 11", "nieznane pole danych 11", "", "", "", "", "neznáme dátové pole 11", "") // TODO translate
MAKE_TRANSLATION(data12, "data12", "unknown datafield 12", "", "", "okänt datafält 12", "nieznane pole danych 12", "", "", "", "", "neznáme dátové pole 12", "") // TODO translate
MAKE_TRANSLATION(data8, "data8", "unknown datafield 8", "", "", "okänt datafält 8", "nieznane pole danych 8", "", "", "", "", "neznáme dátové pole 8", "") // TODO translate
MAKE_TRANSLATION(data0, "data0", "unknown datafield 0", "", "", "okänt datafält 0", "nieznane pole danych 0", "", "", "", "", "neznáme dátové pole 0", "") // TODO translate
MAKE_TRANSLATION(data1, "data1", "unknown datafield 1", "", "", "okänt datafält 1", "nieznane pole danych 1", "", "", "", "", "neznáme dátové pole 1", "") // TODO translate
MAKE_TRANSLATION(setting3, "setting3", "unknown setting 3", "", "", "okänd inställning 3", "nieznane ustawienie 3", "", "", "", "", "neznáme dátové pole 3", "") // TODO translate
MAKE_TRANSLATION(setting4, "setting4", "unknown setting 4", "", "", "okänd inställning 4", "nieznane ustawienie 4", "", "", "", "", "neznáme dátové pole 4", "") // TODO translate
*/
// clang-format on

View File

@@ -310,8 +310,11 @@ ModbusMessage Modbus::handleRead(const ModbusMessage & request) {
}
}
if (error_code) {
LOG_ERROR("Unable to read raw device value %s for tag=%d - error_code = %d", modbusInfo->short_name, (int)tag, error_code);
if (uuid::get_uptime_sec() > 60 || error_code < -2) { // suppress not found messages for the first minute
LOG_ERROR("Unable to read raw device value %s for tag=%d - error_code = %d", modbusInfo->short_name, (int)tag, error_code);
}
response.setError(request.getServerID(), request.getFunctionCode(), SERVER_DEVICE_FAILURE);
return response;
}
response.add(request.getServerID());
@@ -414,9 +417,9 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) {
std::string path;
if (tag < DeviceValueTAG::TAG_HC1) {
path = std::string("ems-esp/") + std::string(EMSdevice::device_type_2_device_name(device_type)) + "/" + modbusInfo->short_name;
path = std::string("api/") + std::string(EMSdevice::device_type_2_device_name(device_type)) + "/" + modbusInfo->short_name;
} else {
path = std::string("ems-esp/") + std::string(EMSdevice::device_type_2_device_name(device_type)) + "/" + EMSdevice::tag_to_mqtt(tag) + "/"
path = std::string("api/") + std::string(EMSdevice::device_type_2_device_name(device_type)) + "/" + EMSdevice::tag_to_mqtt(tag) + "/"
+ modbusInfo->short_name;
}

View File

@@ -55,114 +55,114 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maintenanceDate), 71, 6), // maintenancedate
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(emergencyOps), 77, 1), // emergencyops
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(emergencyTemp), 78, 1), // emergencytemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgTotal), 79, 2), // nrgtotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat), 81, 2), // nrgheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgCool), 83, 2), // nrgcool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterTotal), 85, 2), // metertotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterComp), 87, 2), // metercomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterEHeat), 89, 2), // metereheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterHeat), 91, 2), // meterheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterCool), 93, 2), // metercool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeTotal), 95, 2), // uptimetotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeControl), 97, 2), // uptimecontrol
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompHeating), 99, 2), // uptimecompheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompCooling), 101, 2), // uptimecompcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompPool), 103, 2), // uptimecomppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(totalCompStarts), 105, 2), // totalcompstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingStarts), 107, 2), // heatingstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(coolingStarts), 109, 2), // coolingstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(poolStarts), 111, 2), // poolstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsTotal), 113, 2), // nrgconstotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompTotal), 115, 2), // nrgconscomptotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompHeating), 117, 2), // nrgconscompheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompCooling), 119, 2), // nrgconscompcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompPool), 121, 2), // nrgconscomppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsTotal), 123, 2), // auxelecheatnrgconstotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsHeating), 125, 2), // auxelecheatnrgconsheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsPool), 127, 2), // auxelecheatnrgconspool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppTotal), 129, 2), // nrgsupptotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppHeating), 131, 2), // nrgsuppheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppCooling), 133, 2), // nrgsuppcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppPool), 135, 2), // nrgsupppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPower), 137, 1), // hppower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpMaxPower), 138, 1), // hpmaxpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pvMaxComp), 139, 1), // pvmaxcomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(powerReduction), 140, 1), // powerreduction
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpSetDiffPress), 141, 1), // hpsetdiffpress
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCompOn), 142, 1), // hpcompon
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpActivity), 143, 1), // hpactivity
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrinePumpSpd), 144, 1), // hpbrinepumpspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpSwitchValve), 145, 1), // hpswitchvalve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCompSpd), 146, 1), // hpcompspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCircSpd), 147, 1), // hpcircspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrineIn), 148, 1), // hpbrinein
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrineOut), 149, 1), // hpbrineout
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc0), 150, 1), // hptc0
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc1), 151, 1), // hptc1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc3), 152, 1), // hptc3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr1), 153, 1), // hptr1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr3), 154, 1), // hptr3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr4), 155, 1), // hptr4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr5), 156, 1), // hptr5
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr6), 157, 1), // hptr6
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr7), 158, 1), // hptr7
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTl2), 159, 1), // hptl2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPl1), 160, 1), // hppl1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPh1), 161, 1), // hpph1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTa4), 162, 1), // hpta4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTw1), 163, 1), // hptw1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(poolSetTemp), 164, 1), // poolsettemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hp4wayValve), 165, 1), // hp4way
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput1), 166, 1), // hpin1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn1Opt), 167, 8), // hpin1opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput2), 175, 1), // hpin2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn2Opt), 176, 8), // hpin2opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput3), 184, 1), // hpin3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn3Opt), 185, 8), // hpin3opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput4), 193, 1), // hpin4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn4Opt), 194, 8), // hpin4opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maxHeatComp), 202, 1), // maxheatcomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maxHeatHeat), 203, 1), // maxheatheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(manDefrost), 204, 1), // mandefrost
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pvCooling), 205, 1), // pvcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterOnly), 206, 1), // auxheateronly
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterOff), 207, 1), // auxheateroff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterStatus), 208, 1), // auxheaterstatus
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterLevel), 209, 1), // auxheaterlevel
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterDelay), 210, 1), // auxheaterdelay
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxMaxLimit), 211, 1), // auxmaxlimit
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxLimitStart), 212, 1), // auxlimitstart
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeatMode), 213, 1), // auxheatrmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystHeat), 214, 1), // hphystheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystCool), 215, 1), // hphystcool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystPool), 216, 1), // hphystpool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentMode), 217, 1), // silentmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentFrom), 218, 1), // silentfrom
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentTo), 219, 1), // silentto
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(minTempSilent), 220, 1), // mintempsilent
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempParMode), 221, 1), // tempparmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeatMixValve), 222, 1), // auxheatmix
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempDiffHeat), 223, 1), // tempdiffheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempDiffCool), 224, 1), // tempdiffcool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(vp_cooling), 225, 1), // vpcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatCable), 226, 1), // heatcable
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(VC0valve), 227, 1), // vc0valve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(primePump), 228, 1), // primepump
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(primePumpMod), 229, 1), // primepumpmod
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hp3wayValve), 230, 1), // hp3way
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep1), 231, 1), // elheatstep1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep2), 232, 1), // elheatstep2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep3), 233, 1), // elheatstep3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpEA0), 234, 1), // hpea0
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPumpMode), 235, 1), // hppumpmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpFan), 236, 1), // fan
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpShutdown), 237, 1), // shutdown
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCurrPower), 238, 1), // hpcurrpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPowerLimit), 239, 1), // hppowerlimit
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pc0Flow), 240, 1), // pc0flow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pc1Flow), 241, 1), // pc1flow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pc1On), 242, 1), // pc1on
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pc1Rate), 243, 1), // pc1rate
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pc0Flow), 79, 1), // pc0flow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pc1Flow), 80, 1), // pc1flow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pc1On), 81, 1), // pc1on
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pc1Rate), 82, 1), // pc1rate
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgTotal), 83, 2), // nrgtotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat), 85, 2), // nrgheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgCool), 87, 2), // nrgcool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterTotal), 89, 2), // metertotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterComp), 91, 2), // metercomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterEHeat), 93, 2), // metereheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterHeat), 95, 2), // meterheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterCool), 97, 2), // metercool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeTotal), 99, 2), // uptimetotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeControl), 101, 2), // uptimecontrol
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompHeating), 103, 2), // uptimecompheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompCooling), 105, 2), // uptimecompcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompPool), 107, 2), // uptimecomppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(totalCompStarts), 109, 2), // totalcompstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingStarts), 111, 2), // heatingstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(coolingStarts), 113, 2), // coolingstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(poolStarts), 115, 2), // poolstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsTotal), 117, 2), // nrgconstotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompTotal), 119, 2), // nrgconscomptotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompHeating), 121, 2), // nrgconscompheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompCooling), 123, 2), // nrgconscompcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompPool), 125, 2), // nrgconscomppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsTotal), 127, 2), // auxelecheatnrgconstotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsHeating), 129, 2), // auxelecheatnrgconsheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsPool), 131, 2), // auxelecheatnrgconspool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppTotal), 133, 2), // nrgsupptotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppHeating), 135, 2), // nrgsuppheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppCooling), 137, 2), // nrgsuppcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppPool), 139, 2), // nrgsupppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPower), 141, 1), // hppower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpMaxPower), 142, 1), // hpmaxpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pvMaxComp), 143, 1), // pvmaxcomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(powerReduction), 144, 1), // powerreduction
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpSetDiffPress), 145, 1), // hpsetdiffpress
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCompOn), 146, 1), // hpcompon
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpActivity), 147, 1), // hpactivity
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrinePumpSpd), 148, 1), // hpbrinepumpspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpSwitchValve), 149, 1), // hpswitchvalve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCompSpd), 150, 1), // hpcompspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCircSpd), 151, 1), // hpcircspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrineIn), 152, 1), // hpbrinein
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrineOut), 153, 1), // hpbrineout
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc0), 154, 1), // hptc0
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc1), 155, 1), // hptc1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc3), 156, 1), // hptc3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr1), 157, 1), // hptr1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr3), 158, 1), // hptr3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr4), 159, 1), // hptr4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr5), 160, 1), // hptr5
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr6), 161, 1), // hptr6
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr7), 162, 1), // hptr7
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTl2), 163, 1), // hptl2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPl1), 164, 1), // hppl1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPh1), 165, 1), // hpph1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTa4), 166, 1), // hpta4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTw1), 167, 1), // hptw1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(poolSetTemp), 168, 1), // poolsettemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hp4wayValve), 169, 1), // hp4way
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput1), 170, 1), // hpin1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn1Opt), 171, 8), // hpin1opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput2), 179, 1), // hpin2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn2Opt), 180, 8), // hpin2opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput3), 188, 1), // hpin3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn3Opt), 189, 8), // hpin3opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput4), 197, 1), // hpin4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn4Opt), 198, 8), // hpin4opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maxHeatComp), 206, 1), // maxheatcomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maxHeatHeat), 207, 1), // maxheatheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(manDefrost), 208, 1), // mandefrost
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pvCooling), 209, 1), // pvcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterOnly), 210, 1), // auxheateronly
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterOff), 211, 1), // auxheateroff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterStatus), 212, 1), // auxheaterstatus
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterLevel), 213, 1), // auxheaterlevel
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterDelay), 214, 1), // auxheaterdelay
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxMaxLimit), 215, 1), // auxmaxlimit
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxLimitStart), 216, 1), // auxlimitstart
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeatMode), 217, 1), // auxheatrmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystHeat), 218, 1), // hphystheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystCool), 219, 1), // hphystcool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystPool), 220, 1), // hphystpool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentMode), 221, 1), // silentmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentFrom), 222, 1), // silentfrom
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentTo), 223, 1), // silentto
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(minTempSilent), 224, 1), // mintempsilent
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempParMode), 225, 1), // tempparmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeatMixValve), 226, 1), // auxheatmix
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempDiffHeat), 227, 1), // tempdiffheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempDiffCool), 228, 1), // tempdiffcool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(vp_cooling), 229, 1), // vpcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatCable), 230, 1), // heatcable
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(VC0valve), 231, 1), // vc0valve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(primePump), 232, 1), // primepump
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(primePumpMod), 233, 1), // primepumpmod
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hp3wayValve), 234, 1), // hp3way
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep1), 235, 1), // elheatstep1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep2), 236, 1), // elheatstep2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep3), 237, 1), // elheatstep3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpEA0), 238, 1), // hpea0
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPumpMode), 239, 1), // hppumpmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpFan), 240, 1), // fan
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpShutdown), 241, 1), // shutdown
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCurrPower), 242, 1), // hpcurrpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPowerLimit), 243, 1), // hppowerlimit
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(exhaustTemp), 244, 1), // exhausttemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnGas), 245, 1), // burngas
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnGas2), 246, 1), // burngas2
@@ -266,28 +266,30 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaBuildingType), 50, 1), // building
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaMinExtTemperature), 51, 1), // minexttemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(damping), 52, 1), // damping
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(hybridStrategy), 53, 1), // hybridstrategy
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(switchOverTemp), 54, 1), // switchovertemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(energyCostRatio), 55, 1), // energycostratio
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(fossileFactor), 56, 1), // fossilefactor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(electricFactor), 57, 1), // electricfactor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(delayBoiler), 58, 1), // delayboiler
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempDiffBoiler), 59, 1), // tempdiffboiler
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvEnableWw), 60, 1), // pvenabledhw
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvRaiseHeat), 61, 1), // pvraiseheat
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvLowerCool), 62, 1), // pvlowercool
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaMainDisplay), 63, 1), // display
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaLanguage), 64, 1), // language
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaClockOffset), 65, 1), // clockoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempsensor1), 66, 1), // inttemp1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempsensor2), 67, 1), // inttemp2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(autodst), 68, 1), // autodst
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(backlight), 69, 1), // backlight
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(brightness), 70, 1), // brightness
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(mixingvalves), 71, 1), // mixingvalves
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(heatingPID), 72, 1), // heatingpid
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(preheating), 73, 1), // preheating
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(vacations), 74, 13), // vacations
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(hasSolar), 53, 1), // solar
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(hybridStrategy), 54, 1), // hybridstrategy
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(switchOverTemp), 55, 1), // switchovertemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(energyCostRatio), 56, 1), // energycostratio
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(fossileFactor), 57, 1), // fossilefactor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(electricFactor), 58, 1), // electricfactor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(delayBoiler), 59, 1), // delayboiler
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempDiffBoiler), 60, 1), // tempdiffboiler
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvEnableWw), 61, 1), // pvenabledhw
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvRaiseHeat), 62, 1), // pvraiseheat
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvLowerCool), 63, 1), // pvlowercool
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(absent), 64, 1), // absent
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaMainDisplay), 65, 1), // display
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaLanguage), 66, 1), // language
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaClockOffset), 67, 1), // clockoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempsensor1), 68, 1), // inttemp1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempsensor2), 69, 1), // inttemp2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(autodst), 70, 1), // autodst
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(backlight), 71, 1), // backlight
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(brightness), 72, 1), // brightness
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(mixingvalves), 73, 1), // mixingvalves
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(heatingPID), 74, 1), // heatingpid
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(preheating), 75, 1), // preheating
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(vacations), 76, 13), // vacations
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(selRoomTemp), 0, 1), // seltemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomTemp), 1, 1), // currtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(haclimate), 2, 1), // haclimate
@@ -312,64 +314,68 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpoperatingmode), 21, 1), // hpoperatingmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(summermode), 22, 1), // summermode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpoperatingstate), 23, 1), // hpoperatingstate
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(controlmode), 24, 1), // controlmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(program), 25, 1), // program
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(tempautotemp), 26, 1), // tempautotemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remoteseltemp), 27, 1), // remoteseltemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(fastheatup), 28, 1), // fastheatup
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchonoptimization), 29, 1), // switchonoptimization
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducemode), 30, 1), // reducemode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(noreducetemp), 31, 1), // noreducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducetemp), 32, 1), // reducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(wwprio), 33, 1), // dhwprio
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpcooling), 34, 1), // hpcooling
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolingOn), 35, 1), // coolingon
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpmode), 36, 1), // hpmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(dewoffset), 37, 1), // dewoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomtempdiff), 38, 1), // roomtempdiff
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpminflowtemp), 39, 1), // hpminflowtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(control), 40, 1), // control
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remotetemp), 41, 1), // remotetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remotehum), 42, 1), // remotehum
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatondelay), 43, 1), // heatondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatoffdelay), 44, 1), // heatoffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(instantstart), 45, 1), // instantstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boost), 46, 1), // boost
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boosttime), 47, 1), // boosttime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolstart), 48, 1), // coolstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolondelay), 49, 1), // coolondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(cooloffdelay), 50, 1), // cooloffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchProgMode), 51, 1), // switchprogmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daytemp), 52, 1), // daytemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(nighttemp2), 53, 1), // nighttemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidaytemp), 54, 1), // holidaytemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidaymode), 55, 1), // holidaymode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(flowtempoffset), 56, 1), // flowtempoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidays), 57, 13), // holidays
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations), 70, 13), // vacations
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(pause), 83, 1), // pause
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(party), 84, 1), // party
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacreducetemp), 85, 1), // vacreducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacreducemode), 86, 1), // vacreducemode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchtime1), 87, 8), // switchtime1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchtime2), 95, 8), // switchtime2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(offtemp), 103, 1), // offtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daylowtemp), 104, 1), // daytemp2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daymidtemp), 105, 1), // daytemp3
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(dayhightemp), 106, 1), // daytemp4
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchtime), 107, 8), // switchtime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations1), 115, 11), // vacations1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations2), 126, 11), // vacations2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations3), 137, 11), // vacations3
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations4), 148, 11), // vacations4
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations5), 159, 11), // vacations5
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations6), 170, 11), // vacations6
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations7), 181, 11), // vacations7
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducehours), 192, 1), // reducehours
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reduceminutes), 193, 1), // reduceminutes
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heattemp), 194, 1), // heattemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomsensor), 195, 1), // roomsensor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatup), 196, 1), // heatup
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacationmode), 24, 1), // vacationmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(controlmode), 25, 1), // controlmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(program), 26, 1), // program
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(tempautotemp), 27, 1), // tempautotemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(cooltemp), 28, 1), // cooltemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(fastheatup), 29, 1), // fastheatup
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchonoptimization), 30, 1), // switchonoptimization
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducemode), 31, 1), // reducemode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(noreducetemp), 32, 1), // noreducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducetemp), 33, 1), // reducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(wwprio), 34, 1), // dhwprio
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpcooling), 35, 1), // hpcooling
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolingOn), 36, 1), // coolingon
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpmode), 37, 1), // hpmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(dewoffset), 38, 1), // dewoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomtempdiff), 39, 1), // roomtempdiff
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpminflowtemp), 40, 1), // hpminflowtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(control), 41, 1), // control
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remotetemp), 42, 1), // remotetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remotehum), 43, 1), // remotehum
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatondelay), 44, 1), // heatondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatoffdelay), 45, 1), // heatoffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(instantstart), 46, 1), // instantstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boost), 47, 1), // boost
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boosttime), 48, 1), // boosttime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolstart), 49, 1), // coolstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolondelay), 50, 1), // coolondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(cooloffdelay), 51, 1), // cooloffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchProgMode), 52, 1), // switchprogmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(redthreshold), 53, 1), // redthreshold
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(solarinfl), 54, 1), // solarinfl
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(currsolarinfl), 55, 1), // currsolarinfl
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daytemp), 56, 1), // daytemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(nighttemp2), 57, 1), // nighttemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidaytemp), 58, 1), // holidaytemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidaymode), 59, 1), // holidaymode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(flowtempoffset), 60, 1), // flowtempoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidays), 61, 13), // holidays
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations), 74, 13), // vacations
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(pause), 87, 1), // pause
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(party), 88, 1), // party
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacreducetemp), 89, 1), // vacreducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacreducemode), 90, 1), // vacreducemode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchtime1), 91, 8), // switchtime1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchtime2), 99, 8), // switchtime2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(offtemp), 107, 1), // offtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daylowtemp), 108, 1), // daytemp2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daymidtemp), 109, 1), // daytemp3
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(dayhightemp), 110, 1), // daytemp4
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchtime), 111, 8), // switchtime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations1), 119, 11), // vacations1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations2), 130, 11), // vacations2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations3), 141, 11), // vacations3
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations4), 152, 11), // vacations4
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations5), 163, 11), // vacations5
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations6), 174, 11), // vacations6
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations7), 185, 11), // vacations7
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducehours), 196, 1), // reducehours
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reduceminutes), 197, 1), // reduceminutes
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heattemp), 198, 1), // heattemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomsensor), 199, 1), // roomsensor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatup), 200, 1), // heatup
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(mode), 0, 1), // mode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwSetTemp), 1, 1), // settemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwSetTempLow), 2, 1), // settemplow
@@ -526,6 +532,38 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(minT), 7, 1), // mint
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(maxT), 8, 1), // maxt
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(mode), 9, 1), // mode
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(sysFlowTemp), 0, 1), // sysflowtemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(sysRetTemp), 1, 1), // sysrettemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(aFlowTemp), 2, 1), // altflowtemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(aRetTemp), 3, 1), // altrettemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(cylTopTemp), 4, 1), // cyltoptemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(aCylCenterTemp), 5, 1), // cylcentertemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(cylBottomTemp), 6, 1), // cylbottomtemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(flueGasTemp), 7, 1), // fluegastemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(valveBuffer), 8, 1), // valvebuffer
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(valveReturn), 9, 1), // valvereturn
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(aPumpMod), 10, 1), // apumpmod
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(vr2Config), 11, 1), // vr2config
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(ahsActivated), 12, 1), // ahsactivated
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(aPumpConfig), 13, 1), // apumpconfig
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(aPumpSignal), 14, 1), // apumpsignal
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(aPumpMin), 15, 1), // apumpmin
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(tempRise), 16, 1), // temprise
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(setReturnTemp), 17, 1), // setreturntemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(mixRuntime), 18, 1), // mixruntime
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(setFlowTemp), 19, 1), // setflowtemp
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(bufBypass), 20, 1), // bufbypass
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(bufMixRuntime), 21, 1), // bufmixruntime
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(bufConfig), 22, 1), // bufconfig
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(blockMode), 23, 1), // blockmode
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(blockTerm), 24, 1), // blockterm
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(blockHyst), 25, 1), // blockhyst
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(releaseWait), 26, 1), // releasewait
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(burner), 27, 1), // burner
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(aPump), 28, 1), // apump
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(heatRequest), 29, 1), // heatrequest
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(blockRemain), 30, 1), // blockremain
REGISTER_MAPPING(dt::HEATSOURCE, TAG_TYPE_AHS, FL_(blockRemainWw), 31, 1), // blockremaindhw
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(outFresh), 0, 1), // outfresh
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(inFresh), 1, 1), // infresh
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(outEx), 2, 1), // outexhaust
@@ -535,6 +573,7 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(ventMode), 6, 1), // ventmode
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(airquality), 7, 1), // airquality
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(airHumidity), 8, 1), // airhumidity
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(airbypass), 9, 1), // bypass
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(selRoomTemp), 0, 1), // seltemp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwTemp), 1, 1), // temp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwCurTemp2), 2, 1), // curtemp2

View File

@@ -18,7 +18,6 @@
#include "mqtt.h"
#include "emsesp.h"
#include "version.h"
#include "emsdevice.h"
namespace emsesp {
@@ -67,12 +66,12 @@ std::string Mqtt::lastresponse_ = "";
// icons from https://materialdesignicons.com used with the UOMs (unit of measurements)
MAKE_WORD(measurement)
MAKE_WORD(total_increasing)
MAKE_WORD_CUSTOM(icondegrees, "mdi:coolant-temperature") // DeviceValueUOM::DEGREES
MAKE_WORD_CUSTOM(iconpercent, "mdi:percent-outline") // DeviceValueUOM::PERCENT
MAKE_WORD_CUSTOM(iconkb, "mdi:memory") // DeviceValueUOM::KB
MAKE_WORD_CUSTOM(iconlmin, "mdi:water-boiler") // DeviceValueUOM::LMIN
MAKE_WORD_CUSTOM(iconua, "mdi:lightning-bolt-circle") // DeviceValueUOM::UA
MAKE_WORD_CUSTOM(iconnum, "mdi:counter") // DeviceValueUOM::NONE
// MAKE_WORD_CUSTOM(icondegrees, "mdi:coolant-temperature") // DeviceValueUOM::DEGREES
MAKE_WORD_CUSTOM(iconpercent, "mdi:percent-outline") // DeviceValueUOM::PERCENT
MAKE_WORD_CUSTOM(iconkb, "mdi:memory") // DeviceValueUOM::KB
MAKE_WORD_CUSTOM(iconlmin, "mdi:water-boiler") // DeviceValueUOM::LMIN
MAKE_WORD_CUSTOM(iconua, "mdi:lightning-bolt-circle") // DeviceValueUOM::UA
MAKE_WORD_CUSTOM(iconnum, "mdi:counter") // DeviceValueUOM::NONE
uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON};
@@ -335,7 +334,7 @@ void Mqtt::reset_mqtt() {
// load the settings from service
void Mqtt::load_settings() {
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
EMSESP::esp32React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string
mqtt_qos_ = mqttSettings.mqtt_qos;
mqtt_retain_ = mqttSettings.mqtt_retain;
@@ -368,7 +367,7 @@ void Mqtt::load_settings() {
// start mqtt
void Mqtt::start() {
mqttClient_ = EMSESP::esp8266React.getMqttClient();
mqttClient_ = EMSESP::esp32React.getMqttClient();
load_settings(); // fetch MQTT settings
@@ -560,8 +559,8 @@ void Mqtt::ha_status() {
snprintf(topic, sizeof(topic), "binary_sensor/%s/system_status/config", mqtt_basename_.c_str());
Mqtt::queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
// create the sensors - must match the MQTT payload keys
// these are all from the heartbeat MQTT topic
// create the HA sensors - must match the MQTT payload keys in the heartbeat topic
// Note we don't use camelCase as it would change the HA entity_id and impact historic data
#ifndef EMSESP_STANDALONE
if (!EMSESP::system_.ethernet_connected() || WiFi.isConnected()) {
publish_system_ha_sensor_config(DeviceValueType::INT8, "WiFi RSSI", "rssi", DeviceValueUOM::DBM);
@@ -569,23 +568,26 @@ void Mqtt::ha_status() {
}
#endif
// These come from the heartbeat MQTT topic
// we don't use camelCase as it would change the HA entity_id and impact historic data
publish_system_ha_sensor_config(DeviceValueType::STRING, "EMS Bus", "bus_status", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::STRING, "Uptime", "uptime", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Uptime (sec)", "uptime_sec", DeviceValueUOM::SECONDS);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Free memory", "freemem", DeviceValueUOM::KB);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Max Alloc", "max_alloc", DeviceValueUOM::KB);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Max alloc", "max_alloc", DeviceValueUOM::KB);
publish_system_ha_sensor_config(DeviceValueType::INT8, "MQTT fails", "mqttfails", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Rx received", "rxreceived", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Rx fails", "rxfails", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Tx reads", "txreads", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Tx writes", "txwrites", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Tx fails", "txfails", DeviceValueUOM::NONE);
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
publish_system_ha_sensor_config(DeviceValueType::INT8, "CPU temperature", "temperature", DeviceValueUOM::DEGREES);
#endif
if (!EMSESP::system_.ethernet_connected()) {
publish_system_ha_sensor_config(DeviceValueType::INT16, "WiFi reconnects", "wifireconnects", DeviceValueUOM::NONE);
}
// This comes from the info MQTT topic - and handled in the publish_ha_sensor_config function
// This one comes from the info MQTT topic - and handled in the publish_ha_sensor_config function
publish_system_ha_sensor_config(DeviceValueType::STRING, "Version", "version", DeviceValueUOM::NONE);
}
@@ -1115,15 +1117,16 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
return queue_ha(topic, doc.as<JsonObject>());
}
// Add the state class, device class and an optional icon based on the uom
void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, const char * entity) {
const char * dc_ha = "dev_cla"; // device class
const char * sc_ha = "stat_cla"; // state class
// Add the uom, state class, device class and an optional icon based on the uom
void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, const char * entity, bool is_discovery) {
// for HA discovery we use different namings
const char * dc_ha = is_discovery ? "dev_cla" : "device_class"; // device class
const char * sc_ha = is_discovery ? "stat_cla" : "state_class"; // state class
const char * uom_ha = is_discovery ? "unit_of_meas" : "uom"; // unit of measure
// set icon, except for booleans
// using HA specific codes from https://github.com/home-assistant/core/blob/dev/homeassistant/const.py
// set uom, unless boolean
// using HA uom specific codes from https://github.com/home-assistant/core/blob/dev/homeassistant/const.py
if (type != DeviceValueType::BOOL) {
const char * uom_ha = "unit_of_meas"; // unit of measure
if (uom == DeviceValueUOM::HOURS) {
doc[uom_ha] = "h";
} else if (uom == DeviceValueUOM::MINUTES) {
@@ -1131,7 +1134,7 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con
} else if (uom == DeviceValueUOM::SECONDS) {
doc[uom_ha] = "s";
} else if (uom != DeviceValueUOM::NONE) {
doc[uom_ha] = EMSdevice::uom_to_string(uom); // default
doc[uom_ha] = EMSdevice::uom_to_string(uom); // use default
} else if (discovery_type() != discoveryType::HOMEASSISTANT) {
// Domoticz use " " for a no-uom
doc[uom_ha] = " ";
@@ -1146,12 +1149,14 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con
case DeviceValueUOM::K:
doc[sc_ha] = F_(measurement);
doc[dc_ha] = "temperature";
doc["ic"] = F_(icondegrees); // icon
// override uom if fahrenheit
doc[uom_ha] = EMSESP::system_.fahrenheit() ? DeviceValue::DeviceValueUOM_s[DeviceValueUOM::FAHRENHEIT] : DeviceValue::DeviceValueUOM_s[uom];
break;
case DeviceValueUOM::PERCENT:
doc[sc_ha] = F_(measurement);
doc[dc_ha] = "power_factor";
doc["ic"] = F_(iconpercent); // icon
if (is_discovery)
doc["ic"] = F_(iconpercent); // icon
break;
case DeviceValueUOM::SECONDS:
case DeviceValueUOM::MINUTES:
@@ -1164,11 +1169,13 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con
doc[dc_ha] = "duration"; // https://github.com/emsesp/EMS-ESP32/issues/822
break;
case DeviceValueUOM::KB:
doc["ic"] = F_(iconkb);
if (is_discovery)
doc["ic"] = F_(iconkb);
break;
case DeviceValueUOM::LMIN:
case DeviceValueUOM::LH:
doc["ic"] = F_(iconlmin);
if (is_discovery)
doc["ic"] = F_(iconlmin);
doc[sc_ha] = F_(measurement);
break;
case DeviceValueUOM::WH:
@@ -1187,7 +1194,8 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con
doc[dc_ha] = "energy";
break;
case DeviceValueUOM::UA:
doc["ic"] = F_(iconua);
if (is_discovery)
doc["ic"] = F_(iconua);
doc[sc_ha] = F_(measurement);
break;
case DeviceValueUOM::BAR:
@@ -1212,7 +1220,8 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con
if ((type != DeviceValueType::STRING)
&& (type == DeviceValueType::INT8 || type == DeviceValueType::UINT8 || type == DeviceValueType::INT16 || type == DeviceValueType::UINT16
|| type == DeviceValueType::UINT24 || type == DeviceValueType::UINT32)) {
doc["ic"] = F_(iconnum); // set icon
if (is_discovery)
doc["ic"] = F_(iconnum); // set icon
// determine if its a measurement or total increasing
// most of the values are measurement. for example Tx Reads will increment but can be reset to 0 after a restart
// all the starts are increasing, and they are ULONGs

View File

@@ -111,7 +111,13 @@ class Mqtt {
#endif
static bool connected() {
return mqttClient_->connected();
return mqttClient_ ? mqttClient_->connected() : false;
}
static void disconnect() {
if (mqttClient_) {
mqttClient_->disconnect();
};
}
static MqttClient * client() {
@@ -226,7 +232,7 @@ class Mqtt {
static std::string tag_to_topic(uint8_t device_type, int8_t tag);
static void add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, const char * entity = nullptr);
static void add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, const char * entity = nullptr, bool is_discovery = true);
static void add_ha_sections_to_doc(const char * name,
const char * state_t,

View File

@@ -51,48 +51,6 @@ void Shower::start() {
FL_(coldshot_cmd),
CommandFlag::ADMIN_ONLY);
Command::add(
EMSdevice::DeviceType::SYSTEM,
F_(showertimer),
[&](const char * value, const int8_t id, JsonObject output) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
shower_timer_ = b;
EMSESP::webSettingsService.update([&](WebSettings & settings) {
if (settings.shower_timer != b) {
settings.shower_timer = b;
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
});
return true;
},
FL_(showertimer_cmd),
CommandFlag::ADMIN_ONLY);
Command::add(
EMSdevice::DeviceType::SYSTEM,
F_(showeralert),
[&](const char * value, const int8_t id, JsonObject output) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
shower_alert_ = b;
EMSESP::webSettingsService.update([&](WebSettings & settings) {
if (settings.shower_alert != b) {
settings.shower_alert = b;
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
});
return true;
},
FL_(showeralert_cmd),
CommandFlag::ADMIN_ONLY);
if (shower_timer_) {
set_shower_state(false, true); // turns shower to off and creates HA topic if not already done
}

View File

@@ -33,6 +33,14 @@ class Shower {
// commands
static bool command_coldshot(const char * value, const int8_t id);
void shower_timer(bool enable) {
shower_timer_ = enable;
}
void shower_alert(bool enable) {
shower_alert_ = enable;
}
private:
static uuid::log::Logger logger_;

View File

@@ -81,9 +81,12 @@ std::deque<Token> exprToTokens(const std::string & expr) {
} else if (strncmp(p, "abs", 3) == 0) {
p += 2;
tokens.emplace_back(Token::Type::Unary, "a", 5);
} else if (strncmp(p, "ln", 2) == 0) {
p += 1;
tokens.emplace_back(Token::Type::Unary, "l", 5);
} else if (strncmp(p, "log", 3) == 0) {
p += 2;
tokens.emplace_back(Token::Type::Unary, "l", 5);
tokens.emplace_back(Token::Type::Unary, "g", 5);
} else if (strncmp(p, "exp", 3) == 0) {
p += 2;
tokens.emplace_back(Token::Type::Unary, "e", 5);
@@ -93,13 +96,19 @@ std::deque<Token> exprToTokens(const std::string & expr) {
} else if (strncmp(p, "pow", 3) == 0) {
p += 2;
tokens.emplace_back(Token::Type::Unary, "p", 5);
} else if (*p >= 'a' && *p <= 'z') {
} else if (strncmp(p, "tohex", 5) == 0) {
p += 4;
tokens.emplace_back(Token::Type::Unary, "x", 5);
} else if (strncmp(p, "hex", 3) == 0) {
p += 2;
tokens.emplace_back(Token::Type::Unary, "h", 5);
} else if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p == '_') || (*p & 0x80)) {
const auto * b = p;
while ((*p >= 'a' && *p <= 'z') || (*p == '_')) {
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p == '_') || (*p & 0x80)) {
++p;
}
const auto s = std::string(b, p);
tokens.emplace_back(Token::Type::String, s, -2);
tokens.emplace_back(Token::Type::String, s, -3);
--p;
} else if (*p == '"') {
++p;
@@ -129,7 +138,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
++p;
}
const auto s = std::string(b, p);
tokens.emplace_back(Token::Type::Number, s, -4);
tokens.emplace_back(Token::Type::Number, s, -2);
--p;
} else {
Token::Type token = Token::Type::Operator;
@@ -200,7 +209,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
precedence = 1;
token = Token::Type::Compare;
} else {
precedence = 1;
precedence = 2;
token = Token::Type::Unary;
}
break;
@@ -337,7 +346,7 @@ std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
// check if string is a number
bool isnum(const std::string & s) {
if (s.find_first_not_of("0123456789.") == std::string::npos || (s[0] == '-' && s.find_first_not_of("0123456789.", 1) == std::string::npos)) {
if (!s.empty() && (s.find_first_not_of("0123456789.") == std::string::npos || (s[0] == '-' && s.find_first_not_of("0123456789.", 1) == std::string::npos))) {
return true;
}
return false;
@@ -346,24 +355,22 @@ bool isnum(const std::string & s) {
// replace commands like "<device>/<hc>/<cmd>" with its value"
std::string commands(std::string & expr, bool quotes = true) {
auto expr_new = emsesp::Helpers::toLower(expr);
for (uint8_t device = 0; device < emsesp::EMSdevice::DeviceType::UNKNOWN; device++) {
const char * d = emsesp::EMSdevice::device_type_2_device_name(device);
auto f = expr.find(d);
auto f = expr_new.find(d);
while (f != std::string::npos) {
// entity names are alphanumeric or _
auto e = expr.find_first_not_of("/._abcdefghijklmnopqrstuvwxyz0123456789", f);
auto e = expr_new.find_first_not_of("/._abcdefghijklmnopqrstuvwxyz0123456789", f);
if (e == std::string::npos) {
e = expr.length();
}
while (e > 0 && expr[e - 1] == ' ') { // remove blanks from end
e--;
}
char cmd[COMMAND_MAX_LENGTH];
size_t l = e - f;
if (l >= sizeof(cmd) - 1) {
break;
}
expr.copy(cmd, l, f);
expr_new.copy(cmd, l, f);
cmd[l] = '\0';
if (strstr(cmd, "/value") == nullptr) {
strlcat(cmd, "/value", sizeof(cmd) - 6);
@@ -373,19 +380,22 @@ std::string commands(std::string & expr, bool quotes = true) {
JsonObject output = doc_out.to<JsonObject>();
JsonObject input = doc_in.to<JsonObject>();
std::string cmd_s = "api/" + std::string(cmd);
emsesp::Command::process(cmd_s.c_str(), true, input, output);
if (output["api_data"].is<std::string>()) {
std::string data = output["api_data"];
if (!isnum(data) && quotes) {
data.insert(data.begin(), '"');
data.insert(data.end(), '"');
}
expr.replace(f, l, data);
e = f + data.length();
} else {
auto return_code = emsesp::Command::process(cmd_s.c_str(), true, input, output);
// check for no value (entity is valid but has no value set)
if (return_code != emsesp::CommandRet::OK && return_code != emsesp::CommandRet::NO_VALUE) {
return expr = "";
}
f = expr.find(d, e);
std::string data = output["api_data"] | "";
if (!isnum(data) && quotes) {
data.insert(data.begin(), '"');
data.insert(data.end(), '"');
}
expr.replace(f, l, data);
e = f + data.length();
expr_new = emsesp::Helpers::toLower(expr);
f = expr_new.find(d, e);
}
}
return expr;
@@ -393,10 +403,14 @@ std::string commands(std::string & expr, bool quotes = true) {
// checks for logic value
int to_logic(const std::string & s) {
if (s[0] == '1' || s == "on" || s == "ON" || s == "true") {
if (s.empty()) {
return -1;
}
auto l = emsesp::Helpers::toLower(s);
if (s[0] == '1' || l == "on" || l == "true") {
return 1;
}
if (s[0] == '0' || s == "off" || s == "OFF" || s == "false") {
if (s[0] == '0' || l == "off" || l == "false") {
return 0;
}
return -1;
@@ -414,9 +428,17 @@ std::string to_string(double d) {
return s;
}
// number to hex string
std::string to_hex(uint32_t i) {
char c[10];
snprintf(c, 10, "%02X", i);
std::string s = c;
return s;
}
// RPN calculator
std::string calculate(const std::string & expr) {
auto expr_new = emsesp::Helpers::toLower(expr);
std::string expr_new = expr;
commands(expr_new);
const auto tokens = exprToTokens(expr_new);
@@ -453,42 +475,56 @@ std::string calculate(const std::string & expr) {
}
const auto rhs = stack.back();
stack.pop_back();
if (token.str[0] == '!') {
if (to_logic(rhs) < 0) {
}
if (to_logic(rhs) >= 0) {
stack.push_back(to_logic(rhs) == 0 ? "1" : "0");
} else if (isnum(rhs)) {
stack.push_back(std::stod(rhs) == 0 ? "1" : "0");
} else {
emsesp::EMSESP::logger().warning("missing operator");
return "";
}
break;
}
auto rhd = std::stod(rhs);
switch (token.str[0]) {
default:
return "";
break;
case 'm': // Special operator name for unary '-'
if (!isnum(rhs)) {
return "";
}
stack.push_back(to_string(-1 * std::stod(rhs)));
break;
case '!':
if (to_logic(rhs) < 0) {
return "";
}
stack.push_back(to_logic(rhs) == 0 ? "1" : "0");
stack.push_back(to_string(-1 * rhd));
break;
case 'i':
stack.push_back(to_string(std::stoi(rhs)));
stack.push_back(to_string(static_cast<int>(rhd)));
break;
case 'r':
stack.push_back(to_string(std::round(std::stod(rhs))));
stack.push_back(to_string(std::round(rhd)));
break;
case 'a':
stack.push_back(to_string(std::abs(std::stod(rhs))));
stack.push_back(to_string(std::abs(rhd)));
break;
case 'e':
stack.push_back(to_string(std::exp(std::stod(rhs))));
stack.push_back(to_string(std::exp(rhd)));
break;
case 'l':
stack.push_back(to_string(std::log(std::stod(rhs))));
stack.push_back(to_string(std::log(rhd)));
break;
case 'g':
stack.push_back(to_string(std::log10(rhd)));
break;
case 's':
stack.push_back(to_string(std::sqrt(std::stod(rhs))));
stack.push_back(to_string(std::sqrt(rhd)));
break;
case 'p':
stack.push_back(to_string(std::pow(std::stod(rhs), 2)));
stack.push_back(to_string(std::pow(rhd, 2)));
break;
case 'h':
stack.push_back(to_string(std::stoi(rhs, 0, 16)));
break;
case 'x':
stack.push_back(to_hex(static_cast<int>(rhd)));
break;
}
} break;
@@ -537,14 +573,16 @@ std::string calculate(const std::string & expr) {
stack.push_back((std::stod(lhs) == std::stod(rhs)) ? "1" : "0");
break;
}
stack.push_back((lhs == rhs) ? "1" : "0");
// compare strings lower case
stack.push_back((emsesp::Helpers::toLower(lhs) == emsesp::Helpers::toLower(rhs)) ? "1" : "0");
break;
case '!':
if (isnum(rhs) && isnum(lhs)) {
stack.push_back((std::stod(lhs) != std::stod(rhs)) ? "1" : "0");
break;
}
stack.push_back((lhs != rhs) ? "1" : "0");
// compare strings lower case
stack.push_back((emsesp::Helpers::toLower(lhs) != emsesp::Helpers::toLower(rhs)) ? "1" : "0");
break;
}
} break;
@@ -574,38 +612,40 @@ std::string calculate(const std::string & expr) {
} break;
case Token::Type::Operator: {
// binary operators
if (stack.empty() || !isnum(stack.back())) {
if (stack.size() < 2) {
return "";
}
const auto rhs = std::stod(stack.back());
const auto rhs = stack.back();
stack.pop_back();
if (stack.empty() || !isnum(stack.back())) {
return "";
const auto lhs = stack.back();
stack.pop_back();
if (token.str[0] == '+' && (!isnum(rhs) || !isnum(lhs))) {
stack.push_back(lhs + rhs);
break;
}
const auto lhs = std::stod(stack.back());
stack.pop_back();
auto lhd = std::stod(lhs);
auto rhd = std::stod(rhs);
switch (token.str[0]) {
default:
return "";
break;
case '^':
stack.push_back(to_string(pow(lhs, rhs)));
stack.push_back(to_string(pow(lhd, rhd)));
break;
case '*':
stack.push_back(to_string(lhs * rhs));
stack.push_back(to_string(lhd * rhd));
break;
case '/':
stack.push_back(to_string(lhs / rhs));
stack.push_back(to_string(lhd / rhd));
break;
case '%':
stack.push_back(std::to_string(static_cast<int>(lhs) % static_cast<int>(rhs)));
stack.push_back(std::to_string(static_cast<int>(lhd) % static_cast<int>(rhd)));
break;
case '+':
stack.push_back(to_string(lhs + rhs));
stack.push_back(to_string(lhd + rhd));
break;
case '-':
stack.push_back(to_string(lhs - rhs));
stack.push_back(to_string(lhd - rhd));
break;
}
} break;
@@ -628,7 +668,7 @@ std::string calculate(const std::string & expr) {
// check for multiple instances of <cond> ? <expr1> : <expr2>
std::string compute(const std::string & expr) {
auto expr_new = emsesp::Helpers::toLower(expr);
std::string expr_new = expr;
// search json with url:
auto f = expr_new.find_first_of('{');
@@ -647,17 +687,31 @@ std::string compute(const std::string & expr) {
JsonDocument doc;
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
HTTPClient http;
std::string url = doc["url"] | "";
std::string url, header_s, value_s, method_s, key_s;
// search keys lower case
for (JsonPair p : doc.as<JsonObject>()) {
if (emsesp::Helpers::toLower(p.key().c_str()) == "url") {
url = p.value().as<std::string>();
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "header") {
header_s = p.key().c_str();
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "value") {
value_s = p.key().c_str();
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "method") {
method_s = p.key().c_str();
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "key") {
key_s = p.key().c_str();
}
}
if (http.begin(url.c_str())) {
int httpResult = 0;
for (JsonPair p : doc["header"].as<JsonObject>()) {
for (JsonPair p : doc[header_s].as<JsonObject>()) {
http.addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
}
std::string value = doc["value"] | "";
std::string method = doc["method"] | "GET"; // default GET
std::string value = doc[value_s] | "";
std::string method = doc[method_s] | "get";
// if there is data, force a POST
if (value.length() || method == "post") {
if (value.length() || emsesp::Helpers::toLower(method) == "post") {
if (value.find_first_of('{') != std::string::npos) {
http.addHeader("Content-Type", "application/json"); // auto-set to JSON
}
@@ -667,8 +721,9 @@ std::string compute(const std::string & expr) {
}
if (httpResult > 0) {
std::string result = emsesp::Helpers::toLower(http.getString().c_str());
std::string key = doc["key"] | "";
std::string result = http.getString().c_str();
std::string key = doc[key_s] | "";
doc.clear();
if (key.length() && DeserializationError::Ok == deserializeJson(doc, result)) {
result = doc[key.c_str()].as<std::string>();
@@ -694,14 +749,29 @@ std::string compute(const std::string & expr) {
if (c1 == std::string::npos) {
return ""; // error: missing colon
}
std::string cond = calculate(expr_new.substr(0, q));
auto s = q; // start search brackets
int br = 0; // count brackets
while (s > 0 && (br || expr_new[s - 1] != '(')) {
s--;
br += (expr_new[s] == '(' ? -1 : expr_new[s] == ')' ? 1 : 0);
}
std::string cond = calculate(expr_new.substr(s, q - s));
if (cond.length() == 0) {
return "";
} else if (cond[0] == '1') {
expr_new.erase(c1); // remove second expression after colon
expr_new.erase(0, q + 1); // remove condition before questionmark
auto e = expr_new.length(); // end
if (s) { // there was a opening bracket, find the closing one
e = c1;
br = 0;
while (e < expr_new.length() && (br || expr_new[e + 1] != ')')) {
e++;
br += (expr_new[e] == ')' ? -1 : expr_new[e] == '(' ? 1 : 0);
}
}
expr_new.erase(c1, e + 1 - c1); // remove second expression after colon
expr_new.erase(s, q + 1 - s); // remove condition before questionmark
} else if (cond[0] == '0') {
expr_new.erase(0, c1 + 1); // remove condition and first expression
expr_new.erase(s, c1 + 1 - s); // remove condition and first expression
} else {
return ""; // error
}

View File

@@ -26,7 +26,7 @@
#include <semver200.h>
#if defined(EMSESP_TEST)
#include "test/test.h"
#include "../test/test.h"
#endif
#ifndef EMSESP_STANDALONE
@@ -83,8 +83,6 @@ uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
// init statics
PButton System::myPButton_;
bool System::restart_requested_ = false;
bool System::restart_pending_ = false;
bool System::test_set_all_active_ = false;
uint32_t System::max_alloc_mem_;
uint32_t System::heap_mem_;
@@ -105,6 +103,7 @@ bool System::command_send(const char * value, const int8_t id) {
return EMSESP::txservice_.send_raw(value); // ignore id
}
// returns last response from MQTT
bool System::command_response(const char * value, const int8_t id, JsonObject output) {
JsonDocument doc;
if (DeserializationError::Ok == deserializeJson(doc, Mqtt::get_response())) {
@@ -298,16 +297,15 @@ void System::system_restart(const char * partitionname) {
}
// make sure it's only executed once
restart_requested(false);
restart_pending(false);
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
store_nvs_values(); // save any NVS values
Shell::loop_all(); // flush log to output
Mqtt::disconnect(); // gracefully disconnect MQTT, needed for QOS1
delay(1000); // wait 1 second
ESP.restart();
#else
restart_requested(false);
restart_pending(false);
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
if (partitionname != nullptr) {
LOG_INFO("Restarting EMS-ESP from %s partition", partitionname);
} else {
@@ -318,12 +316,11 @@ void System::system_restart(const char * partitionname) {
// saves all settings
void System::wifi_reconnect() {
EMSESP::esp8266React.getNetworkSettingsService()->read(
EMSESP::esp32React.getNetworkSettingsService()->read(
[](NetworkSettings & networkSettings) { LOG_INFO("WiFi reconnecting to SSID '%s'...", networkSettings.ssid.c_str()); });
Shell::loop_all();
delay(1000); // wait a second
EMSESP::webSettingsService.save(); // save local settings
EMSESP::esp8266React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password
delay(500); // wait
EMSESP::webSettingsService.save(); // save local settings
EMSESP::esp32React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password
}
void System::syslog_init() {
@@ -385,6 +382,7 @@ void System::reload_settings() {
analog_enabled_ = settings.analog_enabled;
low_clock_ = settings.low_clock;
hide_led_ = settings.hide_led;
led_type_ = settings.led_type;
led_gpio_ = settings.led_gpio;
board_profile_ = settings.board_profile;
telnet_enabled_ = settings.telnet_enabled;
@@ -482,7 +480,7 @@ void System::start() {
#endif
#endif
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
hostname(networkSettings.hostname.c_str()); // sets the hostname
});
@@ -551,41 +549,29 @@ void System::button_init(bool refresh) {
// set the LED to on or off when in normal operating mode
void System::led_init(bool refresh) {
if (refresh) {
// disabled old led port before setting new one
if ((led_gpio_ != 0) && is_valid_gpio(led_gpio_)) {
led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
pinMode(led_gpio_, INPUT);
}
reload_settings();
}
if ((led_gpio_ != 0) && is_valid_gpio(led_gpio_)) {
#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1)
// rgb LED WS2812B, use Adafruit Neopixel
neopixelWrite(led_gpio_, 0, 0, 0);
#else
pinMode(led_gpio_, OUTPUT); // 0 means disabled
digitalWrite(led_gpio_, !LED_ON); // start with LED off
#endif
if ((led_gpio_ != 0) && is_valid_gpio(led_gpio_)) { // 0 means disabled
if (led_type_) {
// rgb LED WS2812B, use Neopixel
neopixelWrite(led_gpio_, 0, 0, 0);
} else {
pinMode(led_gpio_, OUTPUT);
digitalWrite(led_gpio_, !LED_ON); // start with LED off
}
}
}
// returns true if OTA is uploading
bool System::upload_isrunning() {
#if defined(EMSESP_STANDALONE)
return false;
#else
return upload_isrunning_ || Update.isRunning();
#endif
}
void System::upload_isrunning(bool in_progress) {
// if we've just started an upload
if (!upload_isrunning_ && in_progress) {
EMSuart::stop();
}
upload_isrunning_ = in_progress;
}
// checks system health and handles LED flashing wizardry
void System::loop() {
// check if we're supposed to do a reset/restart
if (restart_requested()) {
if (systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED) {
system_restart();
}
@@ -706,8 +692,8 @@ void System::heartbeat_json(JsonObject output) {
#ifndef EMSESP_STANDALONE
output["freemem"] = getHeapMem();
output["max_alloc"] = getMaxAllocMem();
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
output["temperature"] = temperature_;
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
output["temperature"] = (int)temperature_;
#endif
#endif
@@ -716,7 +702,7 @@ void System::heartbeat_json(JsonObject output) {
int8_t rssi = WiFi.RSSI();
output["rssi"] = rssi;
output["wifistrength"] = wifi_quality(rssi);
output["wifireconnects"] = EMSESP::esp8266React.getWifiReconnects();
output["wifireconnects"] = EMSESP::esp32React.getWifiReconnects();
}
#endif
}
@@ -747,7 +733,7 @@ void System::network_init(bool refresh) {
#if CONFIG_IDF_TARGET_ESP32
bool disableEth;
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { disableEth = settings.ssid.length() > 0; });
EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { disableEth = settings.ssid.length() > 0; });
// no ethernet present or disabled
if (phy_type_ == PHY_type::PHY_TYPE_NONE || disableEth) {
@@ -789,7 +775,10 @@ void System::system_check() {
last_system_check_ = uuid::get_uptime();
#ifndef EMSESP_STANDALONE
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if defined(CONFIG_IDF_TARGET_ESP32)
uint8_t raw = temprature_sens_read();
temperature_ = (raw - 32) / 1.8f; // convert to Celsius
#elif CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if ESP_IDF_VERSION_MAJOR < 5
temp_sensor_read_celsius(&temperature_);
#else
@@ -828,20 +817,12 @@ void System::system_check() {
if (healthcheck_ == 0) {
// everything is healthy, show LED permanently on or off depending on setting
if (led_gpio_) {
#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1)
neopixelWrite(led_gpio_, 0, hide_led_ ? 0 : 128, 0);
#else
digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
#endif
led_type_ ? neopixelWrite(led_gpio_, 0, hide_led_ ? 0 : 128, 0) : digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
}
} else {
// turn off LED so we're ready to the flashes
if (led_gpio_) {
#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1)
neopixelWrite(led_gpio_, 0, 0, 0);
#else
digitalWrite(led_gpio_, !LED_ON);
#endif
led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
}
}
}
@@ -901,60 +882,51 @@ void System::led_monitor() {
// reset the whole sequence
led_long_timer_ = uuid::get_uptime();
led_flash_step_ = 0;
#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1)
neopixelWrite(led_gpio_, 0, 0, 0);
#else
digitalWrite(led_gpio_, !LED_ON); // LED off
#endif
led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); // LED off
} else if (led_flash_step_ % 2) {
// handle the step events (on odd numbers 3,5,7,etc). see if we need to turn on a LED
// 1 flash is the EMS bus is not connected
// 2 flashes if the network (wifi or ethernet) is not connected
// 3 flashes is both the bus and the network are not connected. Then you know you're truly f*cked.
#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1)
if (led_flash_step_ == 3) {
if ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) {
if (led_type_) {
if (led_flash_step_ == 3) {
if ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) {
neopixelWrite(led_gpio_, 128, 0, 0); // red
} else if ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS) {
neopixelWrite(led_gpio_, 0, 0, 128); // blue
}
}
if (led_flash_step_ == 5 && (healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) {
neopixelWrite(led_gpio_, 128, 0, 0); // red
} else if ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS) {
}
if ((led_flash_step_ == 7) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)
&& ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS)) {
neopixelWrite(led_gpio_, 0, 0, 128); // blue
}
}
if (led_flash_step_ == 5 && (healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) {
neopixelWrite(led_gpio_, 128, 0, 0); // red
}
if ((led_flash_step_ == 7) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)
&& ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS)) {
neopixelWrite(led_gpio_, 0, 0, 128); // blue
}
#else
} else {
if ((led_flash_step_ == 3)
&& (((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) || ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS))) {
led_on_ = true;
}
if ((led_flash_step_ == 3)
&& (((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) || ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS))) {
led_on_ = true;
}
if ((led_flash_step_ == 5) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)) {
led_on_ = true;
}
if ((led_flash_step_ == 5) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)) {
led_on_ = true;
}
if ((led_flash_step_ == 7) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)
&& ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS)) {
led_on_ = true;
}
if ((led_flash_step_ == 7) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)
&& ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS)) {
led_on_ = true;
if (led_on_) {
digitalWrite(led_gpio_, LED_ON); // LED off
}
}
if (led_on_) {
digitalWrite(led_gpio_, LED_ON); // LED off
}
#endif
} else {
// turn the led off after the flash, on even number count
if (led_on_) {
#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1)
neopixelWrite(led_gpio_, 0, 0, 0);
#else
digitalWrite(led_gpio_, !LED_ON); // LED off
#endif
led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
led_on_ = false;
}
}
@@ -982,7 +954,7 @@ void System::show_users(uuid::console::Shell & shell) {
shell.printfln("Users:");
#ifndef EMSESP_STANDALONE
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
EMSESP::esp32React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
for (const User & user : securitySettings.users) {
shell.printfln(" username: %s, password: %s, is_admin: %s", user.username.c_str(), user.password.c_str(), user.admin ? ("yes") : ("no"));
}
@@ -1193,7 +1165,7 @@ bool System::check_restore() {
// returns true if we need a reboot
bool System::check_upgrade(bool factory_settings) {
bool missing_version = true;
std::string settingsVersion{EMSESP_APP_VERSION}; // default setting version
std::string settingsVersion;
if (!factory_settings) {
// fetch current version from settings file
@@ -1205,6 +1177,8 @@ bool System::check_upgrade(bool factory_settings) {
LOG_WARNING("No version information found");
settingsVersion = "3.5.0"; // this was the last stable version without version info
}
} else {
settingsVersion = EMSESP_APP_VERSION; // use the current version
}
version::Semver200_version settings_version(settingsVersion);
@@ -1245,12 +1219,12 @@ bool System::check_upgrade(bool factory_settings) {
// if we're coming from 3.4.4 or 3.5.0b14 which had no version stored then we need to apply new settings
if (missing_version) {
LOG_INFO("Upgrade: Setting MQTT Entity ID format to older v3.4 format (0)");
EMSESP::esp8266React.getMqttSettingsService()->update([&](MqttSettings & mqttSettings) {
EMSESP::esp32React.getMqttSettingsService()->update([&](MqttSettings & mqttSettings) {
mqttSettings.entity_format = Mqtt::entityFormat::SINGLE_LONG; // use old Entity ID format from v3.4
return StateUpdateResult::CHANGED;
});
} else if (settings_version.major() == 3 && settings_version.minor() <= 6) {
EMSESP::esp8266React.getMqttSettingsService()->update([&](MqttSettings & mqttSettings) {
EMSESP::esp32React.getMqttSettingsService()->update([&](MqttSettings & mqttSettings) {
if (mqttSettings.entity_format == 1) {
mqttSettings.entity_format = Mqtt::entityFormat::SINGLE_OLD; // use old Entity ID format from v3.6
LOG_INFO("Upgrade: Setting MQTT Entity ID format to v3.6 format (3)");
@@ -1265,7 +1239,7 @@ bool System::check_upgrade(bool factory_settings) {
}
// changes to Network
EMSESP::esp8266React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) {
EMSESP::esp32React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) {
// Network Settings Wifi tx_power is now using the value * 4.
if (networkSettings.tx_power == 20) {
networkSettings.tx_power = WIFI_POWER_19_5dBm; // use 19.5 as we don't have 20 anymore
@@ -1347,6 +1321,74 @@ bool System::saveSettings(const char * filename, const char * section, JsonObjec
return false; // not found
}
// set a entity of services 'network', 'settings', 'mqtt', etc.
bool System::command_service(const char * cmd, const char * value) {
bool ok = false;
bool b;
if (Helpers::value2bool(value, b)) {
if (!strcmp(cmd, "settings/showertimer")) {
EMSESP::webSettingsService.update([&](WebSettings & settings) {
settings.shower_timer = b;
return StateUpdateResult::CHANGED;
});
EMSESP::shower_.shower_timer(b);
ok = true;
} else if (!strcmp(cmd, "settings/showeralert")) {
EMSESP::webSettingsService.update([&](WebSettings & settings) {
settings.shower_alert = b;
return StateUpdateResult::CHANGED;
});
EMSESP::shower_.shower_alert(b);
ok = true;
} else if (!strcmp(cmd, "settings/hideled")) {
EMSESP::webSettingsService.update([&](WebSettings & settings) {
settings.hide_led = b;
return StateUpdateResult::CHANGED;
});
EMSESP::system_.hide_led(b);
ok = true;
} else if (!strcmp(cmd, "settings/analogenabled")) {
EMSESP::webSettingsService.update([&](WebSettings & settings) {
settings.analog_enabled = b;
return StateUpdateResult::CHANGED;
});
EMSESP::system_.analog_enabled(b);
ok = true;
} else if (!strcmp(cmd, "mqtt/enabled")) {
EMSESP::esp32React.getMqttSettingsService()->update([&](MqttSettings & Settings) {
Settings.enabled = b;
return StateUpdateResult::CHANGED;
});
ok = true;
} else if (!strcmp(cmd, "ap/enabled")) {
EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & Settings) {
Settings.provisionMode = b ? 0 : 2;
return StateUpdateResult::CHANGED;
});
ok = true;
} else if (!strcmp(cmd, "ntp/enabled")) {
EMSESP::esp32React.getNTPSettingsService()->update([&](NTPSettings & Settings) {
Settings.enabled = b;
return StateUpdateResult::CHANGED;
});
ok = true;
} else if (!strcmp(cmd, "syslog/enabled")) {
EMSESP::webSettingsService.update([&](WebSettings & settings) {
settings.syslog_enabled = b;
return StateUpdateResult::CHANGED;
});
EMSESP::system_.syslog_enabled_ = b;
EMSESP::system_.syslog_init();
ok = true;
}
}
if (ok) {
LOG_INFO("System command '%s' with value '%s'", cmd, value);
}
return ok;
}
// return back a system value
bool System::get_value_info(JsonObject output, const char * cmd) {
if (cmd == nullptr || strlen(cmd) == 0) {
@@ -1427,9 +1469,9 @@ void System::get_value_json(JsonObject output, const std::string & circuit, cons
if (circuit.length()) {
output["circuit"] = circuit;
}
output["readable"] = true;
output["writable"] = false;
output["visible"] = true;
output["readable"] = true;
output["writeable"] = (name == "showerTimer" || name == "showerAlert" || name == "enabled" || name == "hideLed" || name == "analogEnabled");
output["visible"] = true;
if (val.is<bool>()) {
output["value"] = val.as<bool>();
output["type"] = "boolean";
@@ -1449,7 +1491,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
// System
node = output["system"].to<JsonObject>();
// prevent false negative in Unity tests every time the version changes
// prevent false-negatives in Unity tests every time the version changes
#if defined(EMSESP_UNITY)
node["version"] = "dev";
#else
@@ -1458,16 +1500,17 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptimeSec"] = uuid::get_uptime_sec();
#ifndef EMSESP_STANDALONE
node["platform"] = EMSESP_PLATFORM;
node["cpuType"] = ESP.getChipModel();
node["arduino"] = ARDUINO_VERSION;
node["sdk"] = ESP.getSdkVersion();
node["freeMem"] = getHeapMem();
node["maxAlloc"] = getMaxAllocMem();
node["freeCaps"] = heap_caps_get_free_size(MALLOC_CAP_8BIT) / 1024; // includes heap and psram
node["usedApp"] = EMSESP::system_.appUsed(); // kilobytes
node["freeApp"] = EMSESP::system_.appFree(); // kilobytes
node["partition"] = esp_ota_get_running_partition()->label; // active partition
node["platform"] = EMSESP_PLATFORM;
node["cpuType"] = ESP.getChipModel();
node["arduino"] = ARDUINO_VERSION;
node["sdk"] = ESP.getSdkVersion();
node["freeMem"] = getHeapMem();
node["maxAlloc"] = getMaxAllocMem();
node["freeCaps"] = heap_caps_get_free_size(MALLOC_CAP_8BIT) / 1024; // includes heap and psram
node["usedApp"] = EMSESP::system_.appUsed(); // kilobytes
node["freeApp"] = EMSESP::system_.appFree(); // kilobytes
node["partition"] = esp_ota_get_running_partition()->label; // active partition
node["flash_chip_size"] = ESP.getFlashChipSize() / 1024; // kilobytes
#endif
node["resetReason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
#ifndef EMSESP_STANDALONE
@@ -1500,7 +1543,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["network"] = "WiFi";
node["hostname"] = WiFi.getHostname();
node["RSSI"] = WiFi.RSSI();
node["WIFIReconnects"] = EMSESP::esp8266React.getWifiReconnects();
node["WIFIReconnects"] = EMSESP::esp32React.getWifiReconnects();
// node["MAC"] = WiFi.macAddress();
// node["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask());
// node["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
@@ -1515,7 +1558,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["hostname"] = "ems-esp";
node["RSSI"] = -23;
#endif
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
if (WiFi.status() == WL_CONNECTED && !settings.bssid.isEmpty()) {
node["BSSID"] = "set"; // we don't disclose the name
}
@@ -1531,7 +1574,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
});
#ifndef EMSESP_STANDALONE
EMSESP::esp8266React.getAPSettingsService()->read([&](const APSettings & settings) {
EMSESP::esp32React.getAPSettingsService()->read([&](const APSettings & settings) {
const char * pM[] = {"always", "disconnected", "never"};
node["APProvisionMode"] = pM[settings.provisionMode];
node["APSecurity"] = settings.password.length() ? "wpa2" : "open";
@@ -1543,7 +1586,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node = output["ntp"].to<JsonObject>();
#ifndef EMSESP_STANDALONE
node["NTPStatus"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
EMSESP::esp8266React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
node["enabled"] = settings.enabled;
node["server"] = settings.server;
node["tzLabel"] = settings.tzLabel;
@@ -1559,7 +1602,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["MQTTPublishFails"] = Mqtt::publish_fails();
node["MQTTReconnects"] = Mqtt::connect_count();
}
EMSESP::esp8266React.getMqttSettingsService()->read([&](const MqttSettings & settings) {
EMSESP::esp32React.getMqttSettingsService()->read([&](const MqttSettings & settings) {
node["enabled"] = settings.enabled;
node["clientID"] = settings.clientId;
node["keepAlive"] = settings.keepAlive;
@@ -1674,6 +1717,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["dallasGPIO"] = settings.dallas_gpio;
node["pbuttonGPIO"] = settings.pbutton_gpio;
node["ledGPIO"] = settings.led_gpio;
node["ledType"] = settings.led_type;
}
node["hideLed"] = settings.hide_led;
node["noTokenApi"] = settings.notoken_api;
@@ -1686,11 +1730,14 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["analogEnabled"] = settings.analog_enabled;
node["telnetEnabled"] = settings.telnet_enabled;
node["maxWebLogBuffer"] = settings.weblog_buffer;
/*
#if defined(EMSESP_UNITY)
node["webLogBuffer"] = 0;
#else
node["webLogBuffer"] = EMSESP::webLogService.num_log_messages();
#endif
*/
node["modbusEnabled"] = settings.modbus_enabled;
node["forceHeatingOff"] = settings.boiler_heatingoff;
node["developerMode"] = settings.developer_mode;
@@ -1783,29 +1830,33 @@ bool System::command_test(const char * value, const int8_t id) {
// 3 = RMII clock output from GPIO17, for 50hz inverted clock
bool System::load_board_profile(std::vector<int8_t> & data, const std::string & board_profile) {
if (board_profile == "S32") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // BBQKees Gateway S32
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S32
} else if (board_profile == "E32") {
data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720, 16, 1, 0}; // BBQKees Gateway E32
data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720, 16, 1, 0, 0}; // BBQKees Gateway E32
} else if (board_profile == "E32V2") {
data = {2, 14, 4, 5, 34, PHY_type::PHY_TYPE_LAN8720, 15, 0, 1}; // BBQKees Gateway E32 V2
data = {2, 14, 4, 5, 34, PHY_type::PHY_TYPE_LAN8720, 15, 0, 1, 0}; // BBQKees Gateway E32 V2
} else if (board_profile == "MH-ET") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // MH-ET Live D1 Mini
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // MH-ET Live D1 Mini
} else if (board_profile == "NODEMCU") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // NodeMCU 32S
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // NodeMCU 32S
} else if (board_profile == "LOLIN") {
data = {2, 18, 17, 16, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin D32
data = {2, 18, 17, 16, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Lolin D32
} else if (board_profile == "OLIMEX") {
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, -1, 0, 0}; // Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Temperature sensor)
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, -1, 0, 0, 0}; // Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Temperature sensor)
} else if (board_profile == "OLIMEXPOE") {
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, 12, 0, 3}; // Olimex ESP32-POE
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, 12, 0, 3, 0}; // Olimex ESP32-POE
} else if (board_profile == "C3MINI") {
data = {7, 1, 4, 5, 9, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin C3 Mini
#if defined(BOARD_C3_MINI_V1)
data = {7, 1, 4, 5, 9, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Lolin C3 Mini V1
#else
data = {7, 1, 4, 5, 9, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 1}; // Lolin C3 Mini with RGB Led
#endif
} else if (board_profile == "S2MINI") {
data = {15, 7, 11, 12, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin S2 Mini
data = {15, 7, 11, 12, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Lolin S2 Mini
} else if (board_profile == "S3MINI") {
data = {17, 18, 8, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Liligo S3
data = {17, 18, 8, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Liligo S3
} else if (board_profile == "S32S3") {
data = {2, 18, 5, 17, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // BBQKees Gateway S3
data = {2, 18, 5, 17, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S3
} else if (board_profile == "CUSTOM") {
// send back current values
data = {(int8_t)EMSESP::system_.led_gpio_,
@@ -1816,7 +1867,8 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
(int8_t)EMSESP::system_.phy_type_,
EMSESP::system_.eth_power_,
(int8_t)EMSESP::system_.eth_phy_addr_,
(int8_t)EMSESP::system_.eth_clock_mode_};
(int8_t)EMSESP::system_.eth_clock_mode_,
(int8_t)EMSESP::system_.led_type_};
} else {
LOG_DEBUG("Couldn't identify board profile %s", board_profile.c_str());
return false; // unknown, return false
@@ -1826,7 +1878,7 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
return true;
}
// format command - factory reset, removing all config files
// format command - factory reset, removing all config fi`les
bool System::command_format(const char * value, const int8_t id) {
LOG_INFO("Removing all config files");
#ifndef EMSESP_STANDALONE
@@ -1840,8 +1892,8 @@ bool System::command_format(const char * value, const int8_t id) {
}
#endif
EMSESP::system_.restart_requested(true); // will be handled by the main loop
// restart will be handled by the main loop
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
return true;
}
@@ -1851,11 +1903,13 @@ bool System::command_restart(const char * value, const int8_t id) {
// if it has an id then it's a web call and we need to queue the restart
// default id is -1 when calling /api/system/restart directly for example
LOG_INFO("Preparing to restart system");
EMSESP::system_.restart_pending(true);
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART);
return true;
}
LOG_INFO("Restarting system immediately");
EMSESP::system_.restart_requested(true); // will be handled by the main loop
// restart will be handled by the main loop
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
return true;
}
@@ -1947,78 +2001,102 @@ String System::getBBQKeesGatewayDetails() {
// Stream from an URL and send straight to OTA uploader service.
//
// This function needs to be called twice, once with a url to persist it, and second with no arguments to start the upload
// This function needs to be called twice, 1st pass once with a url to persist it, 2nd pass with no arguments to start the upload
// This is to avoid timeouts in callback functions, like calling from a web hook.
bool System::uploadFirmwareURL(const char * url) {
#ifndef EMSESP_STANDALONE
static String saved_url;
// if the URL is not empty, store the URL for the 2nd pass
if (url && strlen(url) > 0) {
// if the passed URL is "reset" abort the current upload. This is called when an error happens during OTA
if (strncmp(url, "reset", 5) == 0) {
LOG_DEBUG("Firmware upload - resetting");
saved_url.clear();
return true;
}
// given a URL to download from, save it ready for the 2nd pass
saved_url = url;
EMSESP::system_.upload_isrunning(true); // tell EMS-ESP we're ready to start the uploading process
LOG_INFO("Firmware location: %s", saved_url.c_str());
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_PENDING_UPLOAD); // we're ready to start the upload
return true;
}
// make sure we have a valid URL
// check we have a valid URL from the 1st pass
if (saved_url.isEmpty()) {
LOG_ERROR("Firmware upload failed - invalid URL");
return false; // error
}
Shell::loop_all(); // flush log buffers so latest messages are shown in console
// Configure temporary client
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); // important for GitHub 302's
http.setTimeout(8000);
http.useHTTP10(true); // use HTTP/1.0 for update since the update handler not support any transfer Encoding
http.useHTTP10(true); // use HTTP/1.0 for update since the update handler does not support any transfer Encoding
http.begin(saved_url);
// start a connection, returns -1 if fails
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
LOG_ERROR("Firmware upload failed - HTTP code %d", httpCode);
http.end();
return false; // error
}
// check we have enough space for the upload in the ota partition
int firmware_size = http.getSize();
LOG_INFO("Firmware uploading (file: %s, size: %d bytes). Please wait...", saved_url.c_str(), firmware_size);
LOG_INFO("Firmware uploading (size: %d bytes). Please wait...", firmware_size);
if (!Update.begin(firmware_size)) {
LOG_ERROR("Firmware upload failed - no space");
http.end();
return false; // error
}
// flush log buffers so latest messages are shown
Shell::loop_all();
Shell::loop_all(); // flush log buffers so latest messages are shown in console
// we're about to start the upload, set the status so the Web System Monitor spots it
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING);
// TODO do we need to stop the UART first with EMSuart::stop() ?
// set a callback so we can monitor progress in the WebUI
Update.onProgress([](size_t progress, size_t total) { EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING + (progress * 100 / total)); });
// get tcp stream and send it to Updater
WiFiClient * stream = http.getStreamPtr();
if (Update.writeStream(*stream) != firmware_size) {
LOG_ERROR("Firmware upload failed - size differences");
http.end();
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
return false; // error
}
if (!Update.end(true)) {
LOG_ERROR("Firmware upload failed - general error");
http.end();
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
return false; // error
}
// finished with upload
http.end();
EMSESP::system_.upload_isrunning(false);
saved_url.clear(); // prevent from downloading again
LOG_INFO("Firmware uploaded successfully. Restarting...");
restart_pending(true);
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART);
#endif
return true; // OK
}
// read command, e.g. read <deviceID> <type ID> [offset] [length] from console or API
// from Console use quotes so: call system read "<deviceID> <type ID> [offset] [length]"
bool System::readCommand(const char * data) {
if (!data) {
return false;
}
// extract <deviceID> <type ID> [offset] [length] from string
char * p;
char value[11];
@@ -2037,7 +2115,7 @@ bool System::readCommand(const char * data) {
strlcpy(value, p, 10); // get string
device_id = (uint8_t)Helpers::hextoint(value); // convert hex to int
if (!EMSESP::valid_device(device_id)) {
LOG_ERROR("Invalid device ID (%d) for read command", device_id);
LOG_ERROR("Invalid device ID (0x%02X) in read command", device_id);
return false; // invalid device
}
}
@@ -2073,4 +2151,14 @@ bool System::command_read(const char * value, const int8_t id) {
return readCommand(value);
}
// set the system status code - SYSTEM_STATUS in system.h
void System::systemStatus(uint8_t status_code) {
systemStatus_ = status_code;
// LOG_DEBUG("Setting System status code %d", status_code);
}
uint8_t System::systemStatus() {
return systemStatus_;
}
} // namespace emsesp

View File

@@ -29,9 +29,6 @@
#ifndef EMSESP_STANDALONE
#include <esp_wifi.h>
#if CONFIG_IDF_TARGET_ESP32
// #include <esp_bt.h>
#endif
#include <ETH.h>
#include <uuid/syslog.h>
#endif
@@ -39,7 +36,12 @@
#include <uuid/log.h>
#include <PButton.h>
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if defined(CONFIG_IDF_TARGET_ESP32)
// there is no official API available on the original ESP32
extern "C" {
uint8_t temprature_sens_read();
}
#elif CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if ESP_IDF_VERSION_MAJOR < 5
#include "driver/temp_sensor.h"
#else
@@ -57,6 +59,15 @@ namespace emsesp {
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 };
enum SYSTEM_STATUS : uint8_t {
SYSTEM_STATUS_NORMAL = 0,
SYSTEM_STATUS_PENDING_UPLOAD = 1,
SYSTEM_STATUS_UPLOADING = 100,
SYSTEM_STATUS_ERROR_UPLOAD = 3,
SYSTEM_STATUS_PENDING_RESTART = 4,
SYSTEM_STATUS_RESTART_REQUESTED = 5
};
class System {
public:
void start();
@@ -73,6 +84,7 @@ class System {
static bool command_message(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject output);
static bool command_response(const char * value, const int8_t id, JsonObject output);
static bool command_service(const char * cmd, const char * value);
static bool get_value_info(JsonObject root, const char * cmd);
static void get_value_json(JsonObject output, const std::string & circuit, const std::string & name, JsonVariant val);
@@ -85,8 +97,7 @@ class System {
void store_nvs_values();
void system_restart(const char * partition = nullptr);
void upload_isrunning(bool in_progress);
bool upload_isrunning();
void show_mem(const char * note);
void reload_settings();
void syslog_init();
@@ -119,6 +130,9 @@ class System {
void button_init(bool refresh);
void commands_init();
void systemStatus(uint8_t status_code);
uint8_t systemStatus();
static void extractSettings(const char * filename, const char * section, JsonObject output);
static bool saveSettings(const char * filename, const char * section, JsonObject input);
@@ -127,20 +141,6 @@ class System {
static bool readCommand(const char * data);
static void restart_requested(bool restart_requested) {
restart_requested_ = restart_requested;
}
static bool restart_requested() {
return restart_requested_;
}
static void restart_pending(bool restart_pending) {
restart_pending_ = restart_pending;
}
static bool restart_pending() {
return restart_pending_;
}
bool telnet_enabled() {
return telnet_enabled_;
}
@@ -165,6 +165,14 @@ class System {
return analog_enabled_;
}
void analog_enabled(bool b) {
analog_enabled_ = b;
}
void hide_led(bool b) {
hide_led_ = b;
}
bool readonly_mode() {
return readonly_mode_;
}
@@ -322,7 +330,7 @@ class System {
test_set_all_active_ = n;
}
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
float temperature() {
return temperature_;
}
@@ -330,11 +338,12 @@ class System {
private:
static uuid::log::Logger logger_;
static bool restart_requested_;
static bool restart_pending_; // used in 2-stage process to call restart from Web API
static bool test_set_all_active_; // force all entities in a device to have a value
static uint32_t max_alloc_mem_;
static uint32_t heap_mem_;
static bool test_set_all_active_; // force all entities in a device to have a value
static uint32_t max_alloc_mem_;
static uint32_t heap_mem_;
uint8_t systemStatus_; // uses SYSTEM_STATUS enum
// button
static PButton myPButton_; // PButton instance
@@ -385,6 +394,7 @@ class System {
std::string hostname_;
String locale_;
bool hide_led_;
uint8_t led_type_;
uint8_t led_gpio_;
bool analog_enabled_;
bool low_clock_;
@@ -426,8 +436,8 @@ class System {
#if ESP_IDF_VERSION_MAJOR >= 5
temperature_sensor_handle_t temperature_handle_ = NULL;
#endif
float temperature_ = 0;
#endif
float temperature_ = 0;
};
} // namespace emsesp

View File

@@ -145,6 +145,12 @@ void RxService::add(uint8_t * data, uint8_t length) {
return;
}
// ignore src==0, https://github.com/emsesp/EMS-ESP32/issues/2378
if (!(data[0] & 0x7F)) {
LOG_WARNING("Invalid source: %s", Helpers::data_to_hex(data, length).c_str()); // include CRC
return;
}
// validate the CRC. if it fails then increment the number of corrupt/incomplete telegrams and only report to console/syslog
uint8_t crc = calculate_crc(data, length - 1);
if (data[length - 1] != crc) {

View File

@@ -37,7 +37,7 @@
#define MAX_TX_TELEGRAMS 200 // size of Tx queue
#else
#define MAX_RX_TELEGRAMS 10 // size of Rx queue
#define MAX_TX_TELEGRAMS 100 // size of Tx queue
#define MAX_TX_TELEGRAMS 160 // size of Tx queue
#endif
// default values for null values

View File

@@ -379,6 +379,7 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
return false; // not found
}
// note we don't add the device and state classes here, as we do in the custom entity service
void TemperatureSensor::get_value_json(JsonObject output, const Sensor & sensor) {
output["id"] = sensor.id();
output["name"] = sensor.name();
@@ -395,7 +396,6 @@ void TemperatureSensor::get_value_json(JsonObject output, const Sensor & sensor)
output["visible"] = true;
}
// publish a single sensor to MQTT
void TemperatureSensor::publish_sensor(const Sensor & sensor) {
if (Mqtt::enabled() && Mqtt::publish_single()) {

View File

@@ -112,7 +112,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatValve_, DeviceValueType::UINT8, FL_(heatValve), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DHW1, &wwValve_, DeviceValueType::UINT8, FL_(wwValve), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DHW1, &wwCurFlow_, DeviceValueType::UINT8, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwCurFlow), DeviceValueUOM::LMIN);
// register_device_value(DeviceValueTAG::TAG_DHW1, &wwCurFlow_, DeviceValueType::UINT8, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwCurFlow), DeviceValueUOM::LMIN);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&keepWarmTemp_,
DeviceValueType::UINT8,
@@ -335,6 +335,11 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
MAKE_CF_CB(set_emergency_temp),
15,
70);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pc0Flow_, DeviceValueType::INT16, FL_(pc0Flow), DeviceValueUOM::LH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pc1Flow_, DeviceValueType::INT16, FL_(pc1Flow), DeviceValueUOM::LH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pc1On_, DeviceValueType::BOOL, FL_(pc1On), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pc1Rate_, DeviceValueType::UINT8, FL_(pc1Rate), DeviceValueUOM::PERCENT);
/*
* Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
* thermostat always overwrites settings in boiler
@@ -541,7 +546,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(DeviceValueTAG::TAG_DHW1, &nrgSuppWw_, DeviceValueType::UINT24, FL_(nrgSuppWw), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppCooling_, DeviceValueType::UINT24, FL_(nrgSuppCooling), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppPool_, DeviceValueType::UINT24, FL_(nrgSuppPool), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPower_, DeviceValueType::UINT8, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPower), DeviceValueUOM::KW);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPower_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPower), DeviceValueUOM::KW);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&hpMaxPower_,
DeviceValueType::UINT8,
@@ -684,7 +689,12 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
FL_(auxHeaterOff),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_additionalHeater));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &auxHeaterStatus_, DeviceValueType::BOOL, FL_(auxHeaterStatus), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&auxHeaterStatus_,
DeviceValueType::ENUM,
FL_(enum_hpactivity),
FL_(auxHeaterStatus),
DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &auxHeaterLevel_, DeviceValueType::UINT8, FL_(auxHeaterLevel), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&auxHeaterDelay_,
@@ -856,10 +866,6 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
FL_(hpPowerLimit),
DeviceValueUOM::W,
MAKE_CF_CB(set_hpPowerLimit));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pc0Flow_, DeviceValueType::INT16, FL_(pc0Flow), DeviceValueUOM::LH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pc1Flow_, DeviceValueType::INT16, FL_(pc1Flow), DeviceValueUOM::LH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pc1On_, DeviceValueType::BOOL, FL_(pc1On), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pc1Rate_, DeviceValueType::UINT8, FL_(pc1Rate), DeviceValueUOM::PERCENT);
// heatpump DHW settings
register_device_value(DeviceValueTAG::TAG_DHW1,
@@ -1211,7 +1217,9 @@ void Boiler::process_UBAFactory(std::shared_ptr<const Telegram> telegram) {
return;
}
toggle_fetch(telegram->type_id, false); // only read once
uint8_t min, max, nomPower;
uint8_t min = 0;
uint8_t max = 0;
uint8_t nomPower = 0;
telegram->read_value(nomPower, 4);
telegram->read_value(min, 5);
telegram->read_value(max, 6);
@@ -1474,7 +1482,7 @@ void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
*/
void Boiler::process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, absBurnPow_, 13); // current burner absolute power (percent of rating plate power)
if (model() == EMSdevice::EMS_DEVICE_FLAG_HIU || model() == EMSdevice::EMS_DEVICE_FLAG_HEATPUMP) {
if (model() == EMSdevice::EMS_DEVICE_FLAG_HIU) {
uint8_t state = EMS_VALUE_UINT8_NOTSET;
boilerState_ = 0;
if (telegram->read_value(state, 2)) {
@@ -1484,6 +1492,7 @@ void Boiler::process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegra
if (telegram->read_value(state, 5)) {
boilerState_ |= state == 1 ? 0x0A : 0; // dhw 0/1
}
check_active(); // do a quick check to see if the hot water or heating is active
}
}
@@ -1658,13 +1667,17 @@ void Boiler::process_HpPower(std::shared_ptr<const Telegram> telegram) {
// has_bitupdate(telegram, heating_, 0, 0); // heating on? https://github.com/emsesp/EMS-ESP32/discussions/1898
has_bitupdate(telegram, hpSwitchValve_, 0, 4);
has_bitupdate(telegram, elHeatStep1_, 3, 0);
has_bitupdate(telegram, elHeatStep2_, 3, 1);
has_bitupdate(telegram, elHeatStep3_, 3, 2);
has_bitupdate(telegram, hpCompOn_, 3, 4);
has_bitupdate(telegram, hpEA0_, 3, 6);
has_update(telegram, hpCircSpd_, 4);
has_update(telegram, hpBrinePumpSpd_, 5);
has_update(telegram, auxHeaterLevel_, 6);
has_update(telegram, hpActivity_, 7);
has_update(telegram, hpPower_, 11);
has_update(telegram, auxHeaterStatus_, 8);
has_update(telegram, hpPower_, 10);
has_update(telegram, hpCompSpd_, 17);
has_bitupdate(telegram, hpInput[0].state, 1, 4);
@@ -1676,6 +1689,8 @@ void Boiler::process_HpPower(std::shared_ptr<const Telegram> telegram) {
// has_update(hpCoolingOn_, hpActivity_ == 2 ? 0xFF : 0);
// has_update(hpWwOn_, hpActivity_ == 3 ? 0xFF : 0);
// has_update(hpPoolOn_, hpActivity_ == 4 ? 0xFF : 0);
boilerState_ = hpActivity_ == 1 ? 0x09 : hpActivity_ == 3 ? 0x0A : 0;
check_active(); // do a quick check to see if the hot water or heating is active
}
// Heatpump temperatures - type 0x48F
@@ -1707,7 +1722,7 @@ void Boiler::process_HpPool(std::shared_ptr<const Telegram> telegram) {
// Heatpump inputs - type 0x4A2
// Boiler(0x08) -> All(0x00), ?(0x04A2), data: 02 01 01 00 01 00
// Boiler(0x08) -W-> Me(0x0B), HpInput(0x04A2), data: 20 07 06 01 00 (from #802)
// fix stetes 1/3 see https://github.com/emsesp/EMS-ESP32/issues/1388
// fix states 1/3 see https://github.com/emsesp/EMS-ESP32/issues/1388
void Boiler::process_HpInput(std::shared_ptr<const Telegram> telegram) {
has_bitupdate(telegram, hp4wayValve_, 0, 7);
// has_update(telegram, hpInput[0].state, 2);
@@ -1777,7 +1792,9 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
// UBASetPoint 0x1A
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
uint8_t setFlowTemp_, setBurnPow_, wwSetBurnPow_;
uint8_t setFlowTemp_ = 0;
uint8_t setBurnPow_ = 0;
uint8_t wwSetBurnPow_ = 0;
telegram->read_value(setFlowTemp_, 0);
telegram->read_value(setBurnPow_, 1);
telegram->read_value(wwSetBurnPow_, 2);
@@ -1831,15 +1848,19 @@ void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
uint32_t date = (year - 2000) * 535680UL + month * 44640UL + day * 1440UL + hour * 60 + min + duration;
// check valid https://github.com/emsesp/EMS-ESP32/issues/2189
if (day == 0 || day > 31 || month == 0 || month > 12 || !std::isprint(code[0]) || !std::isprint(code[1])) {
if (!lastCodeDate_) {
char newCode[sizeof(lastCode_)];
snprintf(newCode, sizeof(lastCode_), "%s(%d)", code, codeNo);
has_update(lastCode_, newCode, sizeof(lastCode_));
}
return;
}
// store only the newest code from telegrams 10 and 11
if (date > lastCodeDate_ && lastCodeDate_) {
if (date > lastCodeDate_) {
lastCodeDate_ = date;
snprintf(lastCode_, sizeof(lastCode_), "%s(%d) %02d.%02d.%d %02d:%02d (%d min)", code, codeNo, day, month, year, hour, min, duration);
has_update(lastCode_);
} else if (!lastCodeDate_) { // no publish for first read
lastCodeDate_ = 1;
char newCode[sizeof(lastCode_)];
snprintf(newCode, sizeof(lastCode_), "%s(%d) %02d.%02d.%d %02d:%02d (%d min)", code, codeNo, day, month, year, hour, min, duration);
has_update(lastCode_, newCode, sizeof(lastCode_));
}
}
@@ -1905,11 +1926,9 @@ void Boiler::process_UBAErrorMessage2(std::shared_ptr<const Telegram> telegram)
snprintf(&code[3], sizeof(code) - 3, "(%d) @uptime %lu - %lu min", codeNo, starttime, endtime);
date = starttime;
}
if (date > lastCodeDate_ && lastCodeDate_) {
if (date > lastCodeDate_) {
lastCodeDate_ = date;
has_update(lastCode_, code, sizeof(lastCode_));
} else if (!lastCodeDate_) {
lastCodeDate_ = 1;
}
}
@@ -1963,7 +1982,7 @@ void Boiler::process_HpSilentMode(std::shared_ptr<const Telegram> telegram) {
// Boiler(0x08) -B-> All(0x00), ?(0x0488), data: 8E 00 00 00 00 00 01 03
void Boiler::process_HpValve(std::shared_ptr<const Telegram> telegram) {
has_bitupdate(telegram, auxHeaterStatus_, 0, 2);
// has_bitupdate(telegram, auxHeaterStatus_, 0, 2);
has_update(telegram, auxHeatMixValve_, 7);
has_update(telegram, pc1Rate_, 13); // percent
}
@@ -2024,9 +2043,9 @@ void Boiler::process_HpSettings3(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, primePump_, 4);
has_update(telegram, primePumpMod_, 5);
// has_update(telegram, hp3wayValve_, 6); // read in 48D
has_update(telegram, elHeatStep1_, 7);
has_update(telegram, elHeatStep2_, 8);
has_update(telegram, elHeatStep3_, 9);
// has_update(telegram, elHeatStep1_, 7);
// has_update(telegram, elHeatStep2_, 8);
// has_update(telegram, elHeatStep3_, 9);
}
// boiler(0x08) -W-> Me(0x0B), ?(0x04AE), data: 00 00 BD C4 00 00 5B 6A 00 00 00 24 00 00 62 59 00 00 00 00 00 00 00 00
@@ -2750,6 +2769,11 @@ bool Boiler::set_ww_circulation_pump(const char * value, const int8_t id) {
write_command(EMS_TYPE_UBAParameterWW, 6, v ? 0xFF : 0x00, EMS_TYPE_UBAParameterWW);
}
#ifdef EMSESP_TEST
// set wwCircPump_ , for test "api4" in test.cpp
wwCircPump_ = v ? EMS_VALUE_BOOL_ON : EMS_VALUE_BOOL_OFF;
#endif
return true;
}
@@ -2931,7 +2955,7 @@ bool Boiler::set_HpInLogic(const char * value, const int8_t id) {
return true;
}
char option[] = {"xxxxxxxxxxxxxxx"};
strncpy(option, value, strlen(value)); // copy without termination
strncpy(option, value, strlen(option)); // copy without termination
// inputs 1,2,3 <inv>[<evu1><evu2><evu3><comp><aux><cool><heat><dhw><pv><prot><pres><mod>]
if (id < 4) {
uint8_t index[] = {0, 3, 6, 9, 12, 15, 18, 21, 24, 39, 36, 30};

View File

@@ -194,7 +194,7 @@ class Boiler : public EMSdevice {
uint16_t maintenanceTime_;
// heatpump
uint8_t hpPower_;
uint16_t hpPower_;
uint8_t hpCompOn_;
uint8_t hpBrinePumpSpd_;
uint8_t hpCompSpd_;

View File

@@ -184,8 +184,8 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
register_device_value(DeviceValueTAG::TAG_DHW1, &wwStartsHp_, DeviceValueType::UINT24, FL_(wwStartsHp), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &fuelHeat_, DeviceValueType::UINT32, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(fuelHeat), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DHW1, &elDhw_, DeviceValueType::UINT32, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(fuelDhw), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &fuelHeat_, DeviceValueType::UINT32, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(elHeat), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DHW1, &fuelDhw_, DeviceValueType::UINT32, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(fuelDhw), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &elHeat_, DeviceValueType::UINT32, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(elHeat), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DHW1, &elDhw_, DeviceValueType::UINT32, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(elDhw), DeviceValueUOM::KWH);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&elGenHeat_,

View File

@@ -133,6 +133,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
monitor_typeids = {0x0A};
set_typeids = {};
register_telegram_type(monitor_typeids[0], "EasyMonitor", true, MAKE_PF_CB(process_EasyMonitor));
register_telegram_type(0x02A5, "EasyMonitor", false, MAKE_PF_CB(process_EasyMonitor));
// CRF
} else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) {
@@ -142,6 +143,14 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(monitor_typeids[i], "CRFMonitor", false, MAKE_PF_CB(process_CRFMonitor));
}
} else if (model == EMSdevice::EMS_DEVICE_FLAG_CR11) {
monitor_typeids = {0x02A5};
set_typeids = {0x02B9};
curve_typeids = {0x029B};
register_telegram_type(monitor_typeids[0], "RC300Monitor", true, MAKE_PF_CB(process_CR11Monitor));
register_telegram_type(set_typeids[0], "RC300Set", false, MAKE_PF_CB(process_RC300Set));
register_telegram_type(curve_typeids[0], "RC300Curves", true, MAKE_PF_CB(process_RC300Curve));
// RC300/RC100 variants
} else if (isRC300() || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC};
@@ -171,10 +180,20 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0x31E, "RC300WWmode2", false, MAKE_PF_CB(process_RC300WWmode2));
register_telegram_type(0x23A, "RC300OutdoorTemp", true, MAKE_PF_CB(process_RC300OutdoorTemp));
register_telegram_type(0x267, "RC300Floordry", false, MAKE_PF_CB(process_RC300Floordry));
register_telegram_type(0x240, "RC300Settings", true, MAKE_PF_CB(process_RC300Settings));
if (model == EMSdevice::EMS_DEVICE_FLAG_RC100) {
register_telegram_type(0x241, "RC300Settings", true, MAKE_PF_CB(process_RC300Settings));
} else {
register_telegram_type(0x240, "RC300Settings", true, MAKE_PF_CB(process_RC300Settings));
}
register_telegram_type(0xBB, "HybridSettings", true, MAKE_PF_CB(process_HybridSettings));
register_telegram_type(0x23E, "PVSettings", true, MAKE_PF_CB(process_PVSettings));
register_telegram_type(0x269, "RC300Holiday1", true, MAKE_PF_CB(process_RC300Holiday));
if (model == EMSdevice::EMS_DEVICE_FLAG_RC100) {
register_telegram_type(0x43F, "CRHolidays", true, MAKE_PF_CB(process_RC300Holiday));
} else {
register_telegram_type(0x269, "RC300Holiday", true, MAKE_PF_CB(process_RC300Holiday));
}
register_telegram_type(0x16E, "Absent", true, MAKE_PF_CB(process_Absent));
// JUNKERS/HT3
} else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
@@ -484,6 +503,8 @@ uint8_t Thermostat::HeatingCircuit::get_mode() const {
} else if (mode == 1) {
return HeatingCircuit::Mode::OFF;
}
} else if (model == EMSdevice::EMS_DEVICE_FLAG_CR11) {
return HeatingCircuit::Mode::MANUAL;
} else if (model == EMSdevice::EMS_DEVICE_FLAG_BC400 || model == EMSdevice::EMS_DEVICE_FLAG_CR120) {
if (mode_new == 0) {
return HeatingCircuit::Mode::OFF;
@@ -840,13 +861,30 @@ void Thermostat::process_RC20Monitor(std::shared_ptr<const Telegram> telegram) {
// type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long
void Thermostat::process_EasyMonitor(std::shared_ptr<const Telegram> telegram) {
auto hc = heating_circuit(telegram);
monitor_typeids[0] = telegram->type_id;
auto hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
has_update(telegram, hc->roomTemp, 8); // is * 100
has_update(telegram, hc->selTemp, 10); // is * 100
if (telegram->type_id == 0x0A) {
int16_t temp = hc->roomTemp;
if (telegram->read_value(temp, 8) && temp != 0) {
has_update(telegram, hc->roomTemp, 8); // is * 100
has_update(telegram, hc->selTemp, 10); // is * 100
toggle_fetch(0x0A, true);
}
} else if (telegram->type_id == 0x02A5) { // see #2277
int16_t temp = hc->roomTemp / 10;
if (telegram->read_value(temp, 0)) { // is * 10
has_update(hc->roomTemp, temp * 10); // * 100
toggle_fetch(0x0A, false);
}
int16_t sel = hc->selTemp / 50;
if (telegram->read_value(sel, 6, 1)) { // is * 2
has_update(hc->selTemp, sel * 50); // * 100
}
}
add_ha_climate(hc);
}
@@ -995,6 +1033,11 @@ void Thermostat::process_PVSettings(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, pvLowerCool_, 5);
}
// 0x16E Absent settings - hc or dhw or device_data? #1957
void Thermostat::process_Absent(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, absent_, 0);
}
void Thermostat::process_JunkersSetMixer(std::shared_ptr<const Telegram> telegram) {
auto hc = heating_circuit(telegram);
if (hc == nullptr) {
@@ -1024,6 +1067,21 @@ void Thermostat::process_CRFMonitor(std::shared_ptr<const Telegram> telegram) {
add_ha_climate(hc);
}
// type 0x02A5 - data from CR11
void Thermostat::process_CR11Monitor(std::shared_ptr<const Telegram> telegram) {
auto hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
has_update(telegram, hc->roomTemp, 0); // is * 10
has_update(hc->mode, 0); // set manual for CR11
has_update(telegram, hc->selTemp, 6, 1); // is * 2, force as single byte
has_update(telegram, hc->targetflowtemp, 4);
add_ha_climate(hc);
}
// type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10
// Rx: 10 0B FF 00 01 A5 80 00 01 30 23 00 30 28 01 E7 03 03 01 01 E7 02 33 00 00 11 01 03 FF FF 00 04
void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram) {
@@ -1058,7 +1116,9 @@ void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram)
}
has_update(telegram, hc->targetflowtemp, 4);
has_update(telegram, hc->curroominfl, 27);
has_update(telegram, hc->currSolarInfl, 29);
has_update(telegram, hc->coolingon, 32);
has_update(telegram, hc->vacationmode, 18);
add_ha_climate(hc);
}
@@ -1067,7 +1127,7 @@ void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram)
// Thermostat(0x10) -> Me(0x0B), RC300Set(0x2B9), data: FF 2E 2A 26 1E 02 4E FF FF 00 1C 01 E1 20 01 0F 05 00 00 02 1F
void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
auto hc = heating_circuit(telegram);
if (hc == nullptr) {
if (hc == nullptr || model() == EMSdevice::EMS_DEVICE_FLAG_CR11) {
return;
}
@@ -1106,8 +1166,9 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
has_enumupdate(telegram, hc->reducemode, 5, 1); // 1-outdoor temp threshold, 2-room temp threshold, 3-reduced mode
has_update(telegram, hc->reducetemp, 9);
has_update(telegram, hc->noreducetemp, 12);
has_update(telegram, hc->remoteseltemp, 17); // see https://github.com/emsesp/EMS-ESP32/issues/590
has_update(telegram, hc->cooltemp, 17); // see https://github.com/emsesp/EMS-ESP32/issues/590 and 2456
has_enumupdate(telegram, hc->switchProgMode, 19, 1); // 1-level, 2-absolute
has_update(telegram, hc->redThreshold, 20);
has_update(telegram, hc->boost, 23);
has_update(telegram, hc->boosttime, 24);
has_update(telegram, hc->cooling, 28);
@@ -1124,6 +1185,7 @@ void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hc->roominfluence, 0);
has_update(telegram, hc->roominfl_factor, 1); // is * 10
has_update(telegram, hc->offsettemp, 2);
has_update(telegram, hc->solarInfl, 3);
if (!is_received(summer2_typeids[hc->hc()])) {
has_update(telegram, hc->summertemp, 6);
has_update(telegram, hc->summersetmode, 7);
@@ -1176,7 +1238,7 @@ void Thermostat::process_RC300Curve(std::shared_ptr<const Telegram> telegram) {
has_enumupdate(telegram, hc->nofrostmode, 5, 1); // 1-room, 2-outdoor, 3- room & outdoor
has_update(telegram, hc->nofrosttemp, 6);
if (hc->heatingtype < 3) {
if (hc->heatingtype < 2) {
has_update(telegram, hc->maxflowtemp, 8);
} else {
has_update(telegram, hc->maxflowtemp, 7);
@@ -1222,6 +1284,12 @@ void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wwmode, 2);
const uint8_t modes1[] = {0, 0, 1, 0, 2, 0};
has_update(dhw->wwMode_, wwmode < sizeof(modes1) ? modes1[wwmode] : EMS_VALUE_UINT8_NOTSET);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
const uint8_t modes[] = {0, 2, 3}; //// 0=off(0), 1=on(2), 2=auto(3)
uint8_t wwmode = dhw->wwMode_ < sizeof(modes) ? modes[dhw->wwMode_] : EMS_VALUE_UINT8_NOTSET;
telegram->read_value(wwmode, 2);
const uint8_t modes1[] = {0, 0, 1, 2, 0, 0};
has_update(dhw->wwMode_, wwmode < sizeof(modes1) ? modes1[wwmode] : EMS_VALUE_UINT8_NOTSET);
} else {
has_update(telegram, dhw->wwMode_, 2); // 0=off, 1=low, 2=high, 3=auto, 4=own prog
}
@@ -1254,13 +1322,14 @@ void Thermostat::process_RC300OutdoorTemp(std::shared_ptr<const Telegram> telegr
has_update(telegram, dampedoutdoortemp2_, 0); // is *10
}
// 0x240 RC300 parameter
// 0x240 RC300 parameter, 0x0241 for CW100, see https://github.com/emsesp/EMS-ESP32/issues/2290
// RC300Settings(0x240), data: 26 00 03 00 00 00 00 00 FF 01 F6 06 FF 00 00 00 00 00 00 00 00 00 00
void Thermostat::process_RC300Settings(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, ibaCalIntTemperature_, 7);
has_update(telegram, ibaDamping_, 8);
has_enumupdate(telegram, ibaBuildingType_, 9, 1); // 1=light, 2=medium, 3=heavy
has_update(telegram, ibaMinExtTemperature_, 10);
has_update(telegram, hasSolar_, 13);
}
// 0x2CC - e.g. wwprio for RC310 hcx parameter
@@ -1283,7 +1352,7 @@ void Thermostat::process_RC300Floordry(std::shared_ptr<const Telegram> telegram)
}
// 0x269 - 0x26D RC300 EMS+ holidaymodes 1 to 5
// special case R3000 only date in 0x269
// special case R3000 only date in 0x269, CR50 only 0x043F
void Thermostat::process_RC300Holiday(std::shared_ptr<const Telegram> telegram) {
if (telegram->offset || telegram->message_length < 6) {
return;
@@ -1454,6 +1523,7 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hc->designtemp, 17); // is * 1
has_update(telegram, hc->maxflowtemp, 15); // is * 1
}
setValueEnum(&hc->heatingtype, hc->control == 1 ? FL_(enum_heatingtype2) : FL_(enum_heatingtype));
}
// type 0x3F (HC1), 0x49 (HC2), 0x53 (HC3), 0x5D (HC4) - timer setting
@@ -1592,7 +1662,7 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
// render date to DD.MM.YYYY HH:MM and publish
char newdatetime[sizeof(dateTime_)];
strftime(newdatetime, sizeof(dateTime_), "%d.%m.%G %H:%M", tm_);
strftime(newdatetime, sizeof(dateTime_), "%d.%m.%Y %H:%M", tm_);
has_update(dateTime_, newdatetime, sizeof(dateTime_));
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
@@ -1838,7 +1908,9 @@ bool Thermostat::set_minexttemp(const char * value, const int8_t id) {
if ((model() == EMSdevice::EMS_DEVICE_FLAG_RC20_N) || (model() == EMSdevice::EMS_DEVICE_FLAG_RC25)) {
write_command(0xAD, 14, mt, 0xAD);
} else if (isRC300() || (model() == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
write_command(0x241, 10, mt, 0x241);
} else if (isRC300()) {
write_command(0x240, 10, mt, 0x240);
} else {
write_command(EMS_TYPE_IBASettings, 5, mt, EMS_TYPE_IBASettings);
@@ -1879,7 +1951,9 @@ bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
write_command(EMS_TYPE_RC30Settings, 1, t, EMS_TYPE_RC30Settings);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100H) {
write_command(0x273, 0, t, 0x273);
} else if (isRC300() || model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
write_command(0x241, 7, t, 0x241);
} else if (isRC300()) {
write_command(0x240, 7, t, 0x240);
} else {
write_command(EMS_TYPE_IBASettings, 2, t, EMS_TYPE_IBASettings);
@@ -1912,6 +1986,8 @@ bool Thermostat::set_brightness(const char * value, const int8_t id) {
return true;
}
// thermostat/hc<x>/remotetemp
// value of -1 to turn off
bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
float f;
if (!Helpers::value2temperature(value, f)) {
@@ -1987,7 +2063,9 @@ bool Thermostat::set_building(const char * value, const int8_t id) {
return false;
}
if (isRC300() || (model() == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
write_command(0x241, 9, bd + 1, 0x241);
} else if (isRC300()) {
write_command(0x240, 9, bd + 1, 0x240);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC30) {
write_command(EMS_TYPE_RC30Settings, 4, bd, EMS_TYPE_RC30Settings);
@@ -2017,16 +2095,29 @@ bool Thermostat::set_heatingpid(const char * value, const int8_t id) {
// 0xA5 and 0x0240- Set the damping settings
bool Thermostat::set_damping(const char * value, const int8_t id) {
bool dmp;
if (isRC300()) {
if (Helpers::value2bool(value, dmp)) {
if (Helpers::value2bool(value, dmp)) {
if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
write_command(0x241, 8, dmp ? 0xFF : 0, 0x241);
} else if (isRC300()) {
write_command(0x240, 8, dmp ? 0xFF : 0, 0x240);
return true;
}
} else {
if (Helpers::value2bool(value, dmp)) {
} else {
write_command(EMS_TYPE_IBASettings, 21, dmp ? 0xFF : 0, EMS_TYPE_IBASettings);
return true;
}
return true;
}
return false;
}
// 0x0241- Set solar
bool Thermostat::set_solar(const char * value, const int8_t id) {
bool b;
if (Helpers::value2bool(value, b)) {
if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
write_command(0x241, 13, b ? 0xFF : 0, 0x241);
} else {
write_command(0x240, 13, b ? 0xFF : 0, 0x240);
}
return true;
}
return false;
}
@@ -2058,11 +2149,15 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
}
uint8_t ctrl;
// Junkers
// 1-FB10, 2-FB100
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS && !has_flags(EMSdevice::EMS_DEVICE_FLAG_JUNKERS_OLD)) {
if (Helpers::value2enum(value, ctrl, FL_(enum_j_control))) {
write_command(set_typeids[hc->hc()], 1, ctrl);
return true;
}
// BC400
// 1-RC100, 2-RC100H, 3-RC200
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400) {
if (Helpers::value2enum(value, ctrl, FL_(enum_control2))) {
write_command(hpmode_typeids[hc->hc()], 3, ctrl);
@@ -2081,6 +2176,8 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
}
return true;
}
// RC100 or RC300
// 1-RC200, 2-RC100, 3-RC100H
} else if (isRC300() || model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
if (Helpers::value2enum(value, ctrl, FL_(enum_control1))) {
write_command(hpmode_typeids[hc->hc()], 3, ctrl);
@@ -2150,13 +2247,19 @@ bool Thermostat::set_wwmode(const char * value, const int8_t id) {
}
const uint8_t modes[] = {0, 2, 4};
write_command(0x02F5 + dhw->offset(), 2, modes[set], 0x02F5 + dhw->offset());
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
if (!Helpers::value2enum(value, set, FL_(enum_wwMode2))) { // off, on, auto
return false;
}
const uint8_t modes[] = {0, 2, 3};
write_command(0x02F5 + dhw->offset(), 2, modes[set], 0x02F5 + dhw->offset());
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_R3000) { // Rego3000 - https://github.com/emsesp/EMS-ESP32/issues/1692
if (!Helpers::value2enum(value, set, FL_(enum_wwMode5))) {
return false;
}
const uint8_t modes[] = {1, 2, 5};
write_command(0x02F5 + dhw->offset(), 2, modes[set], 0x02F5 + dhw->offset());
} else if ((model() == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model() == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC300) {
if (!Helpers::value2enum(value, set, FL_(enum_wwMode))) {
return false;
}
@@ -2616,7 +2719,7 @@ bool Thermostat::set_RC30Vacation(const char * value, const int8_t id) {
}
// set R3000 holiday as string dd.mm.yyyy-dd.mm.yyyy
// RC30 and rego 3000
// RC30 and rego 3000 and CR50
bool Thermostat::set_R3000Holiday(const char * value, const int8_t id) {
if (strlen(value) != 21) {
return false;
@@ -2633,7 +2736,11 @@ bool Thermostat::set_R3000Holiday(const char * value, const int8_t id) {
if (!data[2] || data[2] > 31 || !data[1] || data[1] > 12 || !data[5] || data[5] > 31 || !data[4] || data[4] > 12) {
return false;
}
write_command(0x269, 0, data, 6, 0x269);
if (model() == EMSdevice::EMS_DEVICE_FLAG_R3000) {
write_command(0x269, 0, data, 6, 0x269);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
write_command(0x43F, 0, data, 6, 0x43F);
}
return true;
}
@@ -2681,6 +2788,41 @@ bool Thermostat::set_party(const char * value, const int8_t id) {
return true;
}
bool Thermostat::set_absent(const char * value, const int8_t id) {
bool b;
if (Helpers::value2bool(value, b)) {
write_command(0x16E, 0, b ? 0xFF : 0, 0x16E);
return true;
}
return false;
}
bool Thermostat::set_redthreshold(const char * value, const int8_t id) {
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
float t;
if (Helpers::value2temperature(value, t)) {
write_command(set_typeids[hc->hc()], 20, (int8_t)(t * 2), set_typeids[hc->hc()]);
return true;
}
return false;
}
bool Thermostat::set_solarinfl(const char * value, const int8_t id) {
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
float t;
if (Helpers::value2temperature(value, t)) {
write_command(summer_typeids[hc->hc()], 3, (int8_t)t, summer_typeids[hc->hc()]);
return true;
}
return false;
}
// set date&time as string dd.mm.yyyy-hh:mm:ss-dw-dst or "NTP" for setting to internet-time
// dw - day of week (0..6), dst- summertime (0/1)
// id is ignored
@@ -3208,14 +3350,18 @@ bool Thermostat::set_heatingtype(const char * value, const int8_t id) {
}
uint8_t set;
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS && Helpers::value2enum(value, set, FL_(enum_heatingtype1))) {
write_command(set_typeids[hc->hc()], 0, set, set_typeids[hc->hc()]);
return true;
}
if (Helpers::value2enum(value, set, FL_(enum_heatingtype))) {
if ((model() == EMSdevice::EMS_DEVICE_FLAG_RC20_N) || (model() == EMSdevice::EMS_DEVICE_FLAG_RC25)) {
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
if (Helpers::value2enum(value, set, FL_(enum_heatingtype1))) {
write_command(set_typeids[hc->hc()], 0, set, set_typeids[hc->hc()]);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC35 || model() == EMSdevice::EMS_DEVICE_FLAG_RC30_N) {
return true;
}
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC35 || model() == EMSdevice::EMS_DEVICE_FLAG_RC30_N) {
if (Helpers::value2enum(value, set, hc->control == 1 ? FL_(enum_heatingtype2) : FL_(enum_heatingtype))) {
write_command(set_typeids[hc->hc()], 0, set, set_typeids[hc->hc()]);
return true;
}
} else if (Helpers::value2enum(value, set, FL_(enum_heatingtype))) {
if ((model() == EMSdevice::EMS_DEVICE_FLAG_RC20_N) || (model() == EMSdevice::EMS_DEVICE_FLAG_RC25)) {
write_command(set_typeids[hc->hc()], 0, set, set_typeids[hc->hc()]);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC30) {
write_command(curve_typeids[hc->hc()], 0, set, curve_typeids[hc->hc()]);
@@ -3645,6 +3791,9 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
break;
}
} else if (model == EMSdevice::EMS_DEVICE_FLAG_CR11) {
offset = 10; // just seltemp write to manualtemp
} else if (isRC300() || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
validate_typeid = set_typeids[hc->hc()];
switch (mode) {
@@ -3678,7 +3827,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
factor = 1; // to write 0xFF
}
break;
case HeatingCircuit::Mode::REMOTESELTEMP:
case HeatingCircuit::Mode::COOLTEMP:
offset = 0x11;
break;
case HeatingCircuit::Mode::COMFORT:
@@ -4080,7 +4229,8 @@ void Thermostat::register_device_values() {
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_minexttemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ibaDamping_, DeviceValueType::BOOL, FL_(damping), DeviceValueUOM::NONE, MAKE_CF_CB(set_damping));
if (model() == EMSdevice::EMS_DEVICE_FLAG_R3000) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hasSolar_, DeviceValueType::BOOL, FL_(hasSolar), DeviceValueUOM::NONE, MAKE_CF_CB(set_solar));
if (model() == EMSdevice::EMS_DEVICE_FLAG_R3000 || model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&vacation[0],
DeviceValueType::STRING,
@@ -4152,6 +4302,7 @@ void Thermostat::register_device_values() {
DeviceValueTAG::TAG_DEVICE_DATA, &pvRaiseHeat_, DeviceValueType::INT8, FL_(pvRaiseHeat), DeviceValueUOM::K, MAKE_CF_CB(set_pvRaiseHeat), 0, 5);
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &pvLowerCool_, DeviceValueType::INT8, FL_(pvLowerCool), DeviceValueUOM::K, MAKE_CF_CB(set_pvLowerCool), -5, 0);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &absent_, DeviceValueType::BOOL, FL_(absent), DeviceValueUOM::NONE, MAKE_CF_CB(set_absent));
break;
case EMSdevice::EMS_DEVICE_FLAG_RC10:
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
@@ -4402,6 +4553,8 @@ void Thermostat::register_device_values() {
// Easy TC100 have no date/time, see issue #100, not sure about CT200, so leave it.
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
break;
case EMSdevice::EMS_DEVICE_FLAG_CR11:
break;
case EMSdevice::EMS_DEVICE_FLAG_CRF:
default:
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
@@ -4514,6 +4667,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
MAKE_CF_CB(set_summermode));
register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->hpoperatingstate, DeviceValueType::ENUM, FL_(enum_operatingstate), FL_(hpoperatingstate), DeviceValueUOM::NONE);
register_device_value(tag, &hc->vacationmode, DeviceValueType::BOOL, FL_(vacationmode), DeviceValueUOM::NONE);
if (model == EMSdevice::EMS_DEVICE_FLAG_RC100 || model == EMSdevice::EMS_DEVICE_FLAG_CR120) {
register_device_value(
tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode));
@@ -4531,15 +4685,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
MAKE_CF_CB(set_tempautotemp),
-1,
30);
register_device_value(tag,
&hc->remoteseltemp,
DeviceValueType::INT8,
DeviceValueNumOp::DV_NUMOP_DIV2,
FL_(remoteseltemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_remoteseltemp),
-1,
30);
register_device_value(
tag, &hc->cooltemp, DeviceValueType::INT8, DeviceValueNumOp::DV_NUMOP_DIV2, FL_(cooltemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_cooltemp), -1, 30);
register_device_value(tag, &hc->fastHeatup, DeviceValueType::UINT8, FL_(fastheatup), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_fastheatup));
register_device_value(tag,
&hc->switchonoptimization,
@@ -4563,16 +4710,18 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
} else {
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control1), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
}
register_device_value(tag,
&hc->remotetemp,
DeviceValueType::CMD,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(remotetemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_remotetemp),
-1,
101);
register_device_value(tag, &hc->remotehum, DeviceValueType::CMD, FL_(remotehum), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_remotehum), -1, 101);
if (model != EMSdevice::EMS_DEVICE_FLAG_RC100 && model != EMSdevice::EMS_DEVICE_FLAG_CR120) {
register_device_value(tag,
&hc->remotetemp,
DeviceValueType::CMD,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(remotetemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_remotetemp),
-1,
101);
register_device_value(tag, &hc->remotehum, DeviceValueType::CMD, FL_(remotehum), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_remotehum), -1, 101);
}
register_device_value(tag, &hc->heatondelay, DeviceValueType::UINT8, FL_(heatondelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_heatondelay), 1, 48);
register_device_value(tag, &hc->heatoffdelay, DeviceValueType::UINT8, FL_(heatoffdelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_heatoffdelay), 1, 48);
register_device_value(tag, &hc->instantstart, DeviceValueType::UINT8, FL_(instantstart), DeviceValueUOM::K, MAKE_CF_CB(set_instantstart), 1, 10);
@@ -4589,12 +4738,29 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
DeviceValueUOM::NONE,
MAKE_CF_CB(set_switchProgMode));
register_device_value(tag,
&hc->redThreshold,
DeviceValueType::INT8,
DeviceValueNumOp::DV_NUMOP_DIV2,
FL_(redthreshold),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_redthreshold),
12,
22);
register_device_value(tag, &hc->solarInfl, DeviceValueType::UINT8, FL_(solarinfl), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_solarinfl), -5, -1);
register_device_value(tag, &hc->currSolarInfl, DeviceValueType::UINT8, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(currsolarinfl), DeviceValueUOM::DEGREES);
break;
case EMSdevice::EMS_DEVICE_FLAG_CRF:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype5), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT8, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
break;
case EMSdevice::EMS_DEVICE_FLAG_CR11:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode), FL_(mode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT8, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
register_device_value(
tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingtype));
break;
case EMSdevice::EMS_DEVICE_FLAG_RC20:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag,
@@ -4898,6 +5064,8 @@ void Thermostat::register_device_values_dhw(std::shared_ptr<Thermostat::DhwCircu
register_device_value(tag, &dhw->wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode5), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_CR120) {
register_device_value(tag, &dhw->wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode6), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
register_device_value(tag, &dhw->wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
} else {
register_device_value(tag, &dhw->wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
}

View File

@@ -39,7 +39,7 @@ class Thermostat : public EMSdevice {
int16_t remotetemp; // for readback
uint8_t remotehum; // for readback
uint8_t tempautotemp;
int8_t remoteseltemp;
int8_t cooltemp;
uint8_t mode;
uint8_t mode_new;
uint8_t modetype;
@@ -77,6 +77,7 @@ class Thermostat : public EMSdevice {
int8_t reducetemp;
int8_t vacreducetemp;
uint8_t vacreducemode;
uint8_t vacationmode;
uint8_t wwprio;
uint8_t fastHeatup;
char holiday[22];
@@ -87,6 +88,7 @@ class Thermostat : public EMSdevice {
uint8_t switchonoptimization;
uint8_t statusbyte; // from RC300monitor
uint8_t switchProgMode;
int8_t redThreshold;
// RC 10
uint8_t reducehours; // night reduce duration
uint16_t reduceminutes; // remaining minutes to night->day
@@ -108,6 +110,8 @@ class Thermostat : public EMSdevice {
uint8_t instantstart; // 1-10K
uint8_t boost;
uint8_t boosttime; // hours
int8_t currSolarInfl;
int8_t solarInfl;
uint8_t hc_num() const {
return hc_num_;
@@ -155,7 +159,7 @@ class Thermostat : public EMSdevice {
ON,
DAYLOW,
DAYMID,
REMOTESELTEMP,
COOLTEMP,
COOLSTART,
UNKNOWN
@@ -277,7 +281,9 @@ class Thermostat : public EMSdevice {
uint8_t humidity_;
uint8_t battery_;
char vacation[8][22]; // RC30, R3000 only, only one hc
char vacation[8][22]; // RC30, R3000 only, only one hc
uint8_t absent_;
uint8_t hasSolar_;
// HybridHP
uint8_t hybridStrategy_; // co2 = 1, cost = 2, temperature = 3, mix = 4
@@ -426,6 +432,7 @@ class Thermostat : public EMSdevice {
void process_RC10Set(std::shared_ptr<const Telegram> telegram);
void process_RC10Set_2(std::shared_ptr<const Telegram> telegram);
void process_CRFMonitor(std::shared_ptr<const Telegram> telegram);
void process_CR11Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC300Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC300Set(std::shared_ptr<const Telegram> telegram);
void process_RC300Set2(std::shared_ptr<const Telegram> telegram);
@@ -446,6 +453,7 @@ class Thermostat : public EMSdevice {
void process_JunkersRemoteMonitor(std::shared_ptr<const Telegram> telegram);
void process_HybridSettings(std::shared_ptr<const Telegram> telegram);
void process_PVSettings(std::shared_ptr<const Telegram> telegram);
void process_Absent(std::shared_ptr<const Telegram> telegram);
void process_JunkersSetMixer(std::shared_ptr<const Telegram> telegram);
void process_JunkersWW(std::shared_ptr<const Telegram> telegram);
void process_RemoteTemp(std::shared_ptr<const Telegram> telegram);
@@ -563,8 +571,8 @@ class Thermostat : public EMSdevice {
inline bool set_roominfluence(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::ROOMINFLUENCE, true);
}
inline bool set_remoteseltemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::REMOTESELTEMP);
inline bool set_cooltemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::COOLTEMP);
}
inline bool set_coolstart(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::COOLSTART);
@@ -625,6 +633,7 @@ class Thermostat : public EMSdevice {
bool set_display(const char * value, const int8_t id);
bool set_building(const char * value, const int8_t id);
bool set_damping(const char * value, const int8_t id);
bool set_solar(const char * value, const int8_t id);
bool set_language(const char * value, const int8_t id);
bool set_heatingtype(const char * value, const int8_t id);
bool set_reducehours(const char * value, const int8_t id);
@@ -656,6 +665,9 @@ class Thermostat : public EMSdevice {
bool set_coolondelay(const char * value, const int8_t id);
bool set_cooloffdelay(const char * value, const int8_t id);
bool set_switchProgMode(const char * value, const int8_t id);
bool set_absent(const char * value, const int8_t id);
bool set_redthreshold(const char * value, const int8_t id);
bool set_solarinfl(const char * value, const int8_t id);
};
} // namespace emsesp

View File

@@ -47,7 +47,7 @@ Ventilation::Ventilation(uint8_t device_type, uint8_t device_id, uint8_t product
DeviceValueTAG::TAG_DEVICE_DATA, &mode_, DeviceValueType::ENUM, FL_(enum_ventMode), FL_(ventMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_ventMode));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &voc_, DeviceValueType::UINT16, FL_(airquality), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &humidity_, DeviceValueType::UINT8, FL_(airHumidity), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &bypass_, DeviceValueType::BOOL, FL_(bypass), DeviceValueUOM::NONE, MAKE_CF_CB(set_bypass));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &bypass_, DeviceValueType::BOOL, FL_(airbypass), DeviceValueUOM::NONE, MAKE_CF_CB(set_bypass));
}
// message

View File

@@ -1,46 +0,0 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2024 emsesp.org - proddy, MichaelDvP
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EMSESP_EMSESP_STUB_H
#define EMSESP_EMSESP_STUB_H
#include "system.h"
#include "mqtt.h"
#include "temperaturesensor.h"
#include "version.h"
#include "default_settings.h"
#include "helpers.h"
#include "ESP8266React.h"
#include <uuid/log.h>
// forward declarator
// used to bind EMS-ESP functions to external frameworks
namespace emsesp {
class EMSESP {
public:
static Mqtt mqtt_;
static System system_;
static TemperatureSensor temperaturesensor_;
static uuid::log::Logger logger();
static ESP8266React esp8266React;
};
} // namespace emsesp
#endif

View File

@@ -99,9 +99,10 @@ bool Test::test(const std::string & cmd, int8_t id1, int8_t id2) {
if (cmd == "2thermostats") {
EMSESP::logger().notice("Testing with multiple thermostats...");
add_device(0x08, 123); // GB072
add_device(0x08, 123); // GB072 boiler
add_device(0x10, 158); // RC310
add_device(0x18, 157); // Bosch CR100
add_device(0x19, 157); // RC200
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
@@ -122,7 +123,7 @@ bool Test::test(const std::string & cmd, int8_t id1, int8_t id2) {
// 2nd thermostat on HC2
// Thermostat RC300Monitor(0x02A6)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA6, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
uart_telegram({0x99, 0x00, 0xFF, 0x00, 0x01, 0xA6, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
return true;
@@ -287,8 +288,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
}
// extract params
int8_t id1 = -1;
int8_t id2 = -1;
int16_t id1 = -1;
int16_t id2 = -1;
if (!id1_s.empty()) {
if (id1_s[0] == '0' && id1_s[1] == 'x') {
id1 = Helpers::hextoint(id1_s.c_str());
@@ -306,7 +307,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.printfln("Usage: test add <device_id> <product_id>");
return;
}
shell.printfln("Testing Adding a device (product_id %d), with all values...", id2);
shell.printfln("Testing adding a device (deviceID 0x%02X, product_id %d), with all values...", id1, id2);
test("add", id1, id2);
shell.invoke_command("show devices");
ok = true;
@@ -323,20 +324,15 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
if (command == "general") {
shell.printfln("Testing adding a boiler, thermostat, all sensors, scheduler and custom entities...");
// setup fake data
// EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
// add devices
test("general");
// EMSESP::webCustomEntityService.test(); // add custom entities
// EMSESP::temperaturesensor_.test(); // add temperature sensors
// EMSESP::webSchedulerService.test(); // add scheduler items
EMSESP::webCustomEntityService.test(); // custom entities
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
EMSESP::temperaturesensor_.test(); // add temperature sensors
EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions
// shell.invoke_command("show devices");
// shell.invoke_command("show values");
// shell.invoke_command("call system publish");
// shell.invoke_command("show mqtt");
shell.invoke_command("show values");
ok = true;
}
@@ -424,10 +420,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
if (command == "upload") {
// S3 has 16MB flash
// EMSESP::system_.uploadFirmwareURL("https://github.com/emsesp/EMS-ESP32/releases/download/latest/EMS-ESP-3_7_0-dev_32-ESP32S3-16MB+.bin");
// EMSESP::system_.uploadFirmwareURL("https://github.com/emsesp/EMS-ESP32/releases/download/latest/EMS-ESP-3_7_0-dev_32-ESP32S3-16MB+.bin"); // S3
// Test for 4MB Tasmota builds
EMSESP::system_.uploadFirmwareURL("https://github.com/emsesp/EMS-ESP32/releases/download/latest/EMS-ESP-3_7_0-dev_32-ESP32-16MB.bin");
// EMSESP::system_.uploadFirmwareURL("https://github.com/emsesp/EMS-ESP32/releases/download/latest/EMS-ESP-3_7_2-dev_8-ESP32-4MB.bin"); // S32
EMSESP::system_.uploadFirmwareURL("https://github.com/emsesp/EMS-ESP32/releases/download/latest/EMS-ESP-3_7_2-dev_8-ESP32-16MB.bin"); // E32
ok = true;
}
#endif
@@ -614,9 +612,30 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
if (command == "2thermostats") {
shell.printfln("Testing multiple thermostats...");
// adds hc1=(0x10, 158) RC310 and hc2=(0x19, 157) RC200
test("2thermostats");
// shell.invoke_command("show values");
shell.invoke_command("show values");
// shell.invoke_command("show devices");
AsyncWebServerRequest request;
request.method(HTTP_GET);
request.url("/api/thermostat");
EMSESP::webAPIService.webAPIService(&request);
Serial.println();
request.url("/api/thermostat/hc1/entities");
EMSESP::webAPIService.webAPIService(&request);
Serial.println();
request.url("/api/thermostat/hc2/entities");
EMSESP::webAPIService.webAPIService(&request);
Serial.println();
request.url("/api/thermostat/entities");
EMSESP::webAPIService.webAPIService(&request);
ok = true;
}
@@ -835,10 +854,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.printfln("Testing adding Analog sensor");
Mqtt::ha_enabled(true);
// Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
// Mqtt::nested_format(0);
// Mqtt::send_response(false);
// load some EMS data
@@ -861,6 +878,23 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
request.url("/api/analogsensor/36");
EMSESP::webAPIService.webAPIService(&request);
// test setting a value
request.method(HTTP_POST);
JsonDocument doc;
char data[] = "{\"value\":10,\"id\":33}";
deserializeJson(doc, data);
request.url("/api/analogsensor/setvalue");
EMSESP::webAPIService.webAPIService(&request, doc.as<JsonVariant>());
shell.invoke_command("call analogsensor test_analogsensor4");
char data2[] = "{\"value\":11}";
deserializeJson(doc, data2);
request.url("/api/analogsensor/test_analogsensor4");
EMSESP::webAPIService.webAPIService(&request, doc.as<JsonVariant>());
shell.invoke_command("call analogsensor test_analogsensor4");
// test renaming it
// bool update(uint8_t id, const std::string & name, int16_t offset, float factor, uint8_t uom, uint8_t type);
// EMSESP::analogsensor_.update(36, "test_analog1_new", 2, 0.7, 17, 1);
@@ -958,6 +992,48 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
ok = true;
}
if (command == "api4") {
shell.printfln("Testing API writing values...");
EMSESP::system_.bool_format(BOOL_FORMAT_ONOFF_STR);
// EMSESP::system_.bool_format(BOOL_FORMAT_ONOFF_STR_CAP);
// load devices
test("boiler");
test("thermostat");
ok = true;
AsyncWebServerRequest request;
JsonDocument doc;
JsonVariant json;
request.method(HTTP_POST);
shell.invoke_command("call boiler circpump/value"); // initial state is off
// call boiler circpump on
char data1[] = "{\"device\":\"boiler\", \"cmd\":\"circpump\",\"value\":\"on\"}";
deserializeJson(doc, data1);
request.url("/api");
EMSESP::webAPIService.webAPIService(&request, doc.as<JsonVariant>());
shell.invoke_command("call boiler circpump/value");
// switch to german
EMSESP::system_.locale("de");
// call boiler circpump off, but using value in DE
char data2[] = "{\"device\":\"boiler\", \"cmd\":\"circpump\",\"value\":\"aus\"}";
deserializeJson(doc, data2);
request.url("/api");
EMSESP::webAPIService.webAPIService(&request, doc.as<JsonVariant>());
shell.invoke_command("call boiler circpump/value");
// call boiler circpump on, but using value in DE
char data3[] = "{\"device\":\"boiler\", \"cmd\":\"circpump\",\"value\":\"an\"}";
deserializeJson(doc, data3);
request.url("/api");
EMSESP::webAPIService.webAPIService(&request, doc.as<JsonVariant>());
shell.invoke_command("call boiler circpump/value");
}
if (command == "api3") {
shell.printfln("Testing API getting values from system");
EMSESP::system_.bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR
@@ -1005,6 +1081,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// EMSESP::webAPIService.webAPIService(&request);
// request.url("/api/thermostat/hc1");
// EMSESP::webAPIService.webAPIService(&request);
// request.url("/api/boiler/comfort/value");
// EMSESP::webAPIService.webAPIService(&request);
// this should fail but it doesn't
// request.url("/api/boiler/bad/value");
// EMSESP::webAPIService.webAPIService(&request);
// POST COMMANDS
request.method(HTTP_POST);
@@ -1014,10 +1096,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// request.url("/api");
// EMSESP::webAPIService.webAPIService(&request, doc.as<JsonVariant>());
char data2[] = "{\"action\":\"getCustomSupport\", \"param\":\"hello\"}";
deserializeJson(doc, data2);
request.url("/rest/action");
EMSESP::webStatusService.action(&request, doc.as<JsonVariant>());
// char data2[] = "{\"action\":\"getCustomSupport\", \"param\":\"hello\"}";
// deserializeJson(doc, data2);
// request.url("/rest/action");
// EMSESP::webStatusService.action(&request, doc.as<JsonVariant>());
// char data3[] = "{\"action\":\"export\", \"param\":\"schedule\"}";
// deserializeJson(doc, data3);
@@ -2369,7 +2451,7 @@ void Test::add_device(uint8_t device_id, uint8_t product_id) {
#ifndef EMSESP_STANDALONE
void Test::listDir(fs::FS & fs, const char * dirname, uint8_t levels) {
Serial.println();
Serial.printf("Listing directory: %s\r\n", dirname);
Serial.printf("%s\r\n", dirname);
File root = fs.open(dirname);
if (!root) {
@@ -2384,12 +2466,11 @@ void Test::listDir(fs::FS & fs, const char * dirname, uint8_t levels) {
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR: ");
Serial.println(file.name());
Serial.print(file.name());
Serial.println("/");
if (levels) {
// prefix a / to the name to make it a full path
listDir(fs, ("/" + String(file.name())).c_str(), levels - 1);
// listDir(fs, file.name(), levels - 1);
}
Serial.println();
} else {
@@ -2401,7 +2482,6 @@ void Test::listDir(fs::FS & fs, const char * dirname, uint8_t levels) {
}
file = root.openNextFile();
}
Serial.println();
}
#endif
#endif

View File

@@ -42,6 +42,7 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "render"
// #define EMSESP_DEBUG_DEFAULT "api"
// #define EMSESP_DEBUG_DEFAULT "api3"
// #define EMSESP_DEBUG_DEFAULT "api4"
// #define EMSESP_DEBUG_DEFAULT "crash"
// #define EMSESP_DEBUG_DEFAULT "dv"
// #define EMSESP_DEBUG_DEFAULT "lastcode"

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.1"
#define EMSESP_APP_VERSION "3.7.2"

View File

@@ -25,7 +25,10 @@ uint16_t WebAPIService::api_fails_ = 0;
WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) {
server->on(EMSESP_API_SERVICE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { webAPIService(request, json); });
AsyncCallbackJsonWebHandler * jsonHandler = new AsyncCallbackJsonWebHandler(EMSESP_API_SERVICE_PATH);
jsonHandler->setMethod(HTTP_POST | HTTP_GET);
jsonHandler->onRequest([this](AsyncWebServerRequest * request, JsonVariant json) { webAPIService(request, json); });
server->addHandler(jsonHandler);
}
// POST|GET api/
@@ -131,12 +134,13 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
}
// send the json that came back from the command call
// sequence is FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED, INVALID
// sequence matches CommandRet in command.h (FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED, INVALID, NO_VALUE)
// 400 (bad request)
// 200 (OK)
// 404 (not found)
// 401 (unauthorized)
int ret_codes[6] = {400, 200, 404, 400, 401, 400};
// 400 (invalid)
int ret_codes[7] = {400, 200, 404, 400, 401, 400, 404};
response->setCode(ret_codes[return_code]);
response->setLength();
response->setContentType("application/json; charset=utf-8");

View File

@@ -27,7 +27,7 @@ class WebAPIService {
public:
WebAPIService(AsyncWebServer * server, SecurityManager * securityManager);
void webAPIService(AsyncWebServerRequest * request, JsonVariant json);
void webAPIService(AsyncWebServerRequest * request, JsonVariant input);
#if defined(EMSESP_TEST)
// for test.cpp and running unit tests

View File

@@ -21,9 +21,9 @@
namespace emsesp {
WebActivityService::WebActivityService(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(EMSESP_ACTIVITY_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { webActivityService(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->addEndpoint(server, EMSESP_ACTIVITY_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
webActivityService(request);
});
}
void WebActivityService::webActivityService(AsyncWebServerRequest * request) {

View File

@@ -56,7 +56,7 @@ void WebCustomEntity::read(WebCustomEntity & webEntity, JsonObject root) {
ei["offset"] = entityItem.offset;
ei["factor"] = entityItem.factor;
ei["name"] = entityItem.name;
ei["uom"] = entityItem.uom;
ei["uom"] = entityItem.value_type == DeviceValueType::BOOL ? 0 : entityItem.uom;
ei["value_type"] = entityItem.value_type;
ei["writeable"] = entityItem.writeable;
EMSESP::webCustomEntityService.render_value(ei, entityItem, true, true);
@@ -82,7 +82,8 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
// rebuild the list
if (root["entities"].is<JsonArray>()) {
for (const JsonObject ei : root["entities"].as<JsonArray>()) {
auto entities = root["entities"].as<JsonArray>();
for (const JsonObject ei : entities) {
auto entityItem = CustomEntityItem();
entityItem.ram = ei["ram"];
entityItem.device_id = ei["device_id"]; // send as numeric, will be converted to string in web
@@ -97,7 +98,6 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
if (entityItem.ram == 1) {
entityItem.device_id = 0;
entityItem.type_id = 0;
entityItem.uom = 0;
entityItem.value_type = DeviceValueType::STRING;
entityItem.writeable = true;
}
@@ -105,8 +105,10 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
if (entityItem.ram == 0 && entityItem.value_type == DeviceValueType::STRING) {
entityItem.raw = new uint8_t[(size_t)entityItem.factor + 1];
entityItem.data = "";
entityItem.uom = 0;
} else if (entityItem.value_type == DeviceValueType::BOOL) {
entityItem.value = EMS_VALUE_DEFAULT_BOOL;
entityItem.uom = 0;
} else if (entityItem.value_type == DeviceValueType::INT8) {
entityItem.value = EMS_VALUE_DEFAULT_INT8;
} else if (entityItem.value_type == DeviceValueType::UINT8) {
@@ -180,7 +182,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
if (!Helpers::value2bool(value, v)) {
return false;
}
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v ? 0xFF : 0, 0);
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v ? (uint8_t)entityItem.factor : 0, 0);
} else {
float f;
if (!Helpers::value2float(value, f)) {
@@ -214,7 +216,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
// output of a single value
// if add_uom is true it will add the UOM string to the value
void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem & entity, const bool useVal, const bool web, const bool add_uom) {
char payload[12];
char payload[20];
std::string name = useVal ? "value" : entity.name;
switch (entity.value_type) {
case DeviceValueType::BOOL:
@@ -266,7 +268,7 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem &
default:
// if no type treat it as a string
if (entity.data.length() > 0) {
output[name] = entity.data;
output[name] = add_uom ? entity.data + ' ' + EMSdevice::uom_to_string(entity.uom) : entity.data;
}
break;
}
@@ -324,9 +326,8 @@ void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem
output["fullname"] = entity.name;
output["storage"] = entity.ram ? "ram" : "ems";
output["type"] = entity.value_type == DeviceValueType::BOOL ? "boolean" : entity.value_type == DeviceValueType::STRING ? "string" : F_(number);
if (entity.uom > 0) {
output["uom"] = EMSdevice::uom_to_string(entity.uom);
}
// add uom state class and device class
Mqtt::add_ha_uom(output, entity.value_type, entity.uom, nullptr, false);
output["readable"] = true;
output["writeable"] = entity.writeable;
output["visible"] = true;
@@ -490,13 +491,15 @@ void WebCustomEntityService::generate_value_web(JsonObject output, const bool is
}
obj["id"] = "00" + entity.name;
obj["u"] = entity.uom;
if (entity.value_type != DeviceValueType::BOOL) {
obj["u"] = entity.uom;
}
if (entity.writeable) {
obj["c"] = entity.name;
include = true;
if (entity.value_type != DeviceValueType::BOOL && entity.value_type != DeviceValueType::STRING) {
char s[10];
char s[20];
obj["s"] = Helpers::render_value(s, entity.factor, 1);
}
}
@@ -514,25 +517,25 @@ void WebCustomEntityService::generate_value_web(JsonObject output, const bool is
}
case DeviceValueType::INT8:
if ((int8_t)entity.value != EMS_VALUE_INT8_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * (int8_t)entity.value, 0);
obj["v"] = Helpers::transformNumFloat(entity.factor * (int8_t)entity.value);
include = true;
}
break;
case DeviceValueType::UINT8:
if ((uint8_t)entity.value != EMS_VALUE_UINT8_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * (uint8_t)entity.value, 0);
obj["v"] = Helpers::transformNumFloat(entity.factor * (uint8_t)entity.value);
include = true;
}
break;
case DeviceValueType::INT16:
if ((int16_t)entity.value != EMS_VALUE_INT16_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * (int16_t)entity.value, 0);
obj["v"] = Helpers::transformNumFloat(entity.factor * (int16_t)entity.value);
include = true;
}
break;
case DeviceValueType::UINT16:
if ((uint16_t)entity.value != EMS_VALUE_UINT16_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * (uint16_t)entity.value, 0);
obj["v"] = Helpers::transformNumFloat(entity.factor * (uint16_t)entity.value);
include = true;
}
break;
@@ -540,7 +543,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output, const bool is
case DeviceValueType::TIME:
case DeviceValueType::UINT32:
if (entity.value != EMS_VALUE_UINT24_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * entity.value, 0);
obj["v"] = Helpers::transformNumFloat(entity.factor * entity.value);
include = true;
}
break;
@@ -630,6 +633,10 @@ bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram)
for (uint8_t i = 0; i < len[entity.value_type]; i++) {
value = (value << 8) + telegram->message_data[i + entity.offset - telegram->offset];
}
// mask bits for bool values
if (entity.value_type == DeviceValueType::BOOL && entity.factor > 0) {
value = (value & (uint8_t)entity.factor) ? 1 : 0;
}
if (value != entity.value) {
entity.value = value;
if (Mqtt::publish_single()) {

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../telegram.h"
#include "../core/telegram.h"
#ifndef WebCustomEntityService_h
#define WebCustomEntityService_h

View File

@@ -25,20 +25,27 @@ bool WebCustomization::_start = true;
WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE) {
// GET
server->on(EMSESP_DEVICE_ENTITIES_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { device_entities(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->addEndpoint(server, EMSESP_DEVICE_ENTITIES_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
device_entities(request);
});
// POST
server->on(EMSESP_RESET_CUSTOMIZATION_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { reset_customization(request); }, AuthenticationPredicates::IS_ADMIN));
server->on(EMSESP_WRITE_DEVICE_NAME_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { writeDeviceName(request, json); },
AuthenticationPredicates::IS_AUTHENTICATED));
server->on(EMSESP_CUSTOMIZATION_ENTITIES_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { customization_entities(request, json); },
AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->addEndpoint(
server,
EMSESP_RESET_CUSTOMIZATION_SERVICE_PATH,
AuthenticationPredicates::IS_ADMIN,
[this](AsyncWebServerRequest * request) { reset_customization(request); },
HTTP_POST); // force POST
securityManager->addEndpoint(server,
EMSESP_WRITE_DEVICE_NAME_PATH,
AuthenticationPredicates::IS_AUTHENTICATED,
[this](AsyncWebServerRequest * request, JsonVariant json) { writeDeviceName(request, json); });
securityManager->addEndpoint(server,
EMSESP_CUSTOMIZATION_ENTITIES_PATH,
AuthenticationPredicates::IS_AUTHENTICATED,
[this](AsyncWebServerRequest * request, JsonVariant json) { customization_entities(request, json); });
}
// this creates the customization file, saving it to the FS
@@ -87,7 +94,8 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
// Temperature Sensor customization
customizations.sensorCustomizations.clear();
if (root["ts"].is<JsonArray>()) {
for (const JsonObject sensorJson : root["ts"].as<JsonArray>()) {
auto sensorsJsons = root["ts"].as<JsonArray>();
for (const JsonObject sensorJson : sensorsJsons) {
// create each of the sensor, overwriting any previous settings
auto sensor = SensorCustomization();
sensor.id = sensorJson["id"].as<std::string>();
@@ -104,7 +112,8 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
// Analog Sensor customization
customizations.analogCustomizations.clear();
if (root["as"].is<JsonArray>()) {
for (const JsonObject analogJson : root["as"].as<JsonArray>()) {
auto analogJsons = root["as"].as<JsonArray>();
for (const JsonObject analogJson : analogJsons) {
// create each of the sensor, overwriting any previous settings
auto analog = AnalogCustomization();
analog.gpio = analogJson["gpio"];
@@ -124,13 +133,15 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
// load array of entities id's with masks, building up the object class
customizations.entityCustomizations.clear();
if (root["masked_entities"].is<JsonArray>()) {
for (const JsonObject masked_entities : root["masked_entities"].as<JsonArray>()) {
auto masked_entities = root["masked_entities"].as<JsonArray>();
for (const JsonObject masked_entity : masked_entities) {
auto emsEntity = EntityCustomization();
emsEntity.product_id = masked_entities["product_id"];
emsEntity.device_id = masked_entities["device_id"];
emsEntity.custom_name = masked_entities["custom_name"] | "";
emsEntity.product_id = masked_entity["product_id"];
emsEntity.device_id = masked_entity["device_id"];
emsEntity.custom_name = masked_entity["custom_name"] | "";
for (const JsonVariant masked_entity_id : masked_entities["entity_ids"].as<JsonArray>()) {
auto masked_entity_ids = masked_entity["entity_ids"].as<JsonArray>();
for (const JsonVariant masked_entity_id : masked_entity_ids) {
if (masked_entity_id.is<std::string>()) {
emsEntity.entity_ids.push_back(masked_entity_id.as<std::string>()); // add entity list
}
@@ -149,7 +160,8 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
if (LittleFS.remove(EMSESP_CUSTOMIZATION_FILE)) {
AsyncWebServerResponse * response = request->beginResponse(205); // restart needed
request->send(response);
EMSESP::system_.restart_pending(true);
emsesp::EMSESP::system_.systemStatus(
emsesp::SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor
return;
}
@@ -172,7 +184,7 @@ void WebCustomizationService::device_entities(AsyncWebServerRequest * request) {
id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url
#endif
auto * response = new AsyncJsonResponse(true, true); // array and msgpack
auto * response = new AsyncMessagePackResponse(true); // array and msgpack
// while (!response) {
// delete response;
@@ -379,7 +391,7 @@ void WebCustomizationService::test() {
analog.offset = 0;
analog.factor = 0.1;
analog.uom = 17;
analog.type = 3;
analog.type = 3; // ADC
webCustomization.analogCustomizations.push_back(analog);
analog = AnalogCustomization();
@@ -388,7 +400,7 @@ void WebCustomizationService::test() {
analog.offset = 0;
analog.factor = 1;
analog.uom = 0;
analog.type = 1;
analog.type = 1; // DIGITAL_IN
webCustomization.analogCustomizations.push_back(analog);
analog = AnalogCustomization();
@@ -400,6 +412,15 @@ void WebCustomizationService::test() {
analog.type = 0; // disabled, not-used
webCustomization.analogCustomizations.push_back(analog);
analog = AnalogCustomization();
analog.gpio = 33;
analog.name = "test_analogsensor4";
analog.offset = 0;
analog.factor = 1;
analog.uom = 0;
analog.type = 2; // COUNTER
webCustomization.analogCustomizations.push_back(analog);
// EMS entities, mark some as favorites
webCustomization.entityCustomizations.clear();
auto emsEntity = EntityCustomization();

View File

@@ -21,32 +21,38 @@
namespace emsesp {
WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securityManager) {
// write endpoints
server->on(EMSESP_WRITE_DEVICE_VALUE_SERVICE_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { write_device_value(request, json); },
AuthenticationPredicates::IS_ADMIN));
server->on(EMSESP_WRITE_TEMPERATURE_SENSOR_SERVICE_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { write_temperature_sensor(request, json); },
AuthenticationPredicates::IS_ADMIN));
server->on(EMSESP_WRITE_ANALOG_SENSOR_SERVICE_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { write_analog_sensor(request, json); },
AuthenticationPredicates::IS_ADMIN));
// write endpoints - POSTs
securityManager->addEndpoint(server,
EMSESP_WRITE_DEVICE_VALUE_SERVICE_PATH,
AuthenticationPredicates::IS_ADMIN,
[this](AsyncWebServerRequest * request, JsonVariant json) { write_device_value(request, json); });
securityManager->addEndpoint(server,
EMSESP_WRITE_TEMPERATURE_SENSOR_SERVICE_PATH,
AuthenticationPredicates::IS_ADMIN,
[this](AsyncWebServerRequest * request, JsonVariant json) { write_temperature_sensor(request, json); });
securityManager->addEndpoint(server,
EMSESP_WRITE_ANALOG_SENSOR_SERVICE_PATH,
AuthenticationPredicates::IS_ADMIN,
[this](AsyncWebServerRequest * request, JsonVariant json) { write_analog_sensor(request, json); });
// GET's
server->on(EMSESP_DEVICE_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { device_data(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->addEndpoint(server, EMSESP_DEVICE_DATA_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
device_data(request);
});
server->on(EMSESP_CORE_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { core_data(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->addEndpoint(server, EMSESP_CORE_DATA_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
core_data(request);
});
server->on(EMSESP_SENSOR_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { sensor_data(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->addEndpoint(server, EMSESP_SENSOR_DATA_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
sensor_data(request);
});
server->on(EMSESP_DASHBOARD_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { dashboard_data(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->addEndpoint(server, EMSESP_DASHBOARD_DATA_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
dashboard_data(request);
});
}
// this is used in the Devices page and contains all EMS device information
@@ -139,7 +145,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
obj["t"] = sensor.type();
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) {
obj["v"] = Helpers::transformNumFloat(sensor.value(), 0); // is optional and is a float
obj["v"] = Helpers::transformNumFloat(sensor.value()); // is optional and is a float
} else {
obj["v"] = 0; // must have a value for web sorting to work
}
@@ -161,7 +167,7 @@ void WebDataService::device_data(AsyncWebServerRequest * request) {
if (request->hasParam(F_(id))) {
id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url
auto * response = new AsyncJsonResponse(false, true); // use msgPack
auto * response = new AsyncMessagePackResponse();
// check size
// while (!response) {
@@ -267,10 +273,10 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
if (data.is<const char *>()) {
return_code = Command::call(device_type, cmd, data.as<const char *>(), true, id, output);
} else if (data.is<int>()) {
char s[10];
char s[20];
return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as<int32_t>(), 0), true, id, output);
} else if (data.is<float>()) {
char s[10];
char s[20];
return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as<float>(), 1), true, id, output);
} else if (data.is<bool>()) {
return_code = Command::call(device_type, cmd, data.as<bool>() ? "true" : "false", true, id, output);
@@ -346,19 +352,25 @@ void WebDataService::write_analog_sensor(AsyncWebServerRequest * request, JsonVa
// this is used in the dashboard and contains all ems device information
// /dashboardData endpoint
void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(true, true); // its an Array and also msgpack'd
auto * response = new AsyncMessagePackResponse();
#if defined(EMSESP_STANDALONE)
JsonDocument doc;
JsonArray root = doc.to<JsonArray>();
JsonObject root = doc.to<JsonObject>();
#else
JsonArray root = response->getRoot();
JsonObject root = response->getRoot();
#endif
// first do all the recognized devices
// add state of EMS bus
root["connected"] = EMSESP::bus_status() != 2;
// add all the data
JsonArray nodes = root["nodes"].to<JsonArray>();
// first fetch all the recognized devices
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->count_entities_fav()) {
JsonObject obj = root.add<JsonObject>();
JsonObject obj = nodes.add<JsonObject>();
obj["id"] = emsdevice->unique_id(); // it's unique id
obj["n"] = emsdevice->name(); // custom name
obj["t"] = emsdevice->device_type(); // device type number
@@ -368,15 +380,15 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
// add custom entities, if we have any
if (EMSESP::webCustomEntityService.count_entities()) {
JsonObject obj = root.add<JsonObject>();
JsonObject obj = nodes.add<JsonObject>();
obj["id"] = EMSdevice::DeviceTypeUniqueID::CUSTOM_UID; // it's unique id
obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number
EMSESP::webCustomEntityService.generate_value_web(obj, true);
}
// add temperature sensors
// add temperature sensors, if we have any
if (EMSESP::temperaturesensor_.have_sensors()) {
JsonObject obj = root.add<JsonObject>();
JsonObject obj = nodes.add<JsonObject>();
obj["id"] = EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID; // it's unique id
obj["t"] = EMSdevice::DeviceType::TEMPERATURESENSOR; // device type number
JsonArray nodes = obj["nodes"].to<JsonArray>();
@@ -403,7 +415,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
// add analog sensors, count excludes disabled entries
if (EMSESP::analog_enabled() && EMSESP::analogsensor_.count_entities(false)) {
JsonObject obj = root.add<JsonObject>();
JsonObject obj = nodes.add<JsonObject>();
obj["id"] = EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID; // it's unique id
obj["t"] = EMSdevice::DeviceType::ANALOGSENSOR; // device type number
JsonArray nodes = obj["nodes"].to<JsonArray>();
@@ -417,11 +429,11 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
dv["id"] = "00" + sensor.name();
#if CONFIG_IDF_TARGET_ESP32
if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT && (sensor.gpio() == 25 || sensor.gpio() == 26)) {
obj["v"] = Helpers::transformNumFloat(sensor.value(), 0);
obj["v"] = Helpers::transformNumFloat(sensor.value());
} else
#elif CONFIG_IDF_TARGET_ESP32S2
if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT && (sensor.gpio() == 17 || sensor.gpio() == 18)) {
obj["v"] = Helpers::transformNumFloat(sensor.value(), 0);
obj["v"] = Helpers::transformNumFloat(sensor.value());
} else
#endif
if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT || sensor.type() == AnalogSensor::AnalogType::DIGITAL_IN) {
@@ -431,7 +443,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
l.add(Helpers::render_boolean(s, false, true));
l.add(Helpers::render_boolean(s, true, true));
} else {
dv["v"] = Helpers::transformNumFloat(sensor.value(), 0);
dv["v"] = Helpers::transformNumFloat(sensor.value());
dv["u"] = sensor.uom();
}
if (sensor.type() == AnalogSensor::AnalogType::COUNTER || sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT) {
@@ -443,7 +455,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
// show scheduler, with name, on/off
if (EMSESP::webSchedulerService.count_entities(true)) {
JsonObject obj = root.add<JsonObject>();
JsonObject obj = nodes.add<JsonObject>();
obj["id"] = EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID; // it's unique id
obj["t"] = EMSdevice::DeviceType::SCHEDULER; // device type number
JsonArray nodes = obj["nodes"].to<JsonArray>();

View File

@@ -24,10 +24,14 @@ namespace emsesp {
WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager)
: events_(EMSESP_EVENT_SOURCE_LOG_PATH) {
// get & set settings
server->on(EMSESP_LOG_SETTINGS_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { getSetValues(request, json); });
// there is a GET and a POST for LogSettings endpoint, in one function
securityManager->addEndpoint(
server,
EMSESP_LOG_SETTINGS_PATH,
AuthenticationPredicates::IS_AUTHENTICATED,
[this](AsyncWebServerRequest * request, JsonVariant json) { getSetValues(request, json); },
HTTP_ANY);
// events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
server->addHandler(&events_);
}
@@ -121,7 +125,7 @@ void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
log_messages_.emplace_back(++log_message_id_, std::move(message));
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
EMSESP::esp32React.getNTPSettingsService()->read([&](NTPSettings & settings) {
if (!settings.enabled || (time(nullptr) < 1500000000L)) {
time_offset_ = 0;
} else {

View File

@@ -51,13 +51,14 @@ void WebModulesService::loop() {
// and also calls when the Modules web page is refreshed/loaded
void WebModules::read(WebModules & webModules, JsonObject root) {
JsonDocument doc_modules;
JsonObject root_modules = doc_modules.to<JsonObject>();
auto root_modules = doc_modules.to<JsonObject>();
moduleLibrary.list(root_modules); // get list the external library modules, put in a json object
JsonArray modules = root["modules"].to<JsonArray>();
uint8_t counter = 0;
for (const JsonObject module : root_modules["modules"].as<JsonArray>()) {
JsonObject mi = modules.add<JsonObject>();
auto modules_new = root["modules"].to<JsonArray>();
auto modules = root_modules["modules"].as<JsonArray>();
uint8_t counter = 0;
for (const JsonObject module : modules) {
JsonObject mi = modules_new.add<JsonObject>();
mi["id"] = counter++; // id is only used to render the table and must be unique
mi["key"] = module["key"].as<std::string>();
mi["name"] = module["name"].as<std::string>();
@@ -76,7 +77,8 @@ void WebModules::read(WebModules & webModules, JsonObject root) {
StateUpdateResult WebModules::update(JsonObject root, WebModules & webModules) {
bool err = false;
if (root["modules"].is<JsonArray>()) {
for (const JsonObject module : root["modules"].as<JsonArray>()) {
auto modules = root["modules"].as<JsonArray>();
for (const JsonObject module : modules) {
auto key = module["key"].as<const char *>();
auto license = module["license"].as<const char *>();
auto enable = module["enabled"].as<bool>();

View File

@@ -71,7 +71,8 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
EMSESP::webSchedulerService.ha_reset();
// build up the list of schedule items
for (const JsonObject schedule : root["schedule"].as<JsonArray>()) {
auto scheduleItems = root["schedule"].as<JsonArray>();
for (const JsonObject schedule : scheduleItems) {
// create each schedule item, overwriting any previous settings
// ignore the id (as this is only used in the web for table rendering)
auto si = ScheduleItem();
@@ -164,6 +165,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
}
return true;
}
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
if (Helpers::toLower(scheduleItem.name) == cmd) {
get_value_json(output, scheduleItem);
@@ -384,7 +386,6 @@ bool WebSchedulerService::command(const char * name, const std::string & command
snprintf(command_str, sizeof(command_str), "/api/%s", cmd.c_str());
uint8_t return_code = Command::process(command_str, true, input, output); // admin set
if (return_code == CommandRet::OK) {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Schedule command '%s' with data '%s' was successful", cmd.c_str(), data.c_str());
@@ -522,7 +523,7 @@ void WebSchedulerService::loop() {
void WebSchedulerService::scheduler_task(void * pvParameters) {
while (1) {
delay(10); // no need to hurry
if (!EMSESP::system_.upload_isrunning()) {
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) {
EMSESP::webSchedulerService.loop();
}
}
@@ -595,10 +596,6 @@ void WebSchedulerService::test() {
test_value = "(custom/seltemp)";
command("test5", test_cmd.c_str(), compute(test_value).c_str());
// note: this will fail unless test("boiler") is loaded before hand
test_value = "(boiler/outdoortemp)";
command("test6", test_cmd.c_str(), compute(test_value).c_str());
test_value = "boiler/flowtempoffset";
command("test7", test_cmd.c_str(), compute(test_value).c_str());
@@ -612,10 +609,35 @@ void WebSchedulerService::test() {
test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5";
command("test10", test_cmd.c_str(), compute(test_value).c_str());
test_cmd = "{\"method\":\"POST\",\"url\":\"http://192.168.1.42:8123/api/services/script/test_notify2\", \"header\":{\"authorization\":\"Bearer "
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhMmNlYWI5NDgzMmI0ODE2YWQ2NzU4MjkzZDE2YWMxZSIsImlhdCI6MTcyMTM5MTI0NCwiZXhwIjoyMDM2NzUxMjQ0fQ."
"S5sago1tEI6lNhrDCO0dM_WsVQHkD_laAjcks8tWAqo\"}}";
command("test11", test_cmd.c_str(), "");
// test case conversion
test_value = "(thermostat/hc1/modetype == \"comfort\")";
command("test11a", test_cmd.c_str(), compute(test_value).c_str()); // should be 1 true
test_value = "(thermostat/hc1/modetype == \"Comfort\")";
command("test11b", test_cmd.c_str(), compute(test_value).c_str()); // should be 1 true
test_value = "(thermostat/hc1/modetype == \"unknown\")";
command("test11c", test_cmd.c_str(), compute(test_value).c_str()); // should be 0 false
// can't find entity, should fail
test_value = "(boiler/storagetemp/value1)";
command("test12", test_cmd.c_str(), compute(test_value).c_str());
// can't find attribute, should fail
test_value = "(boiler/storagetemp1/value1)";
command("test13", test_cmd.c_str(), compute(test_value).c_str());
// check when entity has no value, should pass
test_value = "(boiler/storagetemp2/value)";
command("test14", test_cmd.c_str(), compute(test_value).c_str());
// should pass
test_value = "(boiler/storagetemp1/value)";
command("test15", test_cmd.c_str(), compute(test_value).c_str());
// test HTTP POST to call HA script
// test_cmd = "{\"method\":\"POST\",\"url\":\"http://192.168.1.42:8123/api/services/script/test_notify2\", \"header\":{\"authorization\":\"Bearer "
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhMmNlYWI5NDgzMmI0ODE2YWQ2NzU4MjkzZDE2YWMxZSIsImlhdCI6MTcyMTM5MTI0NCwiZXhwIjoyMDM2NzUxMjQ0fQ."
// "S5sago1tEI6lNhrDCO0dM_WsVQHkD_laAjcks8tWAqo\"}}";
// command("test99", test_cmd.c_str(), "");
}
#endif

View File

@@ -25,9 +25,10 @@ uint8_t WebSettings::flags_ = 0;
WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) {
server->on(EMSESP_BOARD_PROFILE_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { board_profile(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->addEndpoint(server, EMSESP_BOARD_PROFILE_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
board_profile(request);
});
addUpdateHandler([this] { onUpdate(); }, false);
}
@@ -58,6 +59,7 @@ void WebSettings::read(WebSettings & settings, JsonObject root) {
root["dallas_parasite"] = settings.dallas_parasite;
root["led_gpio"] = settings.led_gpio;
root["hide_led"] = settings.hide_led;
root["led_type"] = settings.led_type;
root["low_clock"] = settings.low_clock;
root["telnet_enabled"] = settings.telnet_enabled;
root["notoken_api"] = settings.notoken_api;
@@ -102,6 +104,7 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
#endif
// get the current board profile saved in the settings file
String org_board_profile = settings.board_profile;
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE; // this is set at compile time in platformio.ini, other it's "default"
String old_board_profile = settings.board_profile;
@@ -132,7 +135,12 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
(int8_t)(root["phy_type"] | PHY_type::PHY_TYPE_NONE),
(int8_t)(root["eth_power"] | 0),
(int8_t)(root["eth_phy_addr"] | 0),
(int8_t)(root["eth_clock_mode"] | 0)};
(int8_t)(root["eth_clock_mode"] | 0),
#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1)
(int8_t)(root["led_type"] | 1)};
#else
(int8_t)(root["led_type"] | 0)}; // 0 = LED, 1 = RGB-LED
#endif
}
// check valid pins in this board profile
if (!System::is_valid_gpio(data[0], psram) || !System::is_valid_gpio(data[1], psram) || !System::is_valid_gpio(data[2], psram)
@@ -179,22 +187,22 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
// override if we know the target from the build config like C3, S2, S3 etc..
#elif CONFIG_IDF_TARGET_ESP32C3
settings.board_profile = "C3MINI";
settings.board_profile = "C3MINI";
#elif CONFIG_IDF_TARGET_ESP32S2
settings.board_profile = "S2MINI";
settings.board_profile = "S2MINI";
#elif CONFIG_IDF_TARGET_ESP32S3
settings.board_profile = "S32S3"; // BBQKees Gateway S3
settings.board_profile = "S32S3"; // BBQKees Gateway S3
#endif
// apply the new board profile setting
System::load_board_profile(data, settings.board_profile.c_str());
}
if (old_board_profile != settings.board_profile) {
// see if need to override the set board profile (e.g. forced by NVS boot string)
EMSESP::logger().info("Setting new Board profile %s (was %s)", settings.board_profile.c_str(), old_board_profile.c_str());
} else {
EMSESP::logger().info("Board profile set to %s", settings.board_profile.c_str());
if (org_board_profile != settings.board_profile) {
if (org_board_profile.isEmpty()) {
EMSESP::logger().info("Setting board profile to %s", settings.board_profile.c_str());
} else {
EMSESP::logger().info("Setting board profile to %s (was %s)", settings.board_profile.c_str(), org_board_profile.c_str());
}
}
int prev;
@@ -228,6 +236,9 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
prev = settings.eth_clock_mode;
settings.eth_clock_mode = data[8];
check_flag(prev, settings.eth_clock_mode, ChangeFlags::RESTART);
prev = settings.led_type;
settings.led_type = data[9];
check_flag(prev, settings.led_type, ChangeFlags::LED);
// tx_mode, rx and tx pins
prev = settings.tx_mode;
@@ -303,6 +314,7 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
settings.low_clock = root["low_clock"];
check_flag(prev, settings.low_clock, ChangeFlags::RESTART);
// Modbus settings
prev = settings.modbus_enabled;
settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED;
check_flag(prev, settings.modbus_enabled, ChangeFlags::RESTART);
@@ -454,6 +466,7 @@ void WebSettingsService::board_profile(AsyncWebServerRequest * request) {
root["eth_power"] = data[6];
root["eth_phy_addr"] = data[7];
root["eth_clock_mode"] = data[8];
root["led_type"] = data[9];
response->setLength();
request->send(response);

View File

@@ -19,7 +19,7 @@
#ifndef WebSettingsService_h
#define WebSettingsService_h
#include "../default_settings.h"
#include "../core/default_settings.h"
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
@@ -54,6 +54,7 @@ class WebSettings {
bool dallas_parasite;
uint8_t led_gpio;
bool hide_led;
uint8_t led_type;
bool low_clock;
bool telnet_enabled;
bool notoken_api;

View File

@@ -27,15 +27,22 @@ namespace emsesp {
WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) {
// GET
server->on(EMSESP_SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { systemStatus(request); });
securityManager->addEndpoint(server, EMSESP_SYSTEM_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
systemStatus(request);
});
// POST - generic action handler
server->on(EMSESP_ACTION_SERVICE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { action(request, json); });
// POST - generic action handler, handles both GET and POST
securityManager->addEndpoint(
server,
EMSESP_ACTION_SERVICE_PATH,
AuthenticationPredicates::IS_AUTHENTICATED,
[this](AsyncWebServerRequest * request, JsonVariant json) { action(request, json); },
HTTP_ANY);
}
// /rest/systemStatus
// This contains both system & hardware Status to avoid having multiple costly endpoints
// This is also used for polling during the RestartMonitor to see if EMS-ESP is alive
// This is also used for polling during the SystemMonitor to see if EMS-ESP is alive
void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap
@@ -58,19 +65,23 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
root["mqtt_status"] = EMSESP::mqtt_.connected();
#ifndef EMSESP_STANDALONE
root["ntp_status"] = [] {
if (esp_sntp_enabled()) {
if (emsesp::EMSESP::system_.ntp_connected()) {
return 2;
} else {
return 1;
}
uint8_t ntp_status = 0; // 0=disabled, 1=enabled, 2=connected
if (esp_sntp_enabled()) {
ntp_status = (emsesp::EMSESP::system_.ntp_connected()) ? 2 : 1;
}
root["ntp_status"] = ntp_status;
if (ntp_status == 2) {
// send back actual time if NTP enabled and active
time_t now = time(nullptr);
if (now > 1500000000L) {
char t[25];
strftime(t, sizeof(t), "%FT%T", localtime(&now));
root["ntp_time"] = t; // optional string
}
return 0;
}();
}
#endif
root["ap_status"] = EMSESP::esp8266React.apStatus();
root["ap_status"] = EMSESP::esp32React.apStatus();
if (emsesp::EMSESP::system_.ethernet_connected()) {
root["network_status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED
@@ -119,8 +130,8 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
root["free_psram"] = ESP.getFreePsram() / 1024;
}
root["model"] = EMSESP::system_.getBBQKeesGatewayDetails();
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
root["temperature"] = EMSESP::system_.temperature();
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
root["temperature"] = Helpers::transformNumFloat(EMSESP::system_.temperature(), 0, EMSESP::system_.fahrenheit() ? 2 : 0); // only 2 decimal places
#endif
// check for a factory partition first
@@ -135,12 +146,11 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
root["has_partition"] = false;
}
// Matches status codes in RestartMonitor.tsx
if (EMSESP::system_.restart_pending()) {
root["status"] = "restarting";
EMSESP::system_.restart_requested(true); // tell emsesp loop to start restart
} else {
root["status"] = EMSESP::system_.upload_isrunning() ? "uploading" : "ready";
// Also used in SystemMonitor.tsx
root["status"] = EMSESP::system_.systemStatus(); // send the status. See System.h for status codes
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART) {
// we're ready to do the actual restart ASAP
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
}
#endif
@@ -183,6 +193,8 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
ok = getCustomSupport(root);
} else if (action == "uploadURL" && is_admin) {
ok = uploadURL(param.c_str());
} else if (action == "systemStatus" && is_admin) {
ok = setSystemStatus(param.c_str());
}
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
@@ -192,12 +204,14 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
Serial.println(COLOR_RESET);
#endif
// send response
// check for error
if (!ok) {
request->send(400);
emsesp::EMSESP::logger().err("Action '%s' failed", action.c_str());
request->send(400); // bad request
return;
}
// send response
response->setLength();
request->send(response);
}
@@ -211,7 +225,7 @@ bool WebStatusService::checkUpgrade(JsonObject root, std::string & versions) {
#else
// for testing only - see api3 test in test.cpp
// current_version_s = "3.6.5";
current_version_s = "3.7.1-dev.8";
current_version_s = "3.7.2-dev.1";
#endif
if (!versions.empty()) {
@@ -244,7 +258,7 @@ bool WebStatusService::checkUpgrade(JsonObject root, std::string & versions) {
}
// action = allvalues
// output all the devices and the values
// output all the devices and their values, including custom entities, scheduler and sensors
void WebStatusService::allvalues(JsonObject output) {
JsonObject device_output;
auto value = F_(values);
@@ -348,4 +362,11 @@ bool WebStatusService::uploadURL(const char * url) {
return true;
}
// action = systemStatus
// sets the system status
bool WebStatusService::setSystemStatus(const char * status) {
emsesp::EMSESP::system_.systemStatus(Helpers::atoint(status));
return true;
}
} // namespace emsesp

View File

@@ -27,7 +27,7 @@ class WebStatusService {
bool exportData(JsonObject root, std::string & type);
bool getCustomSupport(JsonObject root);
bool uploadURL(const char * url);
bool setSystemStatus(const char * status);
void allvalues(JsonObject output);
};