formatting

This commit is contained in:
proddy
2026-10-15 21:21:50 +02:00
parent d7b8c72ac2
commit 5657aacb8b
85 changed files with 1656 additions and 27 deletions
+1
View File
@@ -30,6 +30,7 @@ BreakConstructorInitializersBeforeComma: true # Always break constructor initial
ExperimentalAutoDetectBinPacking: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
InsertNewlineAtEOF: true # clang-format 16+: one newline at end of file if missing
PenaltyBreakBeforeFirstCallParameter: 200
PenaltyExcessCharacter: 10
PointerAlignment: Middle
+1
View File
@@ -64,6 +64,7 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
if (appliedOnce && modeChanged) {
emsesp::EMSESP::network_.schedule_reconnect();
}
appliedOnce = true;
return StateUpdateResult::CHANGED;
+2 -1
View File
@@ -102,6 +102,7 @@ String ArduinoJsonJWT::encode(const char * cstr, int inputLen) {
while (result.length() > 0 && result.charAt(result.length() - 1) == '=') {
result.remove(result.length() - 1);
}
result.replace('+', '-');
result.replace('/', '_');
@@ -129,4 +130,4 @@ String ArduinoJsonJWT::decode(String value) {
// return as string
return result;
}
}
+3
View File
@@ -52,6 +52,7 @@ class StaticContentHandler : public AsyncWebHandler {
return &WWW_ASSETS[i];
}
}
return nullptr;
}
@@ -62,6 +63,7 @@ class StaticContentHandler : public AsyncWebHandler {
if (cached == nullptr) {
cached = lookup("/index.html");
}
return cached;
}
};
@@ -91,6 +93,7 @@ void ESP32React::begin() {
defaultHeaders.addHeader(asyncsrv::T_CORS_ACAH, "Accept, Content-Type, Authorization");
defaultHeaders.addHeader(asyncsrv::T_CORS_ACAC, "true");
}
defaultHeaders.addHeader(asyncsrv::T_Server, networkSettings.hostname);
});
_apSettingsService.begin();
+1
View File
@@ -35,6 +35,7 @@ class FSPersistence {
settingsFile.close();
return;
}
settingsFile.close();
}
+2 -1
View File
@@ -8,6 +8,7 @@ class IPUtils {
static bool isSet(const IPAddress & ip) {
return ip != getNotSetIP();
}
static bool isNotSet(const IPAddress & ip) {
return ip == getNotSetIP();
}
@@ -19,4 +20,4 @@ class IPUtils {
}
};
#endif
#endif
+3
View File
@@ -14,14 +14,17 @@ class JsonUtils {
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) {
const JsonVariant value = root[key];
if (!value.is<String>() || !ip.fromString(value.as<const char *>())) {
ip = defaultIp;
}
}
static void writeIP(JsonObject root, const String & key, const IPAddress & ip) {
if (IPUtils::isSet(ip)) {
char ipStr[16];
+16
View File
@@ -38,6 +38,7 @@ void MqttSettingsService::startClient() {
if ((isSecure && _state.enableTLS) || (!isSecure && !_state.enableTLS)) {
return;
}
delete _mqttClient;
_mqttClient = nullptr;
}
@@ -49,10 +50,12 @@ void MqttSettingsService::startClient() {
} else {
_mqttClient = new espMqttClientSecure(EMSESP_MQTT_PRIORITY, EMSESP_MQTT_RUNNING_CORE);
}
if (!_mqttClient) {
emsesp::EMSESP::logger().warning("MQTT Client alloc failed");
return;
}
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)
@@ -62,12 +65,14 @@ void MqttSettingsService::startClient() {
});
return;
}
isSecure = false;
if (emsesp::EMSESP::system_.PSram() == 0) {
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
} else {
_mqttClient = new espMqttClient(EMSESP_MQTT_PRIORITY, EMSESP_MQTT_RUNNING_CORE);
}
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)
@@ -82,14 +87,17 @@ void MqttSettingsService::loop() {
// emsesp::EMSESP::logger().info("Network connection dropped, stopping MQTT client");
_mqttClient->disconnect(true);
}
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
// configure MQTT client
if (_reconfigureMqtt && _state.enabled) {
emsesp::EMSESP::logger().info("Starting MQTT service");
}
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
_reconfigureMqtt = false;
}
if (emsesp::EMSESP::system_.PSram() == 0) {
_mqttClient->loop();
}
@@ -188,21 +196,25 @@ bool MqttSettingsService::configureMqtt() {
String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n";
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(certificate.c_str());
}
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());
}
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);
@@ -322,6 +334,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
if (newSettings.publish_single) {
newSettings.ha_enabled = false;
}
changed = true;
}
@@ -338,6 +351,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
if (newSettings.ha_enabled) {
newSettings.publish_single = false;
}
changed = true;
}
@@ -387,9 +401,11 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
if (newSettings.rootCA.length() == 0 && newSettings.enableTLS) {
newSettings.rootCA = "insecure";
}
if (newSettings.enableTLS != settings.enableTLS || newSettings.rootCA != settings.rootCA) {
changed = true;
}
// save the new settings
settings = newSettings;
+2
View File
@@ -97,8 +97,10 @@ StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
while (settings.tzFormat.length() < 40) {
settings.tzFormat += " ";
}
while (settings.tzFormatT.length() < 40) {
settings.tzFormatT += " ";
}
return StateUpdateResult::CHANGED;
}
+2 -1
View File
@@ -45,6 +45,7 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
return 1;
}
}
return 0;
}();
@@ -59,4 +60,4 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
response->setLength();
request->send(response);
}
}
+3
View File
@@ -9,6 +9,7 @@ void add_ipv6(JsonObject root, NetworkInterface & netif) {
if (found.empty()) {
return;
}
JsonArray addresses = root["ipv6"].to<JsonArray>();
for (const auto & ipv6 : found) {
JsonObject entry = addresses.add<JsonObject>();
@@ -28,10 +29,12 @@ void add_gateways(JsonObject root, NetworkInterface & netif) {
if (IPUtils::isSet(netif.gatewayIP())) {
root["gateway_ip"] = netif.gatewayIP().toString();
}
auto found = emsesp::Network::ipv6_gateways(netif);
if (found.empty()) {
return;
}
JsonArray gateways = root["gateway_ipv6"].to<JsonArray>();
for (const auto & ip : found) {
gateways.add(ip.toString());
+9
View File
@@ -36,6 +36,7 @@ void SecurityManager::ensureRestDispatcher(AsyncWebServer * server) {
if (_restDispatcherInstalled || server == nullptr) {
return;
}
_restDispatcherInstalled = true;
server->addHandler(new RestCatchAllHandler(this));
}
@@ -47,6 +48,7 @@ void SecurityManager::dispatchRest(AsyncWebServerRequest * request, JsonVariant
if (!route.method.matches(method)) {
continue;
}
if (!route.uri.matches(request)) {
continue;
}
@@ -64,6 +66,7 @@ void SecurityManager::dispatchRest(AsyncWebServerRequest * request, JsonVariant
} else {
route.plainHandler(request);
}
return;
}
@@ -78,11 +81,13 @@ bool SecurityManager::RestCatchAllHandler::canHandle(AsyncWebServerRequest * req
if (!request->isHTTP()) {
return false;
}
for (const auto & route : _owner->_restRoutes) {
if (route.method.matches(request->method()) && route.uri.matches(request)) {
return true;
}
}
return false;
}
@@ -96,6 +101,7 @@ void SecurityManager::RestCatchAllHandler::handleBody(AsyncWebServerRequest * re
if (total == 0 || total > kMaxBodySize || !isJsonContent(request)) {
return;
}
if (index == 0 && request->_tempObject == nullptr) {
request->_tempObject = calloc(total + 1, sizeof(uint8_t)); // freed by request destructor
if (request->_tempObject == nullptr) {
@@ -103,6 +109,7 @@ void SecurityManager::RestCatchAllHandler::handleBody(AsyncWebServerRequest * re
return;
}
}
if (request->_tempObject != nullptr) {
memcpy(static_cast<uint8_t *>(request->_tempObject) + index, data, len);
}
@@ -116,8 +123,10 @@ void SecurityManager::RestCatchAllHandler::handleRequest(AsyncWebServerRequest *
request->send(400);
return;
}
json = doc.as<JsonVariant>();
}
_owner->dispatchRest(request, json);
}
+2
View File
@@ -50,9 +50,11 @@ class AuthenticationPredicates {
(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;
};
@@ -29,6 +29,7 @@ Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerReques
String value = tokenParamater->value();
return authenticateJWT(value);
}
return {};
}
@@ -48,6 +49,7 @@ Authentication SecuritySettingsService::authenticateJWT(String & jwt) {
}
}
}
return {};
}
@@ -57,6 +59,7 @@ Authentication SecuritySettingsService::authenticate(const String & username, co
return Authentication(_user);
}
}
return {};
}
@@ -91,5 +94,6 @@ void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) {
return;
}
}
request->send(401);
}
+2 -1
View File
@@ -62,6 +62,7 @@ class SecuritySettings {
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;
}
};
@@ -90,4 +91,4 @@ class SecuritySettingsService final : public StatefulService<SecuritySettings>,
boolean validatePayload(JsonObject parsedPayload, const User * user); // Verify the payload is correct
};
#endif
#endif
+3
View File
@@ -50,6 +50,7 @@ class StatefulService {
if (!cb) {
return 0;
}
StateUpdateHandlerInfo_t updateHandler(std::move(cb), allowRemove);
_updateHandlers.push_back(std::move(updateHandler));
return updateHandler._id;
@@ -73,6 +74,7 @@ class StatefulService {
if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers();
}
return result;
}
@@ -90,6 +92,7 @@ class StatefulService {
if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers();
}
return result;
}
+8
View File
@@ -55,6 +55,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
} else {
emsesp::EMSESP::logger().info("MD5 digest received (%s). Now upload the firmware BIN", _ota.md5());
}
return;
default:
_ota.clearMd5();
@@ -78,6 +79,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
handleError(request, 406);
return;
}
_is_recovery = true;
emsesp::EMSESP::logger().info("Recovery firmware uploading to the %s partition (size: %dKB). Please wait...", target->label, filesize / 1024);
} else
@@ -96,6 +98,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
if (_ota.md5Applied()) {
emsesp::EMSESP::logger().info("Firmware MD5 check enabled (%s)", _ota.md5());
}
request->onDisconnect([this] { handleDisconnect(); }); // success, let's make sure we end the update if the client hangs up
} else {
emsesp::EMSESP::logger().err("OTA begin failed: %s", _ota.errorString());
@@ -133,6 +136,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
handleError(request, 500); // internal error, failed
return;
}
if (final) {
if (!_ota.end()) {
emsesp::EMSESP::logger().err("OTA update failed: %s", _ota.errorString());
@@ -156,6 +160,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
request->send(request->beginResponse(code));
_response_sent = true;
}
_is_firmware = false;
_is_filesystem = false;
_is_recovery = false;
@@ -187,6 +192,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
emsesp::EMSESP::logger().info("Firmware MD5 matches");
root["md5_ok"] = true;
}
response->setLength();
request->send(response);
_ota.clearMd5();
@@ -214,6 +220,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
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;
@@ -237,6 +244,7 @@ void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
if (request->_tempObject) {
return;
}
request->_tempObject = reinterpret_cast<void *>(static_cast<intptr_t>(code)); // stop later chunks from retrying the write
emsesp::EMSESP::logger().info("Upload error: %d", code);
+2 -1
View File
@@ -34,9 +34,10 @@ void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
network["channel"] = WiFi.channel(i);
network["encryption_type"] = static_cast<uint8_t>(WiFi.encryptionType(i));
}
response->setLength();
request->send(response);
} else {
request->send(202); // special code to indicate scan in progress
}
}
}
+1
View File
@@ -34,6 +34,7 @@ class ModuleLibrary {
: key(key)
, module(std::move(module)) {
}
const char * key;
std::unique_ptr<Module> module;
};
+53 -1
View File
@@ -35,22 +35,27 @@ void IRAM_ATTR AnalogSensor::freqIrq0() {
edgecnt[0] = edgecnt[0] + 1;
edge[0] = micros();
}
portEXIT_CRITICAL_ISR(&mux);
}
void IRAM_ATTR AnalogSensor::freqIrq1() {
portENTER_CRITICAL_ISR(&mux);
if (micros() - edge[1] > 10) { // limit to 100kHz
edgecnt[1] = edgecnt[1] + 1;
edge[1] = micros();
}
portEXIT_CRITICAL_ISR(&mux);
}
void IRAM_ATTR AnalogSensor::freqIrq2() {
portENTER_CRITICAL_ISR(&mux);
if (micros() - edge[2] > 10) { // limit to 100kHz
edgecnt[2] = edgecnt[2] + 1;
edge[2] = micros();
}
portEXIT_CRITICAL_ISR(&mux);
}
#endif
@@ -89,6 +94,7 @@ void AnalogSensor::start(const bool factory_settings) {
});
LOG_DEBUG("Adding system sensors for core_voltage, supply_voltage and led");
}
reload(true); // fetch the list of sensors from our customization service
if (!analog_enabled_) {
@@ -140,6 +146,7 @@ void AnalogSensor::reload(bool get_nvs) {
sensors_.clear();
return;
}
changed_ = true;
// load the list of analog sensors from the customization service
// and store them locally and then activate them
@@ -155,11 +162,13 @@ void AnalogSensor::reload(bool get_nvs) {
&& (sensor_.type() != sensor.type || sensor_.offset() != sensor.offset || sensor_.factor() != sensor.factor)) {
sensor_.set_value(sensor.offset);
}
if ((sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2))
&& sensor_.offset() != sensor.offset && sensor.offset != EMSESP::nvs_.getDouble(sensor.name, 0)) {
EMSESP::nvs_.putDouble(sensor.name, sensor.offset);
sensor_.set_value(sensor.offset);
}
sensor_.set_name(sensor.name);
sensor_.set_type(sensor.type);
sensor_.set_offset(sensor.offset);
@@ -169,6 +178,7 @@ void AnalogSensor::reload(bool get_nvs) {
found = true;
}
}
if (!found) {
it = sensors_.erase(it);
} else {
@@ -184,6 +194,7 @@ void AnalogSensor::reload(bool get_nvs) {
found = true;
}
}
if (!found) {
// it's new, we assume it's valid
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type, sensor.is_system);
@@ -212,6 +223,7 @@ void AnalogSensor::reload(bool get_nvs) {
CommandFlag::ADMIN_ONLY);
}
}
return true;
});
@@ -225,10 +237,12 @@ void AnalogSensor::reload(bool get_nvs) {
exclude_types_.push_back(sensor.type());
exclude_types_.push_back(sensor.type() + 3);
}
if ((sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
exclude_types_.push_back(sensor.type());
exclude_types_.push_back(sensor.type() - 3);
}
if ((sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2)) {
exclude_types_.push_back(sensor.type());
}
@@ -247,6 +261,7 @@ void AnalogSensor::reload(bool get_nvs) {
#endif
pinMode(sensor.gpio(), INPUT_PULLUP);
}
if (sensor.type() == AnalogType::ADC) {
LOG_DEBUG("ADC Sensor on GPIO %02d", sensor.gpio());
// analogSetPinAttenuation does not work with analogReadMilliVolts
@@ -263,6 +278,7 @@ void AnalogSensor::reload(bool get_nvs) {
if (double_t val = EMSESP::nvs_.getDouble(sensor.name(), 0)) {
sensor.set_value(val);
}
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) {
LOG_DEBUG("Timer/Rate on GPIO %02d", sensor.gpio());
@@ -290,6 +306,7 @@ void AnalogSensor::reload(bool get_nvs) {
if (double_t val = EMSESP::nvs_.getDouble(sensor.name(), 0)) {
sensor.set_value(val);
}
publish_sensor(sensor);
attachInterrupt(sensor.gpio(), index == 0 ? freqIrq0 : index == 1 ? freqIrq1 : freqIrq2, FALLING);
edgecnt[index] = 0;
@@ -320,6 +337,7 @@ void AnalogSensor::reload(bool get_nvs) {
} else if (sensor.offset() < 0) {
sensor.set_offset(0);
}
dacWrite(sensor.gpio(), sensor.offset());
sensor.set_value(sensor.offset());
} else
@@ -330,6 +348,7 @@ void AnalogSensor::reload(bool get_nvs) {
} else if (sensor.offset() < 0) {
sensor.set_offset(0);
}
dacWrite(sensor.gpio(), sensor.offset());
sensor.set_value(sensor.offset());
} else
@@ -344,10 +363,12 @@ void AnalogSensor::reload(bool get_nvs) {
} else if (sensor.uom() > 1) {
sensor.set_uom(2);
}
sensor.set_offset(sensor.offset() > 0 ? 1 : 0);
digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() > 0));
sensor.set_value(sensor.offset());
}
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::PULSE) {
LOG_DEBUG("Pulse on GPIO %02d", sensor.gpio());
@@ -362,6 +383,7 @@ void AnalogSensor::reload(bool get_nvs) {
} else if (sensor.offset() < 0) {
sensor.set_offset(0);
}
ledcWrite(sensor.gpio(), (uint32_t)(sensor.offset() * 8191 / 100));
sensor.set_value(sensor.offset());
sensor.set_uom(DeviceValueUOM::PERCENT);
@@ -406,6 +428,7 @@ void AnalogSensor::measure() {
sensor.sum_ = (sensor.sum_ * 15 + a * 16) / 16;
sensor.analog_ = sensor.sum_ / 16;
}
if (sensor.analog_ > 0 && sensor.analog_ < NTC_VREF_MV
&& (sensor.last_reading_ + 1 < sensor.analog_ || sensor.last_reading_ > sensor.analog_ + 1)) {
sensor.set_value(sensor.offset() + 1 / (1 / T25 + log((double)sensor.analog_ / (NTC_VREF_MV - sensor.analog_) * (Rt / R0)) / Beta)
@@ -429,6 +452,7 @@ void AnalogSensor::measure() {
} else if (micros() - edge[index] > FREQ_ZERO_TIMEOUT_US && sensor.value() > 0) {
sensor.set_value(0);
}
if (sensor.value() != oldval) {
changed_ = true;
publish_sensor(sensor);
@@ -461,6 +485,7 @@ void AnalogSensor::measure() {
sensor.polltime_ = uuid::get_uptime(); // remember time of pinchange
sensor.poll_ = current_reading;
}
// debounce and check for real pinchange
if (uuid::get_uptime() - sensor.polltime_ >= 15 && sensor.poll_ != sensor.last_reading_) {
sensor.last_reading_ = sensor.poll_;
@@ -475,15 +500,18 @@ void AnalogSensor::measure() {
} else if (sensor.type() == AnalogType::TIMER) { // default seconds with factor 1
sensor.set_value(sensor.factor() * (sensor.polltime_ - sensor.last_polltime_) / 1000);
}
sensor.last_polltime_ = sensor.polltime_;
}
}
}
if (sensor.type() == AnalogType::PULSE && sensor.value() && sensor.polltime_ && sensor.polltime_ < uuid::get_uptime()) {
sensor.set_value(0);
digitalWrite(sensor.gpio(), sensor.offset());
sensor.polltime_ = 0;
}
// see if there is a change and increment # reads
if (old_value != sensor.value()) {
sensorreads_++;
@@ -531,6 +559,7 @@ bool AnalogSensor::update(uint8_t gpio, const char * org_name, double offset, do
} else {
strlcpy(name, org_name, sizeof(name));
}
// first see if we can find the sensor in our customization list
bool found_sensor = false;
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
@@ -545,12 +574,14 @@ bool AnalogSensor::update(uint8_t gpio, const char * org_name, double offset, do
if (EMSESP::nvs_.isKey(AnalogCustomization.name)) {
EMSESP::nvs_.remove(AnalogCustomization.name);
}
settings.analogCustomizations.remove(AnalogCustomization);
} else {
// update existing record
if (!strcmp(name, AnalogCustomization.name) && EMSESP::nvs_.isKey(AnalogCustomization.name)) {
EMSESP::nvs_.remove(AnalogCustomization.name);
}
strlcpy(AnalogCustomization.name, name, sizeof(AnalogCustomization.name));
AnalogCustomization.offset = offset;
AnalogCustomization.factor = factor;
@@ -559,9 +590,11 @@ bool AnalogSensor::update(uint8_t gpio, const char * org_name, double offset, do
AnalogCustomization.is_system = is_system;
LOG_DEBUG("Customizing existing analog GPIO %02d", gpio);
}
return StateUpdateResult::CHANGED; // persist the change
}
}
return StateUpdateResult::UNCHANGED;
});
@@ -608,6 +641,7 @@ bool AnalogSensor::updated_values() {
changed_ = false;
return true;
}
return false;
}
@@ -620,14 +654,17 @@ void AnalogSensor::publish_sensor(const Sensor & sensor) const {
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", F_(analogsensor), "_data", sensor.name());
}
char result[12];
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT) {
Helpers::render_boolean(result, sensor.value() != 0);
} else {
Helpers::render_value(result, sensor.value(), 2); // double
}
Mqtt::queue_publish(topic, result); // always publish as doubles
}
char cmd[COMMAND_MAX_LENGTH];
snprintf(cmd, sizeof(cmd), "%s/%s", F_(analogsensor), sensor.name());
EMSESP::webSchedulerService.onChange(cmd);
@@ -661,6 +698,7 @@ void AnalogSensor::remove_ha_topic(const int8_t type, const uint8_t gpio) const
} else {
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio);
}
Mqtt::queue_remove_topic(topic);
}
@@ -677,6 +715,7 @@ void AnalogSensor::publish_values(const bool force) {
for (const auto & sensor : sensors_) {
publish_sensor(sensor);
}
return;
} else if (!EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
return; // wait for first time period
@@ -728,10 +767,12 @@ void AnalogSensor::publish_values(const bool force) {
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name());
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
}
char sample_val[12] = "0";
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT || sensor.type() == AnalogType::PULSE) {
Helpers::render_boolean(sample_val, false);
}
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
@@ -840,6 +881,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
for (const auto & sensor : sensors_) {
output[sensor.name()] = sensor.value();
}
return true;
}
@@ -848,6 +890,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
for (const auto & sensor : sensors_) {
get_value_json(output[sensor.name()].to<JsonObject>(), sensor);
}
return true;
}
@@ -857,6 +900,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
output["api_data"] = metrics;
return true;
}
return false;
}
@@ -886,12 +930,14 @@ std::string AnalogSensor::get_metrics_prometheus() {
} else {
result += (std::string) ", boolean";
}
result += (std::string) ", readable, visible";
if (sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE
|| (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2)
|| (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
result += (std::string) ", writable";
}
result += (std::string) "\n# TYPE emsesp_" + sensor.name() + " gauge\n";
result += (std::string) "emsesp_" + sensor.name() + " ";
if (sensor.type() != AnalogType::DIGITAL_OUT && sensor.type() != AnalogType::DIGITAL_IN) {
@@ -900,6 +946,7 @@ std::string AnalogSensor::get_metrics_prometheus() {
result += (std::string)(sensor.value() == 0 ? "0\n" : "1\n");
}
}
return result;
}
@@ -970,6 +1017,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
if (!Helpers::value2bool(value, b)) {
return false;
}
val = b ? 1 : 0;
}
@@ -984,6 +1032,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
// sensor.set_offset(val);
sensor.set_value(val);
}
sensor.set_offset(sensor.value());
if (sensor.value() != EMSESP::nvs_.getDouble(sensor.name(), 0)) {
EMSESP::nvs_.putDouble(sensor.name(), sensor.value());
@@ -1037,16 +1086,19 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
} else if (val < 0) {
val = 0;
}
sensor.set_offset(val);
sensor.set_value(val);
ledcWrite(sensor.gpio(), (uint32_t)(sensor.offset() * 8191 / 100));
} else {
return false;
}
if (oldoffset != sensor.offset()) {
publish_sensor(sensor);
changed_ = true;
}
return true;
}
}
@@ -1054,4 +1106,4 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
return false;
}
} // namespace emsesp
} // namespace emsesp
+5 -1
View File
@@ -63,6 +63,7 @@ class AnalogSensor {
bool is_system() const {
return is_system_;
}
void set_is_system(const bool is_system) {
is_system_ = is_system;
}
@@ -70,6 +71,7 @@ class AnalogSensor {
double factor() const {
return factor_;
}
void set_factor(const double factor) {
factor_ = factor;
}
@@ -81,6 +83,7 @@ class AnalogSensor {
void set_uom(const uint8_t uom) {
uom_ = uom;
}
uint8_t uom() const {
return uom_;
}
@@ -172,6 +175,7 @@ class AnalogSensor {
if (exclude_disabled_system) {
return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) { return !sensor.is_system(); });
}
return sensors_.size();
}
@@ -221,4 +225,4 @@ class AnalogSensor {
} // namespace emsesp
#endif
#endif
+33
View File
@@ -35,6 +35,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
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);
@@ -102,6 +103,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
if (device_type >= EMSdevice::DeviceType::BOILER) {
command_p = parse_command_string(command_p, id_n);
}
if (command_p == nullptr) {
// handle dead endpoints like api/system or api/boiler
// default to 'value' for all devices
@@ -159,6 +161,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
if (device_type1 >= EMSdevice::DeviceType::BOILER) {
data_p = parse_command_string(data_p, id_d);
}
if (data_p == nullptr) {
return CommandRet::INVALID;
}
@@ -202,6 +205,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
} else {
return json_message(CommandRet::ERROR, "cannot parse command", output); // can't process
}
return return_code;
}
@@ -222,6 +226,7 @@ const char * Command::return_code_string(const uint8_t return_code) {
default:
break;
}
static char s[4]; // static to avoid allocation on each call and loosing scope
return Helpers::smallitoa(s, return_code);
}
@@ -249,6 +254,7 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
for (size_t i = 0; i < len; i++) {
lowerCmd[i] = tolower(command[i]);
}
lowerCmd[len] = '\0';
// check prefix and valid number range, also check 'id'
@@ -294,6 +300,7 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
id = id_org;
return cmd_org;
}
command++;
// return null for empty command
@@ -315,6 +322,7 @@ const char * Command::get_attribute(const char * cmd) {
*breakp = '\0';
return breakp + 1;
}
return nullptr;
}
@@ -365,6 +373,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
if (command == nullptr) {
return CommandRet::NOT_FOUND;
}
char cmd[COMMAND_MAX_LENGTH];
strlcpy(cmd, Helpers::toLower(command).c_str(), sizeof(cmd));
@@ -378,6 +387,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
if (!strcmp(cmd, F_(commands))) {
return Command::list(device_type, output);
}
// for the Commands device, calling a named command executes it (using its stored value)
// rather than returning its definition, so skip the value-info lookup and fall through
// to the registered command function. The list keywords above are still handled.
@@ -430,6 +440,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
output["message"] = err;
LOG_WARNING("Command failed: %s", err.c_str());
}
return CommandRet::NOT_FOUND;
}
@@ -461,6 +472,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
} else {
snprintf(error, sizeof(error), "Command %s: %s failed (%s)", cmd, value, return_code_string(return_code));
}
output.clear();
output["message"] = error;
LOG_WARNING(error);
@@ -475,6 +487,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
} else {
snprintf(info_s, sizeof(info_s), "%s/%s", dname, cmd);
}
if (single_command) {
// log as DEBUG (TRACE) regardless if compiled with EMSESP_DEBUG
logger_.debug("%sCalled command %s", ro.c_str(), info_s);
@@ -552,6 +565,7 @@ void Command::erase_command(const uint8_t device_type, const char * cmd, uint8_t
if ((cmd == nullptr) || (strlen(cmd) == 0) || (cmdfunctions_.empty())) {
return;
}
const std::string cmd_lower = Helpers::toLower(cmd);
auto it = cmdfunctions_.begin();
for (auto const & cf : cmdfunctions_) {
@@ -559,6 +573,7 @@ void Command::erase_command(const uint8_t device_type, const char * cmd, uint8_t
cmdfunctions_.erase(it);
return;
}
it++;
}
}
@@ -607,6 +622,7 @@ bool Command::list(const uint8_t device_type, JsonObject output) {
sorted_cmds.push_back(tagged_cmd(cf.cmd_, cf.flags_));
}
}
sorted_cmds.sort();
for (const auto & cl : sorted_cmds) {
@@ -616,6 +632,7 @@ bool Command::list(const uint8_t device_type, JsonObject output) {
}
}
}
return true;
}
@@ -628,6 +645,7 @@ 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");
@@ -638,6 +656,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
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
@@ -650,6 +669,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
shell.print(cl);
shell.print(" ");
}
shell.println();
return;
}
@@ -668,6 +688,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
while (i++ < 30) {
shell.print(' ');
}
shell.print(COLOR_BRIGHT_CYAN);
if (cf.has_flags(CommandFlag::CMD_FLAG_HC)) {
shell.print(Helpers::translated_word(FL_(tag_hcx)));
@@ -685,12 +706,14 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
shell.print(Helpers::translated_word(FL_(tag_srcx)));
shell.print(' ');
}
shell.print(Helpers::translated_word(cf.description_));
if (!cf.has_flags(CommandFlag::ADMIN_ONLY)) {
shell.print(' ');
shell.print(COLOR_BRIGHT_GREEN);
shell.print('*');
}
shell.print(COLOR_RESET);
shell.println();
}
@@ -747,6 +770,7 @@ void Command::show_devices(uuid::console::Shell & shell) {
if (EMSESP::sensor_enabled()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::TEMPERATURESENSOR));
}
if (EMSESP::analog_enabled()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR));
}
@@ -759,6 +783,7 @@ void Command::show_devices(uuid::console::Shell & shell) {
}
}
}
shell.println();
}
@@ -788,6 +813,7 @@ void Command::show_all(uuid::console::Shell & shell) {
if (EMSESP::sensor_enabled()) {
show(shell, EMSdevice::DeviceType::TEMPERATURESENSOR, true);
}
if (EMSESP::analog_enabled()) {
show(shell, EMSdevice::DeviceType::ANALOGSENSOR, true);
}
@@ -811,6 +837,7 @@ uint8_t Command::json_message(uint8_t error_code, const char * message, const Js
} else {
output["message"] = message;
}
return error_code;
}
@@ -826,6 +853,7 @@ std::string SUrlParser::path() {
s += f;
s += "/";
}
s.pop_back(); // deleting last letter, that is slash '/'
return std::string(s);
}
@@ -858,12 +886,14 @@ bool SUrlParser::parse(const char * uri) {
m_folders.push_back(s);
s.clear();
}
t = Type::folder;
} else if (*c == '?' && (t == Type::folder || t == Type::begin)) {
if (s.length() > 0) {
m_folders.push_back(s);
s.clear();
}
t = Type::param;
} else if (*c == '=' && (t == Type::param || t == Type::begin)) {
m_keysvalues[s] = "";
@@ -877,6 +907,7 @@ bool SUrlParser::parse(const char * uri) {
m_keysvalues[s] = "";
last_param = s;
}
t = Type::param;
s.clear();
} else if (*c == '\0' && s.length() > 0) {
@@ -888,11 +919,13 @@ bool SUrlParser::parse(const char * uri) {
m_keysvalues[s] = "";
last_param = s;
}
s.clear();
} else if (*c == '\0' && s.length() == 0) {
if (t == Type::param && last_param.length() > 0) {
m_keysvalues[last_param] = "";
}
s.clear();
} else {
s += *c;
+4 -1
View File
@@ -86,12 +86,15 @@ class Command {
inline void add_flags(uint8_t flags) {
flags_ |= flags;
}
inline bool has_flags(uint8_t flags) const {
return (flags_ & flags) == flags;
}
inline void remove_flags(uint8_t flags) {
flags_ &= ~flags;
}
inline uint8_t flags() const {
return flags_;
}
@@ -183,4 +186,4 @@ class SUrlParser {
} // namespace emsesp
#endif
#endif
+8
View File
@@ -55,6 +55,7 @@ static void console_log_level(Shell & shell, const std::vector<std::string> & ar
return;
}
}
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(shell.log_level()));
}
@@ -224,6 +225,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
return StateUpdateResult::CHANGED;
}
}
// otherwise add a new admin user
securitySettings.users.emplace_back(F_(admin), password2.c_str(), true);
return StateUpdateResult::CHANGED;
@@ -395,6 +397,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
if (!data.empty()) {
data += " ";
}
data += arg;
}
@@ -509,6 +512,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
if (device_type >= EMSdevice::DeviceType::BOILER) {
cmd = Command::parse_command_string(cmd, id); // extract hc or dhw
}
if (cmd == nullptr) {
cmd = F_(values);
}
@@ -544,6 +548,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
shell.println(data.c_str());
return;
}
serializeJsonPretty(doc, shell);
shell.println();
return;
@@ -575,6 +580,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
devices_list.emplace_back(EMSdevice::device_type_2_device_name(device_class.first));
}
}
return devices_list;
} else if (current_arguments.size() == 1) {
uint8_t device_type = EMSdevice::device_name_2_device_type(current_arguments[0].c_str());
@@ -585,6 +591,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
command_list.emplace_back(cf.cmd_);
}
}
return command_list;
}
}
@@ -612,6 +619,7 @@ void EMSESPShell::stopped() {
if (has_flags(CommandFlags::ADMIN)) {
logger().log(LogLevel::INFO, LogFacility::AUTH, F("Admin session closed on console %s"), console_name().c_str());
}
logger().log(LogLevel::INFO, LogFacility::CONSOLE, F("User session closed on console %s"), console_name().c_str());
}
+110
View File
@@ -31,6 +31,7 @@ uint8_t EMSdevice::count_entities() {
count++;
}
}
return count;
}
@@ -40,6 +41,7 @@ uint8_t EMSdevice::count_entities_fav() {
for (const auto & dv : devicevalues_) {
count += dv.has_state(DeviceValueState::DV_FAVORITE) && dv.hasValue();
}
return count;
}
@@ -50,6 +52,7 @@ bool EMSdevice::has_entities() const {
return true;
}
}
bool found = false;
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (const auto & entityCustomization : settings.entityCustomizations) {
@@ -84,6 +87,7 @@ uint8_t EMSdevice::tag_to_flag(const uint8_t tag) {
} else if (tag >= DeviceValueTAG::TAG_SRC1 && tag <= DeviceValueTAG::TAG_SRC16) {
return CommandFlag::CMD_FLAG_SRC;
}
return 0;
}
@@ -112,6 +116,7 @@ const char * EMSdevice::brand_to_cstr() const {
if (!custom_brand().empty()) {
return custom_brand().c_str();
}
switch (brand_) {
case EMSdevice::Brand::BOSCH:
return F_(bosch);
@@ -192,6 +197,7 @@ const char * EMSdevice::device_type_2_device_name_translated() {
if (flags_ == EMS_DEVICE_FLAG_HEATPUMP) {
return Helpers::translated_word(FL_(boiler_hp_device));
}
return Helpers::translated_word(FL_(boiler_device));
case DeviceType::THERMOSTAT:
return Helpers::translated_word(FL_(thermostat_device));
@@ -224,6 +230,7 @@ const char * EMSdevice::device_type_2_device_name_translated() {
default:
break;
}
return Helpers::translated_word(FL_(unknown_device));
}
@@ -243,45 +250,59 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (!strcmp(lowtopic, F_(boiler))) {
return DeviceType::BOILER;
}
if (!strcmp(lowtopic, F_(thermostat))) {
return DeviceType::THERMOSTAT;
}
if (!strcmp(lowtopic, F_(scheduler))) {
return DeviceType::SCHEDULER;
}
if (!strcmp(lowtopic, F_(heatpump))) {
return DeviceType::HEATPUMP;
}
if (!strcmp(lowtopic, F_(solar))) {
return DeviceType::SOLAR;
}
if (!strcmp(lowtopic, F_(mixer))) {
return DeviceType::MIXER;
}
if (!strcmp(lowtopic, F_(switcher))) {
return DeviceType::SWITCH;
}
if (!strcmp(lowtopic, F_(gateway))) {
return DeviceType::GATEWAY;
}
if (!strcmp(lowtopic, F_(alert))) {
return DeviceType::ALERT;
}
if (!strcmp(lowtopic, F_(extension))) {
return DeviceType::EXTENSION;
}
if (!strcmp(lowtopic, F_(heatsource))) {
return DeviceType::HEATSOURCE;
}
if (!strcmp(lowtopic, F_(ventilation))) {
return DeviceType::VENTILATION;
}
if (!strcmp(lowtopic, F_(water))) {
return DeviceType::WATER;
}
if (!strcmp(lowtopic, F_(pool))) {
return DeviceType::POOL;
}
if (!strcmp(lowtopic, F_(connect))) {
return DeviceType::CONNECT;
}
@@ -290,18 +311,23 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (!strcmp(lowtopic, F_(custom))) {
return DeviceType::CUSTOM;
}
if (!strcmp(lowtopic, F_(temperaturesensor))) {
return DeviceType::TEMPERATURESENSOR;
}
if (!strcmp(lowtopic, F_(analogsensor))) {
return DeviceType::ANALOGSENSOR;
}
if (!strcmp(lowtopic, F_(scheduler))) {
return DeviceType::SCHEDULER;
}
if (!strcmp(lowtopic, F_(commands))) {
return DeviceType::COMMAND;
}
if (!strcmp(lowtopic, F_(system))) {
return DeviceType::SYSTEM;
}
@@ -403,6 +429,7 @@ bool EMSdevice::is_fetch(uint16_t telegram_id, uint8_t len) const {
return tf.fetch_ && tf.length_ >= len;
}
}
return false;
}
@@ -413,6 +440,7 @@ bool EMSdevice::is_received(uint16_t telegram_id) const {
return tf.received_;
}
}
return false;
}
@@ -423,6 +451,7 @@ bool EMSdevice::has_tags(const int8_t tag) const {
return true;
}
}
return false;
}
@@ -433,6 +462,7 @@ bool EMSdevice::has_cmd(const char * cmd, const int8_t id) const {
return true;
}
}
return false;
}
@@ -471,6 +501,7 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) const {
if (telegram_functions_.empty()) {
return;
}
/*
// colored list of type-ids
shell.printf(" This %s will listen to telegram type IDs: ", device_type_name().c_str());
@@ -492,6 +523,7 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) const {
shell.printf("0x%02X ", tf.telegram_type_id_);
}
}
shell.println();
shell.printf(" Fetched telegram type IDs: ");
for (const auto & tf : telegram_functions_) {
@@ -499,6 +531,7 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) const {
shell.printf("0x%02X ", tf.telegram_type_id_);
}
}
shell.println();
shell.printf(" Pending telegram type IDs: ");
for (const auto & tf : telegram_functions_) {
@@ -506,11 +539,13 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) const {
shell.printf("0x%02X ", tf.telegram_type_id_);
}
}
shell.println();
shell.printf(" Ignored telegram type IDs: ");
for (auto handlers : handlers_ignored_) {
shell.printf("0x%02X ", handlers);
}
shell.println();
}
@@ -525,6 +560,7 @@ char * EMSdevice::show_telegram_handlers(char * result, const size_t len, const
if (i++ > 0) {
strlcat(result, " ", len);
}
strlcat(result, Helpers::hextoa(tf.telegram_type_id_, true).c_str(), len);
}
}
@@ -535,6 +571,7 @@ char * EMSdevice::show_telegram_handlers(char * result, const size_t len, const
if (i++ > 0) {
strlcat(result, " ", len);
}
strlcat(result, Helpers::hextoa(h).c_str(), len);
}
}
@@ -548,6 +585,7 @@ void EMSdevice::add_handlers_ignored(const uint16_t handler) {
return;
}
}
handlers_ignored_.push_back(handler);
}
@@ -623,6 +661,7 @@ void EMSdevice::add_device_value(int8_t tag, // to
} else if (uom == DeviceValueUOM::PERCENT) {
*(uint8_t *)(value_p) = use_max ? static_cast<uint8_t>(max) : (test_active ? EMS_VALUE_DEFAULT_UINT8_DUMMY : EMS_VALUE_DEFAULT_UINT8);
}
break;
}
@@ -662,6 +701,7 @@ void EMSdevice::add_device_value(int8_t tag, // to
if (has_custom_name) {
custom_fullname = entity_id.substr(custom_name_pos + 1);
}
break;
}
}
@@ -692,6 +732,7 @@ void EMSdevice::erase_device_values() {
Command::erase_command(device_type_, dv.short_name, tag_to_flag(dv.tag));
}
}
devicevalues_.clear();
telegram_functions_.clear();
handlers_ignored_.clear();
@@ -802,6 +843,7 @@ bool EMSdevice::is_readable(const void * value_p) const {
return !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
}
}
return false;
}
@@ -814,6 +856,7 @@ bool EMSdevice::is_readonly(const std::string & cmd, const int8_t id) const {
return dv.has_state(DeviceValueState::DV_READONLY);
}
}
return true; // not found, no write
}
@@ -824,6 +867,7 @@ bool EMSdevice::has_command(const void * value_p) const {
return dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
}
}
return false;
}
@@ -879,8 +923,10 @@ void EMSdevice::publish_value(void * value_p) const {
strlcpy(payload, enum_str, sizeof(payload));
}
}
break;
}
case DeviceValueType::UINT16:
Helpers::render_value(payload, *(uint16_t *)(value_p), num_op, fahrenheit);
break;
@@ -905,6 +951,7 @@ void EMSdevice::publish_value(void * value_p) const {
if (Helpers::hasValue((char *)(value_p))) {
Helpers::render_string(payload, (char *)(value_p), sizeof(payload));
}
break;
default:
break;
@@ -913,6 +960,7 @@ void EMSdevice::publish_value(void * value_p) const {
if (Mqtt::publish_single() && payload[0] != '\0') {
Mqtt::queue_publish(topic, payload);
}
// check scheduler for on change
char cmd[COMMAND_MAX_LENGTH];
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
@@ -920,6 +968,7 @@ void EMSdevice::publish_value(void * value_p) const {
} else {
snprintf(cmd, sizeof(cmd), "%s/%s", device_type_2_device_name(device_type_), (dv.short_name));
}
EMSESP::webSchedulerService.onChange(cmd);
}
}
@@ -933,6 +982,7 @@ std::string EMSdevice::get_value_uom(const std::string & shortname) const {
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break;
}
return EMSdevice::uom_to_string(dv.uom);
}
}
@@ -949,6 +999,7 @@ bool EMSdevice::export_values(uint8_t device_type, JsonObject output, const int8
has_value |= emsdevice->generate_values(output, id, (id < 1), output_target); // use nested for id -1 and 0
}
}
return has_value;
}
@@ -962,10 +1013,12 @@ bool EMSdevice::export_values(uint8_t device_type, JsonObject output, const int8
output_hc = output[EMSdevice::tag_to_mqtt(tag)].to<JsonObject>();
nest_created = true;
}
has_value |= emsdevice->generate_values(output_hc, tag, true, output_target); // use nested for id -1 and 0
}
}
}
return has_value;
}
@@ -1083,12 +1136,14 @@ void EMSdevice::generate_values_web(JsonObject output, const bool is_dashboard)
l.add(Helpers::render_boolean(result, false, true));
l.add(Helpers::render_boolean(result, true, true));
}
// add command help template
else if (dv.type == DeviceValueType::STRING || (dv.type == DeviceValueType::CMD && dv.uom == DeviceValueUOM::NONE)) {
if (dv.options_size == 1) {
obj["h"] = dv.options_single[0]; // NOT translated
}
}
// handle INTs
else {
// add min and max values, if available
@@ -1182,6 +1237,7 @@ void EMSdevice::generate_values_web_customization(JsonArray output) {
} else {
obj["n"] = "!" + std::string(fullname); // prefix commands with a !
}
if (dv.has_tag()) {
obj["t"] = tag_to_string(dv.tag);
}
@@ -1229,6 +1285,7 @@ void EMSdevice::generate_values_web_customization(JsonArray output) {
obj["w"] = false; // not writeable as it won't be shown
}
}
break;
}
}
@@ -1243,6 +1300,7 @@ void EMSdevice::set_climate_minmax(int8_t tag, int16_t min, uint32_t max) {
dv.max = max;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
}
return;
}
}
@@ -1254,6 +1312,7 @@ void EMSdevice::setValueEnum(const void * value_p, const char * const ** options
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;
@@ -1324,6 +1383,7 @@ void EMSdevice::getCustomizationEntities(std::vector<std::string> & entity_ids)
strcpy(name, tag_to_mqtt(dv.tag));
strcat(name, "/");
}
strcat(name, dv.short_name);
std::string entity_name = name;
@@ -1335,6 +1395,7 @@ void EMSdevice::getCustomizationEntities(std::vector<std::string> & entity_ids)
break;
}
}
if (!is_set && (mask || dv.has_custom_fullname())) {
if (!dv.has_custom_fullname()) {
entity_ids.push_back(Helpers::hextoa(mask, false) + entity_name);
@@ -1403,6 +1464,7 @@ void EMSdevice::dump_devicevalue_info() {
Serial.print("\\|");
}
}
Serial.print(']');
break;
@@ -1454,6 +1516,7 @@ void EMSdevice::dump_devicevalue_info() {
Serial.print(dv.max);
Serial.print(")");
}
Serial.print(",");
// uom
@@ -1462,6 +1525,7 @@ void EMSdevice::dump_devicevalue_info() {
} else {
Serial.print(DeviceValue::DeviceValueUOM_s[dv.uom]);
}
Serial.print(",");
// writeable flag
@@ -1576,9 +1640,11 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
if (!strcmp(cmd, F_(info))) {
return export_values(device_type(), output, tag, EMSdevice::OUTPUT_TARGET::API_VERBOSE);
}
if (!strcmp(cmd, F_(values))) {
return export_values(device_type(), output, tag, EMSdevice::OUTPUT_TARGET::API_SHORTNAMES);
}
if (!strcmp(cmd, F_(entities))) {
char name[30];
for (auto & dv : devicevalues_) {
@@ -1589,14 +1655,17 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
get_value_json(output[name].to<JsonObject>(), dv);
}
}
return true;
}
if (!strcmp(cmd, F_(metrics))) {
std::string metrics = get_metrics_prometheus(tag);
if (!metrics.empty()) {
output["api_data"] = metrics;
return true;
}
return false;
}
@@ -1613,6 +1682,7 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
return Command::get_attribute(output, cmd_s, attribute_s);
}
}
return false; // not found, but don't return a message error yet
}
@@ -1649,11 +1719,13 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
json[value] = Helpers::translated_word(dv.options[*(uint8_t *)(dv.value_p)]); // text
}
}
json[type] = F_(enum);
JsonArray enum_ = json[F_(enum)].to<JsonArray>();
for (uint8_t i = 0; i < dv.options_size; i++) {
enum_.add(Helpers::translated_word(dv.options[i]));
}
break;
}
@@ -1661,6 +1733,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
json[value] = serialized(Helpers::render_value(val, *(uint16_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
}
json[type] = F_(number);
break;
@@ -1668,6 +1741,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
json[value] = serialized(Helpers::render_value(val, *(uint8_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
}
json[type] = F_(number);
break;
@@ -1675,6 +1749,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
if (Helpers::hasValue(*(int16_t *)(dv.value_p))) {
json[value] = serialized(Helpers::render_value(val, *(int16_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
}
json[type] = F_(number);
break;
@@ -1682,6 +1757,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
if (Helpers::hasValue(*(int8_t *)(dv.value_p))) {
json[value] = serialized(Helpers::render_value(val, *(int8_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
}
json[type] = F_(number);
break;
@@ -1690,6 +1766,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = serialized(Helpers::render_value(val, *(uint32_t *)(dv.value_p), dv.numeric_operator));
}
json[type] = F_(number);
break;
@@ -1700,6 +1777,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
json["index"] = value_b ? 1 : 0;
Mqtt::add_value_bool(json, value, value_b);
}
json[type] = ("boolean");
break;
@@ -1707,6 +1785,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = serialized(Helpers::render_value(val, *(uint32_t *)(dv.value_p), dv.numeric_operator));
}
json[type] = F_(number);
break;
@@ -1715,6 +1794,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
char s[55];
json[value] = Helpers::render_string(s, (char *)(dv.value_p), sizeof(s));
}
json[type] = ("string");
break;
@@ -1730,6 +1810,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
} else {
json[type] = F_(command);
}
break;
default:
@@ -1773,11 +1854,13 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
if (tag >= 0 && tag != dv.tag) {
continue;
}
// only count supported types
if (dv.hasValue() && is_supported_type(dv.type)) {
entity_count++;
}
}
result.reserve(160 * entity_count);
for (auto & dv : devicevalues_) {
@@ -1799,30 +1882,35 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
has_value = true;
metric_value = (bool)*(uint8_t *)(dv.value_p) ? 1.0 : 0.0;
}
break;
case DeviceValueType::UINT8:
if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
has_value = true;
metric_value = *(uint8_t *)(dv.value_p);
}
break;
case DeviceValueType::INT8:
if (Helpers::hasValue(*(int8_t *)(dv.value_p))) {
has_value = true;
metric_value = *(int8_t *)(dv.value_p);
}
break;
case DeviceValueType::UINT16:
if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
has_value = true;
metric_value = *(uint16_t *)(dv.value_p);
}
break;
case DeviceValueType::INT16:
if (Helpers::hasValue(*(int16_t *)(dv.value_p))) {
has_value = true;
metric_value = *(int16_t *)(dv.value_p);
}
break;
case DeviceValueType::UINT24:
case DeviceValueType::UINT32:
@@ -1831,12 +1919,14 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
has_value = true;
metric_value = *(uint32_t *)(dv.value_p);
}
break;
case DeviceValueType::ENUM:
if (*(uint8_t *)(dv.value_p) < dv.options_size) {
has_value = true;
metric_value = *(uint8_t *)(dv.value_p);
}
break;
default:
break;
@@ -1890,8 +1980,10 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
if (i > 0) {
enum_mapping += "; ";
}
enum_mapping += std::to_string(i) + ": " + std::string(Helpers::translated_word(dv.options[i]));
}
enum_mapping += ")";
} else if (dv.uom != DeviceValueUOM::NONE) {
uom_str = uom_to_string(dv.uom);
@@ -1901,6 +1993,7 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
if (!uom_str.empty()) {
help_line += ", " + uom_str;
}
if (!enum_mapping.empty()) {
help_line += ", enum, " + enum_mapping;
}
@@ -1912,9 +2005,11 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
if (readable) {
help_line += ", readable";
}
if (writeable) {
help_line += ", writeable";
}
if (visible) {
help_line += ", visible";
}
@@ -1940,6 +2035,7 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
if (!circuit_label.empty()) {
result += "{circuit=\"" + circuit_label + "\"}";
}
result += " ";
char val_str[30];
@@ -1959,6 +2055,7 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
} else {
snprintf(val_str, sizeof(val_str), "%.2f", final_value);
}
result += val_str;
result += "\n";
}
@@ -2092,6 +2189,7 @@ bool EMSdevice::generate_values(JsonObject output, const int8_t tag_filter, cons
if (dv.numeric_operator == DeviceValueNumOp::DV_NUMOP_DIV60) {
time_value /= 60; // sometimes we need to divide by 60
}
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
char time_s[60];
snprintf(time_s,
@@ -2125,6 +2223,7 @@ bool EMSdevice::generate_values(JsonObject output, const int8_t tag_filter, cons
if (fahrenheit) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
if (v < dv.min) {
dv.min = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
@@ -2174,6 +2273,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
} else {
dv.add_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
}
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
count++;
}
@@ -2205,9 +2305,11 @@ void EMSdevice::mqtt_ha_entity_config_create() {
if ((d.tag != dv.tag) || (d.type != DeviceValueType::ENUM)) {
continue;
}
if (!strcmp(d.short_name, FL_(mode)[0]) && (d.options_size > 0)) {
mode_options = d.options;
}
if (!strcmp(d.short_name, FL_(icon)[0])) {
uint8_t val = *(uint8_t *)(d.value_p);
if (val != 0 && val < d.options_size) {
@@ -2215,6 +2317,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
}
}
}
Mqtt::publish_ha_climate_config(dv, true, mode_options, false, icon);
count++;
}
@@ -2250,6 +2353,7 @@ bool EMSdevice::has_telegram_id(uint16_t id) const {
return true;
}
}
return false;
}
@@ -2281,6 +2385,7 @@ bool EMSdevice::handle_telegram(const std::shared_ptr<const Telegram> & telegram
tf.process_function_(this, telegram);
return true;
}
// if the data block is empty and we have not received data before, assume that this telegram
// is not recognized by the bus master. So remove it from the automatic fetch list
if (telegram->message_length == 0 && telegram->offset == 0 && !tf.received_) {
@@ -2292,8 +2397,10 @@ bool EMSdevice::handle_telegram(const std::shared_ptr<const Telegram> & telegram
if (uuid::get_uptime_sec() > FETCH_RETRY_DURATION_SEC) {
tf.fetch_ = false;
}
return false;
}
if (telegram->message_length > 0) {
tf.received_ = true;
tf.process_function_(this, telegram);
@@ -2302,6 +2409,7 @@ bool EMSdevice::handle_telegram(const std::shared_ptr<const Telegram> & telegram
return true;
}
}
return false; // type not found
}
@@ -2332,6 +2440,7 @@ std::string EMSdevice::name() {
if (model().empty()) {
return default_name();
}
return model() + "/" + std::string(default_name());
}
@@ -2442,6 +2551,7 @@ int EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname,
if (modbus_data.size() != 2) {
return -2;
}
jsonValue["value"] = modbus_data[0] || modbus_data[1];
}
+7
View File
@@ -120,12 +120,15 @@ class EMSdevice {
inline void add_flags(uint8_t flags) {
flags_ |= flags;
}
inline bool has_flags(uint8_t flags) const {
return (flags_ & flags) == flags;
}
inline void remove_flags(uint8_t flags) {
flags_ &= ~flags;
}
inline uint8_t flags() const {
return flags_;
}
@@ -147,6 +150,7 @@ class EMSdevice {
const std::string & custom_brand() const {
return custom_brand_;
}
// set device model
void model(std::string const & model) {
model_ = model;
@@ -240,6 +244,7 @@ class EMSdevice {
publish_value((void *)&value);
return;
}
++i;
}
}
@@ -388,6 +393,7 @@ class EMSdevice {
bool ha_config_done() const {
return ha_config_done_;
}
void ha_config_done(const bool v) {
ha_config_done_ = v;
}
@@ -571,6 +577,7 @@ class EMSdevice {
, fetch_(fetch) {
}
};
void dump_telegram_info(std::vector<TelegramFunctionDump> & telegram_functions_dump);
void dump_devicevalue_info();
#endif
+11
View File
@@ -326,6 +326,7 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, uint32_t & dv_set_max) {
} else {
dv_set_max = Helpers::transformNumFloat(EMS_VALUE_UINT8_NOTSET - 1, numeric_operator, fahrenheit);
}
return true;
}
@@ -337,6 +338,7 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, uint32_t & dv_set_max) {
dv_set_min = Helpers::transformNumFloat(-EMS_VALUE_INT8_NOTSET + 1, numeric_operator, fahrenheit);
dv_set_max = Helpers::transformNumFloat(EMS_VALUE_INT8_NOTSET - 1, numeric_operator, fahrenheit);
}
return true;
}
@@ -359,11 +361,14 @@ bool DeviceValue::get_custom_min(int16_t & val) {
if (fahrenheit) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
if (max > 0 && v > 0 && (uint32_t)v > max) {
return false;
}
val = v;
}
return has_min;
}
@@ -378,11 +383,14 @@ bool DeviceValue::get_custom_max(uint32_t & val) {
if (fahrenheit) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
if (v < 0 || v < (int32_t)min) {
return false;
}
val = v;
}
return has_max;
}
@@ -417,6 +425,7 @@ std::string DeviceValue::get_custom_fullname() const {
if (minmax_pos != std::string::npos) {
return cf.substr(0, minmax_pos);
}
return cf;
}
@@ -427,6 +436,7 @@ std::string DeviceValue::get_fullname() const {
if (customname.empty()) {
return Helpers::translated_word(fullname);
}
return customname;
}
@@ -436,6 +446,7 @@ std::string DeviceValue::get_name(const std::string & entity) {
if (pos != std::string::npos) {
return entity.substr(2, pos - 2);
}
return entity.substr(2);
}
+3
View File
@@ -236,12 +236,15 @@ class DeviceValue {
void add_state(uint8_t s) {
state |= s;
}
bool has_state(uint8_t s) const {
return (state & s) == s;
}
void remove_state(uint8_t s) {
state &= ~s;
}
uint8_t get_state() const {
return state;
}
+62
View File
@@ -120,6 +120,7 @@ void EMSESP::fetch_device_values(const uint8_t device_id) {
for (const auto & emsdevice : emsdevices) {
emsdevice->fetch_values();
}
return;
}
@@ -144,6 +145,7 @@ bool EMSESP::valid_device(const uint8_t device_id) {
return true;
}
}
return false; // can't find it
}
@@ -170,6 +172,7 @@ bool EMSESP::cmd_is_readonly(const uint8_t device_type, const uint8_t device_id,
return emsdevice->is_readonly(cmd, id);
}
}
return false;
}
@@ -183,6 +186,7 @@ uint8_t EMSESP::device_id_from_cmd(const uint8_t device_type, const char * cmd,
return emsdevice->device_id();
}
}
return 0;
}
@@ -202,6 +206,7 @@ void EMSESP::clear_all_devices() {
for (const auto & emsdevice : emsdevices) {
emsdevice->erase_device_values();
}
emsdevices.clear();
}
@@ -221,6 +226,7 @@ void EMSESP::compact_entities_if_stable() {
if (!entity_compaction_pending_) {
return; // nothing to do (cheap early-out on the hot path)
}
if ((uuid::get_uptime() - last_entity_change_) < ENTITY_COMPACT_DELAY) {
return; // still settling
}
@@ -230,6 +236,7 @@ void EMSESP::compact_entities_if_stable() {
emsdevice->compact();
}
}
Command::compact();
entity_compaction_pending_ = false;
@@ -248,6 +255,7 @@ uint8_t EMSESP::count_devices() {
count++;
}
}
return count;
}
@@ -348,6 +356,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
} else if ((it.telegram_->operation) == Telegram::Operation::TX_WRITE) {
op = "WRITE";
}
shell.printfln(" [%02d%c] %s %s", it.id_, ((it.retry_) ? '*' : ' '), op.c_str(), pretty_telegram(it.telegram_).c_str());
}
}
@@ -385,10 +394,12 @@ 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;
}
// For MX400 SRC plus base
if (device.device_type == DeviceType::CONNECT && device.product_id == 17) {
device_id = EMSdevice::EMS_DEVICE_ID_RFBASE;
@@ -510,6 +521,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
shell.printfln(" %s: %s%s %s%s", key.c_str(), COLOR_BRIGHT_GREEN, p.value().as<std::string>().c_str(), uom.c_str(), COLOR_RESET);
}
shell.println();
}
}
@@ -524,6 +536,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
for (JsonPair p : custom_output) {
shell.printfln(" %s: %s%s%s", p.key().c_str(), COLOR_BRIGHT_GREEN, p.value().as<std::string>().c_str(), COLOR_RESET);
}
shell.println();
}
}
@@ -555,6 +568,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
sensor.is_system() ? "Yes" : "No");
}
}
shell.println();
}
@@ -588,6 +602,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
break;
}
}
shell.println();
}
}
@@ -650,6 +665,7 @@ void EMSESP::publish_all_loop() {
if (Mqtt::ha_enabled()) {
Mqtt::ha_status();
}
system_.send_heartbeat(); // send MQTT heartbeat topic
break;
default:
@@ -698,9 +714,11 @@ void EMSESP::publish_device_values(uint8_t device_type) {
json_tag = doc[EMSdevice::tag_to_mqtt(tag)].to<JsonObject>();
nest_created = true;
}
need_publish |= emsdevice->generate_values(json_tag, tag, false, EMSdevice::OUTPUT_TARGET::MQTT);
}
}
if (need_publish && !nested) {
Mqtt::queue_publish(Mqtt::tag_to_topic(device_type, tag), json);
json = doc.to<JsonObject>();
@@ -712,6 +730,7 @@ void EMSESP::publish_device_values(uint8_t device_type) {
if (doc.overflowed()) {
LOG_WARNING("MQTT buffer overflow, please use individual topics");
}
Mqtt::queue_publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
}
@@ -769,6 +788,7 @@ void EMSESP::publish_response(const std::shared_ptr<const Telegram> & telegram)
delete[] buffer;
buffer = nullptr;
}
if (buffer == nullptr) {
offset = telegram->offset; // store offset from first part
type = telegram->type_id;
@@ -778,17 +798,21 @@ void EMSESP::publish_response(const std::shared_ptr<const Telegram> & telegram)
buffer[i * 3 + 1] = '0';
buffer[i * 3 + 2] = ' ';
}
buffer[267] = '\0';
}
if (telegram->message_length) {
strlcpy(&buffer[(telegram->offset - offset) * 3], Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str(), 768);
} else {
strlcpy(&buffer[(telegram->offset - offset) * 3], "", 768);
}
if (response_id_ != 0) {
buffer[strlen(buffer)] = ' '; // overwrite termination \0
return; // do not delete buffer
}
JsonDocument doc;
char s[10];
doc["src"] = Helpers::hextoa(s, telegram->src);
@@ -801,6 +825,7 @@ void EMSESP::publish_response(const std::shared_ptr<const Telegram> & telegram)
uint32_t value = Helpers::hextoint(buffer);
doc["value"] = value;
}
Mqtt::queue_publish("response", doc.as<JsonObject>());
delete[] buffer;
buffer = nullptr;
@@ -1061,6 +1086,7 @@ void EMSESP::process_UBADevices(const std::shared_ptr<const Telegram> & telegram
LOG_DEBUG("Remove EMS device with ID 0x%02X", device_id);
erase_device(device_id); //
}
next_byte = next_byte >> 1; // advance 1 bit
}
}
@@ -1072,6 +1098,7 @@ void EMSESP::process_deviceName(const std::shared_ptr<const Telegram> & telegram
if (telegram->offset > 27 || (telegram->offset + telegram->message_length) < 29) {
return;
}
char name[16];
// len including zero terminator, if there is one, otherwise copy to end of telegram
// https://github.com/emsesp/EMS-ESP32/discussions/2482#discussioncomment-12649817
@@ -1081,6 +1108,7 @@ void EMSESP::process_deviceName(const std::shared_ptr<const Telegram> & telegram
while (isprint(*c)) {
c++;
};
*c = '\0';
if (strlen(name) > 2) { // https://github.com/emsesp/EMS-ESP32/issues/2166#issuecomment-2454488657
LOG_DEBUG("Model name received for device 0x%02X: %s", telegram->src, name);
@@ -1177,17 +1205,21 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
if (!trace_raw_) {
LOG_TRACE("%s", pretty_telegram(telegram).c_str());
}
if (!read_next_) {
response_id_ = 0;
}
publish_response(telegram);
} else {
LOG_NOTICE("%s", pretty_telegram(telegram).c_str());
}
// check if read is finished or gives more parts
if (!read_next_) {
read_id_ = WATCH_ID_NONE;
}
read_next_ = false;
} else if (watch() == WATCH_ON) {
if ((watch_id_ == WATCH_ID_NONE) || (telegram->type_id == watch_id_)
@@ -1226,6 +1258,7 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
if (!wait_km_) {
process_UBADevices(telegram);
}
return true;
}
@@ -1252,6 +1285,7 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
found_device->has_update(false); // reset flag
publish_device_values(found_device->device_type()); // publish to MQTT if we explicitly have too
}
// auto publish: timeinterval 0 and publish single not set, only if queue is empty
else if (mqtt_.get_publish_onchange(found_device->device_type()) && found_device->has_update() && mqtt_.publish_queued() == 0) {
found_device->has_update(false); // reset flag
@@ -1260,6 +1294,7 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
}
}
}
break; // remove this to handle same telegrams on multiple devices
}
}
@@ -1271,12 +1306,14 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
if (found_device && telegram->message_length > 0) {
found_device->add_handlers_ignored(telegram->type_id);
}
// handle unknown broadcasted telegrams (or send to us)
if (telegram->dest == 0 || telegram->dest == EMSbus::ems_bus_id()) {
LOG_DEBUG("No telegram type handler found for ID 0x%02X (src 0x%02X)", telegram->type_id, telegram->src);
if (watch() == WATCH_UNKNOWN) {
LOG_NOTICE("%s", pretty_telegram(telegram).c_str());
}
if (!wait_km_ && !found_device && (telegram->src != EMSbus::ems_bus_id()) && (telegram->message_length > 0)) {
#ifdef EMSESP_GATEWAY_TX_HOLD
// a new device often sends several broadcasts before its version reply is processed, only ask once
@@ -1291,6 +1328,7 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
}
}
}
return telegram_found;
}
@@ -1300,6 +1338,7 @@ bool EMSESP::device_hasEntities(const uint8_t device_id) {
return true;
}
}
return false; // not found
}
@@ -1381,12 +1420,15 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
if (product_id == 0) { // no product-id, ignore
return false;
}
if ((*it)->product_id() == product_id) { // update version if we have valid product_id
if (!strcmp((*it)->version(), "00.00")) {
(*it)->version(version);
}
return true;
}
// product-id has changed for this device-id, delete and re-add
(*it)->erase_device_values();
emsdevices.erase(it); // erase the old device without product_id and re detect
@@ -1406,6 +1448,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
device_p = &device;
break;
}
if (device_id >= EMSdevice::EMS_DEVICE_ID_HS1 && device_id <= EMSdevice::EMS_DEVICE_ID_HS16) {
device_p = &device;
device_p->device_type = DeviceType::HEATSOURCE;
@@ -1442,11 +1485,13 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
default_name = "Wireless sensor base";
}
}
// map MX400 also to RF Base
if (device_id == EMSdevice::EMS_DEVICE_ID_RFBASE) {
device_type = DeviceType::CONNECT;
default_name = "Wireless base";
}
if ((device_id >= EMSdevice::EMS_DEVICE_ID_DHW1 && device_id <= EMSdevice::EMS_DEVICE_ID_DHW8) || device_id == EMSdevice::EMS_DEVICE_ID_IPM_DHW) {
device_type = DeviceType::WATER;
}
@@ -1463,6 +1508,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
flags = DeviceFlags::EMS_DEVICE_FLAG_CR120;
default_name = "CR120";
}
if (product_id == 158 && strncmp(version, "73", 2) == 0) {
flags = DeviceFlags::EMS_DEVICE_FLAG_HMC310;
default_name = "HMC310";
@@ -1628,6 +1674,7 @@ void EMSESP::reply_own_request(const uint8_t * data, const uint8_t length, const
EMSuart::transmit(reply, 7);
LOG_DEBUG("Empty reply to 0x%02X read 0x%02X%02X", reply[1], data[5], data[6]);
}
return;
}
@@ -1730,6 +1777,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
if (poll_id != 0x48 && (uuid::get_uptime_sec() - connect_time) < EMS_WAIT_KM_TIMEOUT) {
return;
}
wait_km_ = false; // KM200 is polled, from now on it is safe to send
}
@@ -1797,6 +1845,7 @@ void EMSESP::scheduled_fetch_values() {
return;
}
}
webCustomEntityService.fetch();
no = 0;
}
@@ -1809,6 +1858,7 @@ void EMSESP::gateway_rx_check(const uint8_t * data, const uint8_t length) {
if (length < 4 || (data[0] & 0x7F) != EMSdevice::EMS_DEVICE_ID_GATEWAY1 || !(data[1] & 0x80)) {
return;
}
uint8_t dest = data[1] & 0x7F;
if (dest == 0 || dest == EMSbus::ems_bus_id()) {
return;
@@ -1819,10 +1869,12 @@ void EMSESP::gateway_rx_check(const uint8_t * data, const uint8_t length) {
gateway_window_start_ = now;
gateway_window_count_ = 0;
}
if (now - gateway_fast_start_ > GATEWAY_FAST_WINDOW) {
gateway_fast_start_ = now;
gateway_fast_count_ = 0;
}
++gateway_window_count_;
++gateway_fast_count_;
if (gateway_window_count_ >= GATEWAY_BURST_THRESHOLD || gateway_fast_count_ >= GATEWAY_FAST_THRESHOLD) {
@@ -1840,12 +1892,15 @@ void EMSESP::gateway_tx_hold_start() {
if ((int32_t)(now - gateway_cooldown_until_) < 0) {
return;
}
gateway_cooldown_until_ = 0;
}
if (!gateway_hold_until_) {
gateway_hold_start_ = now;
LOG_INFO("Gateway 0x%02X is busy, holding Tx", EMSdevice::EMS_DEVICE_ID_GATEWAY1);
}
gateway_hold_until_ = (now + GATEWAY_HOLD_TIME) | 1; // 0 means no hold
}
@@ -1853,6 +1908,7 @@ bool EMSESP::gateway_tx_hold() {
if (!gateway_hold_until_) {
return false;
}
uint32_t now = uuid::get_uptime();
if (now - gateway_hold_start_ > GATEWAY_HOLD_MAX) {
gateway_hold_until_ = 0;
@@ -1860,11 +1916,13 @@ bool EMSESP::gateway_tx_hold() {
LOG_WARNING("Gateway 0x%02X still busy after %lu s, resuming Tx", EMSdevice::EMS_DEVICE_ID_GATEWAY1, (unsigned long)(GATEWAY_HOLD_MAX / 1000));
return false;
}
if ((int32_t)(now - gateway_hold_until_) >= 0) {
gateway_hold_until_ = 0;
LOG_INFO("Gateway 0x%02X is quiet, resuming Tx after %lu s", EMSdevice::EMS_DEVICE_ID_GATEWAY1, (unsigned long)((now - gateway_hold_start_) / 1000));
return false;
}
return true;
}
#endif
@@ -2044,6 +2102,7 @@ void EMSESP::start() {
force_analog_sensors = settings.analogCustomizations.empty();
force_temperature_sensors = settings.sensorCustomizations.empty();
};
webCustomizationService.read(check_empty_customizations);
}
}
@@ -2052,6 +2111,7 @@ void EMSESP::start() {
device_library_ = {
#include "device_library.h"
};
LOG_INFO("Library loaded: %d EMS devices, %d device entities, %s", device_library_.size(), EMSESP_TRANSLATION_COUNT, system_.languages_string().c_str());
// start telnet service if it's enabled
@@ -2192,10 +2252,12 @@ void EMSESP::loop() {
shell_prompt();
show_prompt = false; // only show it once
}
int c = serial_console_.read();
if (c != -1) {
show_prompt = true;
}
// https://daleswanson.org/ascii.htm#:~:text=0
// CTRL-D to open
if (c == '\x04') {
+3
View File
@@ -196,6 +196,7 @@ class EMSESP {
static uint8_t watch() {
return watch_;
}
static void set_read_id(uint16_t id) {
read_id_ = id;
}
@@ -211,6 +212,7 @@ class EMSESP {
static bool wait_validate() {
return (wait_validate_ != 0);
}
static void wait_validate(uint16_t wait) {
wait_validate_ = wait;
}
@@ -328,6 +330,7 @@ class EMSESP {
const char * default_name;
uint8_t flags;
};
static std::vector<Device_record, AllocatorPSRAM<Device_record>> device_library_;
static uint16_t watch_id_;
+5
View File
@@ -165,16 +165,20 @@ class Network {
if (!desc) {
return NetIface::NONE;
}
if (strcmp(desc, "sta") == 0) {
return NetIface::WIFI;
}
if (strcmp(desc, "ap") == 0) {
return NetIface::AP;
}
// any "eth*" (eth, eth0, eth1, ...) is treated as Ethernet
if (strncmp(desc, "eth", 3) == 0) {
return NetIface::ETHERNET;
}
return NetIface::NONE;
}
@@ -190,6 +194,7 @@ class Network {
default:
return "None";
}
return "unknown";
}
+1
View File
@@ -58,6 +58,7 @@ class EMSFactory {
if (auto * ptr = makeRaw(device_type, device_id, product_id, version, default_name, flags, brand)) {
return std::unique_ptr<EMSdevice>(ptr);
}
return nullptr;
}
+7
View File
@@ -55,9 +55,11 @@ static int compare_prerelease(const std::string & a, const std::string & b) {
if (a == b) {
return 0;
}
if (a.empty()) {
return 1; // release > prerelease
}
if (b.empty()) {
return -1;
}
@@ -70,9 +72,11 @@ static int compare_prerelease(const std::string & a, const std::string & b) {
if (ea == std::string::npos) {
ea = a.size();
}
if (eb == std::string::npos) {
eb = b.size();
}
std::string id_a = a.substr(ia, ea - ia);
std::string id_b = b.substr(ib, eb - ib);
@@ -102,9 +106,11 @@ static int compare_prerelease(const std::string & a, const std::string & b) {
if (ia < a.size()) {
return 1;
}
if (ib < b.size()) {
return -1;
}
return 0;
}
@@ -144,6 +150,7 @@ void FirmwareVersion::parse(const char * s) {
if (s == nullptr || *s == '\0') {
return;
}
// parse numeric major.minor.patch; accept partial ("3", "3.9", "3.9.0")
sscanf(s, "%d.%d.%d", &major_, &minor_, &patch_);
// capture prerelease tag after '-' if present (stop at '+' which is build metadata)
+40
View File
@@ -53,6 +53,7 @@ char * Helpers::hextoa(char * result, const uint16_t value) {
if (value <= 0xFF) {
return hextoa(result, (uint8_t)value);
}
hextoa(result, (uint8_t)(value >> 8));
hextoa(&result[2], (uint8_t)(value & 0xFF));
return result;
@@ -201,6 +202,7 @@ char * Helpers::render_boolean(char * result, const bool value, const bool dashb
} else {
strlcpy(result, value ? "true" : "false", 7); // default
}
return result;
}
@@ -209,10 +211,12 @@ char * Helpers::remove_trailing_zero(char * result) {
while (result[strlen(result) - 1] == '0') {
result[strlen(result) - 1] = '\0';
}
if (result[strlen(result) - 1] == '.') {
result[strlen(result) - 1] = '\0';
}
}
return result;
}
@@ -228,6 +232,7 @@ char * Helpers::render_value(char * result, uint8_t value, int8_t format, const
} else {
render_boolean(result, true); // assume on. could have value 0x01 or 0xFF
}
return result;
}
@@ -301,6 +306,7 @@ char * Helpers::render_value(char * result, const double value, const int8_t for
*result++ = '0'; // add leading zeros
}
}
itoa(decimal, result, 10);
return remove_trailing_zero(ret);
@@ -376,6 +382,7 @@ char * Helpers::render_value(char * result, const uint32_t value, const int8_t f
if (!hasValue(value)) {
return nullptr;
}
result[0] = '\0';
uint32_t new_value = fahrenheit ? format ? value * 1.8 + 32 * format * (fahrenheit - 1) : value * 1.8 + 32 * (fahrenheit - 1) : value;
char s[14] = {0};
@@ -447,9 +454,11 @@ char * Helpers::render_string(char * result, const char * c, const uint8_t len)
*p = (*c & 0x80) ? '?' : *c;
break;
}
c++;
p++;
}
*p = '\0'; // terminate result
return result;
}
@@ -489,12 +498,15 @@ char * Helpers::utf8tolatin1(char * result, const char * c, const uint8_t len) {
} else {
*p = *c;
}
c++;
p++;
}
*p = '\0'; // terminate result
return result;
}
// creates string of hex values from an array of bytes
std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
if (length == 0) {
@@ -512,6 +524,7 @@ std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
*p++ = buffer[1];
*p++ = ' '; // space
}
*--p = '\0'; // null terminate just in case, loosing the trailing space
return std::string(str.data());
@@ -559,13 +572,16 @@ int Helpers::atoint(const char * value) {
if (s == '-') {
++value;
}
while (*value >= '0' && *value <= '9') {
x = (x * 10) + (*value - '0');
++value;
}
if (s == '-') {
return (-x);
}
return x;
}
@@ -603,6 +619,7 @@ bool Helpers::hasValue(const uint8_t & value, const uint8_t isBool) {
if (isBool == EMS_VALUE_BOOL) {
return (value != EMS_VALUE_BOOL_NOTSET);
}
return (value != EMS_VALUE_UINT8_NOTSET);
}
@@ -647,6 +664,7 @@ bool Helpers::value2number(const char * value, int & value_i, const int min, con
if (value_i >= min && value_i <= max) {
return true;
}
return false;
}
@@ -666,6 +684,7 @@ bool Helpers::value2float(const char * value, float & value_f) {
value_f = atof(value + 1);
return true;
}
return false;
}
@@ -674,8 +693,10 @@ bool Helpers::value2temperature(const char * value, float & value_f, bool relati
if (EMSESP::system_.fahrenheit()) {
value_f = relative ? (value_f / 1.8) : (value_f - 32) / 1.8;
}
return true;
}
return false;
}
@@ -684,8 +705,10 @@ bool Helpers::value2temperature(const char * value, int & value_i, const bool re
if (EMSESP::system_.fahrenheit()) {
value_i = relative ? (value_i / 1.8) : (value_i - 32) / 1.8;
}
return true;
}
return false;
}
@@ -735,6 +758,7 @@ bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * co
if ((value == nullptr) || (strlen(value) == 0)) {
return false;
}
std::string str = toLower(value);
for (value_ui = 0; strs[value_ui]; value_ui++) {
@@ -746,6 +770,7 @@ bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * co
return true;
}
}
value_ui = 0;
return false;
}
@@ -755,6 +780,7 @@ bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * co
if (!value2enum(value, v, strs) || v >= mask.size()) {
return false;
}
value_ui = mask[v];
return true;
}
@@ -766,6 +792,7 @@ bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * co
if ((value == nullptr) || (strlen(value) == 0)) {
return false;
}
std::string str = toLower(value);
std::string s_on = Helpers::translated_word(FL_(on));
@@ -791,6 +818,7 @@ bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * co
if (!value2enum(value, v, strs) || v >= mask.size()) {
return false;
}
value_ui = mask[v];
return true;
}
@@ -829,6 +857,7 @@ void Helpers::CharToUpperUTF8(char * c) {
if ((p_v >= (char)0xA0) && (p_v <= (char)0xBE)) {
*p -= 0x20;
}
// Additional special characters for supported languages
switch (p_v) {
case (char)0xA0: // à -> À
@@ -865,6 +894,7 @@ void Helpers::CharToUpperUTF8(char * c) {
*p -= 0x20;
break;
}
break;
case (char)0xC4:
switch (p_v) {
@@ -881,6 +911,7 @@ void Helpers::CharToUpperUTF8(char * c) {
*p -= 1;
break;
}
break;
case (char)0xC5:
switch (p_v) {
@@ -903,6 +934,7 @@ void Helpers::CharToUpperUTF8(char * c) {
*p -= 1;
break;
}
break;
default:
*c = toupper(*c); // works on Latin1 letters
@@ -939,6 +971,7 @@ uint8_t Helpers::count_items(const char * const * list) {
list_size++;
}
}
return list_size;
}
@@ -951,6 +984,7 @@ uint8_t Helpers::count_items(const char * const ** list) {
list_size++;
}
}
return list_size;
}
@@ -983,19 +1017,23 @@ uint16_t Helpers::string2minutes(const std::string & str) {
if (str[i] >= '0' && str[i] <= '9') {
tmp = tmp * 10 + str[i] - '0';
}
// Or if we got a colon
else if (str[i] == ':') {
// If we were reading the hours
if (state == 0) {
res = 60 * tmp;
}
// Or if we were reading the minutes
else if (state == 1) {
if (tmp > 60) {
return 0;
}
res += tmp;
}
// Or we got an extra colon
else {
return 0;
@@ -1009,6 +1047,7 @@ uint16_t Helpers::string2minutes(const std::string & str) {
else {
return 0;
}
i++;
}
@@ -1034,6 +1073,7 @@ int16_t Helpers::calc_dew(int16_t temp, uint8_t humi) {
if (humi == EMS_VALUE_UINT8_NOTSET || temp == EMS_VALUE_INT16_NOTSET) {
return EMS_VALUE_INT16_NOTSET;
}
const float k2 = 17.62;
const float k3 = 243.12;
const float t = (float)temp / 10;
+12
View File
@@ -69,6 +69,7 @@ int HttpClient::request(std::string url, const std::string & method, const std::
ssl_client->setBufferSizes(TLS_RX_BUFFER_SIZE, TLS_TX_BUFFER_SIZE);
ssl_client->setSessionTimeout(TLS_SESSION_TIMEOUT_S);
}
// WiFiClient is NetworkClient, which declares no setTimeout() of its own - calling it binds to
// Stream::setTimeout() and only affects readBytes(), leaving the socket on the core's 3s default
basic_client->setConnectionTimeout(CONNECT_TIMEOUT_MS);
@@ -89,17 +90,21 @@ int HttpClient::request(std::string url, const std::string & method, const std::
agent_set |= (key == "user-agent");
req += std::string(p.key().c_str()) + ": " + p.value().as<std::string>() + "\r\n";
}
if (!agent_set) {
req += "User-Agent: EMS-ESP\r\n"; // CDNs are more likely to stall on a request without one
}
if (is_post) {
if (!content_set) {
req += "Content-Type: ";
req += value.starts_with('{') ? asyncsrv::T_application_json : asyncsrv::T_text_plain;
req += "\r\n";
}
req += "Content-Length: " + std::to_string(value.length()) + "\r\n";
}
req += "Connection: close\r\n\r\n"; // the blank line terminates the headers - without it the server never responds
ssl_client->print(req.c_str());
@@ -120,6 +125,7 @@ int HttpClient::request(std::string url, const std::string & method, const std::
truncated = true;
break;
}
uint8_t buf[128];
const size_t want = (avail < (int)sizeof(buf)) ? (size_t)avail : sizeof(buf);
const int len = ssl_client->read(buf, want);
@@ -128,20 +134,25 @@ int HttpClient::request(std::string url, const std::string & method, const std::
last_data = millis();
continue;
}
// available() reported bytes the client won't hand over. Yielding here keeps a
// stale count from busy-spinning the whole read budget on this task's core
delay(1);
continue;
}
if (!ssl_client->connected()) {
break; // closed, with nothing left buffered
}
// the server may take a while to start replying
if (millis() - last_data > (result.empty() ? FIRST_BYTE_TIMEOUT_MS : IDLE_TIMEOUT_MS)) {
break;
}
delay(1);
}
ssl_client->stop();
const auto received = result.length();
@@ -170,6 +181,7 @@ int HttpClient::request(std::string url, const std::string & method, const std::
} else {
EMSESP::logger().warning("%s connection to %s failed", is_https ? "HTTPS" : "HTTP", host.c_str());
}
delete ssl_client;
delete basic_client;
+9 -1
View File
@@ -59,6 +59,7 @@ Channel * init(uint8_t pin) {
if (ch.pin == pin) {
return &ch;
}
if (!free_slot && ch.pin == NO_PIN) {
free_slot = &ch;
}
@@ -73,12 +74,14 @@ Channel * init(uint8_t pin) {
free_slot = &ch;
}
}
end(free_slot->pin);
}
if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, RMT_FREQ_HZ)) {
return nullptr;
}
free_slot->pin = pin;
free_slot->last_tx_us = micros() - RESET_US; // no wait on first write
return free_slot;
@@ -188,6 +191,7 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) {
} else {
reset_led();
}
return false;
}
@@ -267,6 +271,7 @@ void LED::set_led(Color color) {
if (!led_gpio_ || color == last_color_) {
return;
}
last_color_ = color;
// RGB lookup table indexed by Color enum (must match enum order in led.h)
@@ -283,6 +288,7 @@ void LED::set_led(Color color) {
{0, B, B}, // CYAN
{H, 0, H} // PINK
};
static_assert(sizeof(rgb_table) / sizeof(rgb_table[0]) == Color::PINK + 1, "rgb_table must match the Color enum");
const uint8_t * rgb = rgb_table[color];
@@ -324,6 +330,7 @@ bool LED::set_custom_led_routine(const std::string & color, const std::string &
break;
}
}
if (!color_matched) {
return false;
}
@@ -393,6 +400,7 @@ void LED::sequence_led() {
is_user_led_blink_ = false;
previous_healthcheck_ = System::HEALTHCHECK_RESET; // this will force the healthcheck to be checked again
}
return;
}
@@ -414,4 +422,4 @@ void LED::start_led_fast_flash(uint8_t duration) {
led_fast_flash_timer_ = true; // it's active
}
} // namespace emsesp
} // namespace emsesp
+16
View File
@@ -72,21 +72,27 @@ constexpr uint32_t TYPE_CCRC = 0x500;
constexpr bool tag_isvalid(uint32_t t) {
return (t & 0x80000000) == 0;
}
constexpr uint32_t tag_type2(uint32_t t) {
return (t & 0x78000000) >> 20;
}
constexpr uint32_t tag_type3(uint32_t t) {
return (t & 0x7ff00000) >> 20;
}
constexpr uint32_t tag_chunk(uint32_t t) {
return (t & 0x0ff00000) >> 20;
}
constexpr uint32_t tag_id(uint32_t t) {
return (t & 0x000ffc00) >> 10;
}
constexpr uint32_t tag_size(uint32_t t) {
return t & 0x3ff;
}
// a size of 0x3ff marks a delete tag, which carries no payload
constexpr uint32_t tag_dsize(uint32_t t) {
return 4 + (tag_size(t) == 0x3ff ? 0 : tag_size(t));
@@ -95,9 +101,11 @@ constexpr uint32_t tag_dsize(uint32_t t) {
uint32_t rd_be32(const uint8_t * p) {
return (static_cast<uint32_t>(p[0]) << 24) | (static_cast<uint32_t>(p[1]) << 16) | (static_cast<uint32_t>(p[2]) << 8) | p[3];
}
uint32_t rd_le32(const uint8_t * p) {
return (static_cast<uint32_t>(p[3]) << 24) | (static_cast<uint32_t>(p[2]) << 16) | (static_cast<uint32_t>(p[1]) << 8) | p[0];
}
void wr_le32(uint8_t * p, uint32_t v) {
p[0] = static_cast<uint8_t>(v);
p[1] = static_cast<uint8_t>(v >> 8);
@@ -125,11 +133,13 @@ uint32_t lfs_crc(uint32_t crc, const void * buffer, size_t size) {
0xa00ae278,
0xbdbdf21c,
};
const uint8_t * data = static_cast<const uint8_t *>(buffer);
for (size_t i = 0; i < size; i++) {
crc = (crc >> 4) ^ rtable[(crc ^ data[i]) & 0xf];
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf];
}
return crc;
}
@@ -166,15 +176,18 @@ Located locate_superblock(const uint8_t * buf) {
if (!tag_isvalid(tag)) {
break; // nothing committed beyond this point
}
if (off + tag_dsize(tag) > BLOCK_SIZE) {
break;
}
ptag = tag;
if (tag_type2(tag) == TYPE_CCRC) {
if (crc != rd_le32(&buf[off + 4])) {
break; // commit never completed, so the chain ends here
}
if (pending) {
found.found = true;
found.struct_off = pending_off;
@@ -182,6 +195,7 @@ Located locate_superblock(const uint8_t * buf) {
found.crc_off = off + 4;
pending = false;
}
ptag ^= (tag_chunk(tag) & 1u) << 31;
crc = 0xffffffff;
commit_start = off + tag_dsize(tag); // the padding after the CRC is not covered
@@ -204,11 +218,13 @@ const esp_partition_t * find_partition(const char * partition_label) {
if (partition_label != nullptr) {
return esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, partition_label);
}
// 0x83 is the littlefs subtype, 0x82 the spiffs one that older partition tables use
const esp_partition_t * part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, static_cast<esp_partition_subtype_t>(0x83), nullptr);
if (part != nullptr) {
return part;
}
return esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, nullptr);
}
+8
View File
@@ -37,12 +37,14 @@ void Modbus::start(uint8_t systemServerId, uint16_t port, uint8_t max_clients, u
modbusServer_->registerWorker(i, WRITE_MULT_REGISTERS, [this](auto && request) { return handleWrite(request); });
}
}
modbusServer_->start(port, max_clients, timeout);
LOG_INFO("Starting Modbus service (ID %d, port %d)", systemServerId, port);
#else
if (!check_parameter_order()) {
LOG_ERROR("Unable to enable Modbus - the parameter list order is corrupt. This is a firmware bug.");
}
LOG_INFO("Modbus deactivated in standalone build.");
#endif
}
@@ -71,8 +73,10 @@ bool Modbus::check_parameter_order() {
LOG_ERROR("Error in modbus parameters: %s must be listed before %s.", mi.short_name, prev->short_name);
return false;
}
prev = &mi;
}
return true;
}
@@ -263,10 +267,12 @@ ModbusMessage Modbus::handleRead(const ModbusMessage & request) {
}
}
}
if (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;
}
@@ -361,6 +367,7 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) {
}
}
}
if (error_code) {
// error getting modbus value as json
LOG_ERROR("error getting modbus value as json, error code = %d", error_code);
@@ -397,6 +404,7 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) {
} else {
snprintf(error, sizeof(error), "Modbus write command failed with error code (%s)", Command::return_code_string(return_code));
}
LOG_ERROR(error);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_VALUE);
} else {
+41
View File
@@ -83,6 +83,7 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_
if (cb) {
mqtt_subfunction.mqtt_subfunction_ = cb;
}
return; // exit - don't add
}
}
@@ -237,6 +238,7 @@ void Mqtt::on_message(const char * topic, const uint8_t * payload, size_t len) {
queue_publish_message(topic, "", true);
LOG_DEBUG("Remove topic %s", topic);
}
return;
}
@@ -251,6 +253,7 @@ void Mqtt::on_message(const char * topic, const uint8_t * payload, size_t len) {
LOG_ERROR("error: invalid payload %s for this topic %s", message, topic);
Mqtt::queue_publish("response", "error: invalid data");
}
return;
}
}
@@ -282,6 +285,7 @@ void Mqtt::on_message(const char * topic, const uint8_t * payload, size_t len) {
} else {
snprintf(error, sizeof(error), "MQTT command failed with error %s", Command::return_code_string(return_code));
}
LOG_ERROR(error);
Mqtt::queue_publish("response", error);
} else {
@@ -351,6 +355,7 @@ void Mqtt::reset_mqtt() {
if (mqttClient_->connected()) {
mqttClient_->disconnect(true); // force a disconnect
}
load_settings(); // reload MQTT settings
}
@@ -401,6 +406,7 @@ void Mqtt::start() {
if (initialized_) {
return;
}
initialized_ = true;
// add the 'publish' command ('call system publish' in console or via API)
@@ -447,6 +453,7 @@ bool Mqtt::get_publish_onchange(uint8_t device_type) {
if (publish_single_ && !ha_enabled_) {
return false;
}
if (device_type == EMSdevice::DeviceType::BOILER) {
if (!publish_time_boiler_) {
return true;
@@ -470,6 +477,7 @@ bool Mqtt::get_publish_onchange(uint8_t device_type) {
} else if (!publish_time_other_) {
return true;
}
return false;
}
@@ -626,6 +634,7 @@ void Mqtt::ha_status() {
"bootTime",
DeviceValueUOM::UPTIME); // called Uptime in HA and derives value from bootTime element in info topic
}
// add sub or pub task to the queue.
// the base is not included in the topic
bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain) {
@@ -648,14 +657,17 @@ bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, con
mqtt_message_id_++;
mqtt_publish_fails_++;
}
LOG_WARNING("%s failed: low memory", operation == Operation::PUBLISH ? "Publish" : operation == Operation::SUBSCRIBE ? "Subscribe" : "Unsubscribe");
return false; // quit
}
if (queuecount_ >= MQTT_QUEUE_MAX_SIZE) {
if (operation == Operation::PUBLISH) {
mqtt_message_id_++;
mqtt_publish_fails_++;
}
LOG_WARNING("%s failed: queue full", operation == Operation::PUBLISH ? "Publish" : operation == Operation::SUBSCRIBE ? "Subscribe" : "Unsubscribe");
return false; // quit
}
@@ -758,6 +770,7 @@ bool Mqtt::queue_publish(const char * topic, const JsonObjectConst payload, cons
serializeJson(payload, payload_text); // convert json to string
return queue_publish_message(topic, payload_text, retain);
}
return false;
}
@@ -893,6 +906,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
snprintf(entity_with_tag, sizeof(entity_with_tag), "%sww", dhw_old[i]);
}
}
snprintf(uniq_id, sizeof(uniq_id), "%s_%s", device_name, entity_with_tag);
} else if (has_tag && device_type == EMSdevice::DeviceType::WATER && tag >= DeviceValue::DeviceValueTAG::TAG_DHW3) {
snprintf(entity_with_tag, sizeof(entity_with_tag), "wwc%d_%s", tag - DeviceValue::DeviceValueTAG::TAG_DHW1 + 1, entity);
@@ -913,6 +927,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
snprintf(entity_with_tag, sizeof(entity_with_tag), "%sww", dhw_old[i]);
}
}
snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", Mqtt::basename().c_str(), device_name, entity_with_tag);
} else if (has_tag && device_type == EMSdevice::DeviceType::WATER && tag >= DeviceValue::DeviceValueTAG::TAG_DHW3) {
snprintf(entity_with_tag, sizeof(entity_with_tag), "wwc%d_%s", tag - DeviceValue::DeviceValueTAG::TAG_DHW1 + 1, entity);
@@ -975,6 +990,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
if (discovery_type() == discoveryType::HOMEASSISTANT || discovery_type() == discoveryType::DOMOTICZ_LATEST) {
snprintf(topic, sizeof(topic), "number/%s", config_topic);
}
break;
case DeviceValueType::BOOL:
// switch - https://www.home-assistant.io/integrations/switch.mqtt
@@ -990,6 +1006,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
} else if (discovery_type() == discoveryType::HOMEASSISTANT || discovery_type() == discoveryType::DOMOTICZ_LATEST) {
snprintf(topic, sizeof(topic), "number/%s", config_topic);
}
break;
case DeviceValueType::STRING:
// text - https://www.home-assistant.io/integrations/text.mqtt
@@ -997,6 +1014,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
if (discovery_type() == discoveryType::HOMEASSISTANT) {
snprintf(topic, sizeof(topic), "text/%s", config_topic); // e.g. set_datetime, set_holiday, set_wwswitchtime
}
break;
default:
break;
@@ -1040,6 +1058,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
} else {
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", device_name, entity);
}
doc["cmd_t"] = command_topic;
// extend for enums and also commands, add options
@@ -1063,6 +1082,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
} else {
doc["mode"] = "box"; // auto, slider or box
}
if (num_op > 0) {
doc["step"] = 1.0 / num_op;
} else if (num_op < 0) {
@@ -1089,6 +1109,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
} else {
snprintf(ha_name, sizeof(ha_name), "%s", F_name); // no tag
}
free(F_name); // very important!
doc["name"] = ha_name;
@@ -1108,6 +1129,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
snprintf(stat_t, sizeof(stat_t), "~/%s", F_(info));
}
}
doc["stat_t"] = stat_t;
// value template
@@ -1141,6 +1163,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// default
doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
}
add_ha_avty_section(doc.as<JsonObject>(), stat_t, val_cond); // adds availability section
} else {
// Domoticz doesn't support value templates, so we just use the value directly
@@ -1180,6 +1203,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
if (device_type == EMSdevice::DeviceType::SYSTEM) {
doc["ent_cat"] = "diagnostic"; // instead of 'config'
}
if (type == DeviceValueType::BOOL) {
return;
}
@@ -1238,12 +1262,14 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
if (display_only) {
doc[ic_ha] = F_(iconpercent); // set icon
}
doc[sc_ha] = sc_ha_measurement;
if (entity == FL_(airHumidity)[0] || entity == FL_(remotehum)[0]) {
doc[dc_ha] = "humidity";
} else if (entity == FL_(battery)[0]) {
doc[dc_ha] = "battery";
}
break;
case DeviceValueUOM::SECONDS:
case DeviceValueUOM::MINUTES:
@@ -1253,12 +1279,14 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
} else {
doc[sc_ha] = sc_ha_measurement;
}
doc[dc_ha] = "duration"; // https://github.com/emsesp/EMS-ESP32/issues/822
break;
case DeviceValueUOM::KB:
if (display_only) {
doc[ic_ha] = F_(iconkb);
}
doc[dc_ha] = "data_size";
break;
case DeviceValueUOM::LMIN:
@@ -1266,6 +1294,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
if (display_only) {
doc[ic_ha] = F_(iconlmin);
}
doc[sc_ha] = sc_ha_measurement;
doc[dc_ha] = "volume_flow_rate";
break;
@@ -1280,6 +1309,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
} else {
doc[sc_ha] = sc_ha_measurement; // no device class for this
}
break;
case DeviceValueUOM::KWH:
doc[sc_ha] = sc_ha_total_increasing;
@@ -1289,6 +1319,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
if (display_only) {
doc[ic_ha] = F_(iconua);
}
doc[sc_ha] = sc_ha_measurement;
// doc[dc_ha] = "current"; // there is no uA in HA, only mA
break;
@@ -1354,6 +1385,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
if (display_only) {
doc[ic_ha] = 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
@@ -1363,6 +1395,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
doc[sc_ha] = sc_ha_measurement; // default to measurement
}
}
break;
}
}
@@ -1412,10 +1445,12 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
snprintf(currtemp_s, sizeof(currtemp_s), "value_json.%s%d.currtemp", tagname, hc_num);
snprintf(currtemp_cond, sizeof(currtemp_cond), "value_json.%s%d is defined and %s is defined", tagname, hc_num, currtemp_s);
}
if (tag >= DeviceValueTAG::TAG_SRC1) {
snprintf(currhum_s, sizeof(currhum_s), "value_json.%s%d.airhumidity", tagname, hc_num);
snprintf(currhum_cond, sizeof(currhum_cond), "value_json.%s%d is defined and %s is defined", tagname, hc_num, currhum_s);
}
snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE).c_str());
} else {
// single format
@@ -1428,10 +1463,12 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
snprintf(currtemp_s, sizeof(currtemp_s), "value_json.currtemp");
snprintf(currtemp_cond, sizeof(currtemp_cond), "%s is defined", currtemp_s);
}
if (tag >= DeviceValueTAG::TAG_SRC1) {
snprintf(currhum_s, sizeof(currhum_s), "value_json.airhumidity");
snprintf(currhum_cond, sizeof(currhum_cond), "%s is defined", currhum_s);
}
snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(device_type, tag).c_str());
}
@@ -1480,6 +1517,7 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
doc["curr_temp_t"] = topic_t;
doc["curr_temp_tpl"] = (std::string) "{{" + currtemp_s + " if " + currtemp_cond + " else 'None'}}";
}
if (tag >= DeviceValueTAG::TAG_SRC1) {
doc["curr_hum_t"] = topic_t;
doc["curr_hum_tpl"] = (std::string) "{{" + currhum_s + " if " + currhum_cond + " else 'None'}}";
@@ -1531,12 +1569,15 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
if (found_auto) {
modes.add("auto");
}
if (found_heat) {
modes.add("heat");
}
if (found_off) {
modes.add("off");
}
if (found_cool) {
modes.add("cool");
}
+2 -1
View File
@@ -164,6 +164,7 @@ class Mqtt {
if (discovery_prefix_.empty()) {
return std::string{};
}
return discovery_prefix_ + "/";
}
@@ -334,4 +335,4 @@ class Mqtt {
} // namespace emsesp
#endif
#endif
+29
View File
@@ -151,6 +151,7 @@ bool Network::formatBSSID([[maybe_unused]] const String & bssid, [[maybe_unused]
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]);
}
@@ -187,6 +188,7 @@ std::vector<Network::IPv6Address> Network::ipv6_addresses([[maybe_unused]] Netwo
esp_ip6_addr_type_t type;
const char * name;
};
static const Scope scopes[] = {{ESP_IP6_ADDR_IS_GLOBAL, "global"},
{ESP_IP6_ADDR_IS_UNIQUE_LOCAL, "unique local"},
{ESP_IP6_ADDR_IS_SITE_LOCAL, "site local"},
@@ -216,6 +218,7 @@ std::vector<IPAddress> Network::dns_servers(const NetworkInterface & netif) {
servers.push_back(ip);
}
}
return servers;
}
@@ -236,9 +239,11 @@ esp_err_t collect_ipv6_gateways(void * ctx) {
if (router == nullptr || netif_get_index(router->netif) != args->netif_index) {
continue;
}
const ip6_addr_t * addr = &router->next_hop_address;
args->gateways->push_back(IPAddress(IPv6, reinterpret_cast<const uint8_t *>(addr->addr), ip6_addr_zone(addr)));
}
return ESP_OK;
}
@@ -343,6 +348,7 @@ NetPhase Network::initialPhase() const {
if (!ssid_.isEmpty() || ap_provisionMode_ == AP_MODE_NEVER) {
return NetPhase::WIFI;
}
return NetPhase::AP;
}
@@ -354,6 +360,7 @@ void Network::loop() {
reconnect_pending_ = false;
reconnect();
}
// already have a connection: verify it's still alive
if (network_ip_ != 0 || last_disconnect_reason_ != 0) {
checkConnection();
@@ -394,10 +401,12 @@ void Network::checkConnection() {
if (iface_from_desc(esp_netif_get_desc(netif)) != network_iface_) {
continue;
}
esp_netif_ip_info_t ip_info = {};
if (esp_netif_is_netif_up(netif) && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK && ip_info.ip.addr == network_ip_) {
still_up = true;
}
break; // only one active netif per kind in our world (sta / eth / ap)
}
@@ -416,8 +425,10 @@ void Network::checkConnection() {
if (wifi_radio_off_) {
startWiFiRadio();
}
return;
}
begin();
}
#endif
@@ -439,6 +450,7 @@ void Network::setWiFiPower([[maybe_unused]] uint8_t tx_power) {
if (!WiFi.setTxPower(static_cast<wifi_power_t>(tx_power))) {
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
}
return;
}
@@ -455,6 +467,7 @@ void Network::setWiFiPower([[maybe_unused]] uint8_t tx_power) {
if (newrssi < threshold) {
min_tx_pwr = threshold - newrssi;
}
if (min_tx_pwr > max_tx_pwr) {
min_tx_pwr = max_tx_pwr;
}
@@ -621,6 +634,7 @@ void Network::startWiFiRadio() {
} else {
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
}
if (nosleep_) {
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
} else {
@@ -638,6 +652,7 @@ void Network::stopWiFiRadio() {
if (wifi_radio_off_) {
return;
}
WiFi.mode(WIFI_OFF);
wifi_radio_off_ = true;
wifi_connect_pending_ = false;
@@ -801,18 +816,23 @@ const char * Network::ethernet_status() const {
if (phy_type_ == PHY_type::PHY_TYPE_NONE) {
return nullptr;
}
if (ethClockConflictsPsram()) {
return "disabled - clock mode GPIO16/17 conflicts with PSRAM";
}
if (ethernet_connected()) {
return "connected";
}
if (ethernet_init_failed_) {
return "failed to initialize - no PHY found, check the board profile";
}
if (!ethernet_started_) {
return "not started";
}
return ethernet_connect_pending_ ? "connecting" : "no connection";
#else
return nullptr;
@@ -871,6 +891,7 @@ void Network::findNetworks() {
if (desc) {
strlcpy(info.desc, desc, sizeof(info.desc));
}
info.has_ipv6 = (esp_netif_get_ip6_linklocal(netif, &info.ip6) == ESP_OK);
best_iface = candidate;
@@ -896,9 +917,11 @@ void Network::findNetworks() {
} else if (network_iface_ == NetIface::AP) {
phase_ = NetPhase::AP;
}
if (!ethernet_ever_connected_ && ethernet_connected()) {
ethernet_ever_connected_ = true;
}
LOG_INFO("Network connected via %s (IP: " IPSTR ")",
network_iface_ == NetIface::ETHERNET ? "Ethernet"
: network_iface_ == NetIface::WIFI ? "WiFi"
@@ -962,6 +985,7 @@ void Network::findNetworks() {
LOG_WARNING("Ethernet failed to connect after %u attempts, switching to WiFi", connect_retry_);
phase_ = NetPhase::WIFI;
}
ethernet_connect_pending_ = false;
} else if (phase_ == NetPhase::WIFI) {
if (!wifi_ever_connected_) {
@@ -972,8 +996,10 @@ void Network::findNetworks() {
phase_ = NetPhase::AP;
}
}
wifi_connect_pending_ = false;
}
connect_retry_ = 0;
}
}
@@ -1009,6 +1035,7 @@ void Network::startAP() {
LOG_DEBUG("softAPConfig failed");
return;
}
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
// WiFi.softAPenableIPv6();
@@ -1027,6 +1054,7 @@ void Network::startAP() {
WiFi.softAPdisconnect(true);
return;
}
LOG_INFO("Starting Access Point with captive portal on %u.%u.%u.%u", apIp[0], apIp[1], apIp[2], apIp[3]);
ap_dnsServer_ = new DNSServer;
@@ -1035,6 +1063,7 @@ void Network::startAP() {
WiFi.softAPdisconnect(true);
return;
}
ap_dnsServer_->start(DNS_PORT, "*", apIp);
#endif
}
+2
View File
@@ -81,6 +81,7 @@ class PsramAsyncJsonResponse : public ::AsyncJsonResponse {
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
@@ -119,6 +120,7 @@ class PsramAsyncMessagePackResponse : public ::AsyncMessagePackResponse {
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
+2
View File
@@ -57,6 +57,7 @@ class PsramJsonAllocator : public ArduinoJson::Allocator {
if (p == nullptr) {
p = malloc(size);
}
return p;
#endif
}
@@ -80,6 +81,7 @@ class PsramJsonAllocator : public ArduinoJson::Allocator {
if (p == nullptr) {
p = realloc(ptr, new_size);
}
return p;
#endif
}
+21
View File
@@ -36,10 +36,12 @@ uint32_t Roomctrl::timeout_ = 0;
void Roomctrl::set_timeout(uint8_t t) {
timeout_ = t * 3600000; // ms
}
void Roomctrl::set_remotetemp(const uint8_t type, const uint8_t hc, const int16_t temp) {
if (!type_[hc] && !type) {
return;
}
if (remotetemp_[hc] != EMS_VALUE_INT16_NOTSET && temp == EMS_VALUE_INT16_NOTSET) { // switch remote off
remotetemp_[hc] = EMS_VALUE_INT16_NOTSET;
switch_off_[hc] = true;
@@ -47,13 +49,16 @@ void Roomctrl::set_remotetemp(const uint8_t type, const uint8_t hc, const int16_
sendtype_[hc] = SendType::TEMP;
return;
}
if (hc >= HCS || !type) {
return;
}
if (remotetemp_[hc] != temp) {
send_time_[hc] = uuid::get_uptime() - SEND_INTERVAL; // send now
sendtype_[hc] = SendType::TEMP;
}
type_[hc] = type;
remotetemp_[hc] = temp;
receive_time_[hc] = uuid::get_uptime();
@@ -64,10 +69,12 @@ void Roomctrl::set_remotehum(const uint8_t type, const uint8_t hc, const int8_t
if (hc >= HCS || type != type_[hc]) {
return;
}
if (remotehum_[hc] != hum) {
send_time_[hc] = uuid::get_uptime() - SEND_INTERVAL; // send now
sendtype_[hc] = SendType::HUMI;
}
remotehum_[hc] = hum;
}
@@ -82,6 +89,7 @@ uint8_t Roomctrl::get_hc(uint8_t addr) {
} else if (addr >= 0x18 && addr <= 0x1B && (type_[addr - 0x18] == RC20 || type_[addr - 0x18] == FB10)) {
return addr - 0x18; // RC20, FB10
}
return 0xFF; // invalid
}
@@ -92,11 +100,13 @@ void Roomctrl::send(uint8_t addr) {
if ((addr & 0x80) || EMSESP::system_.readonly_mode()) {
return;
}
uint8_t hc = get_hc(addr);
// check address, reply only on addresses 0x18..0x1B or 0x40..0x43
if (hc >= HCS) {
return;
}
// no reply if the temperature is not set
if (!switch_off_[hc] && remotetemp_[hc] == EMS_VALUE_INT16_NOTSET && remotehum_[hc] == EMS_VALUE_UINT8_NOTSET) {
return;
@@ -108,12 +118,14 @@ void Roomctrl::send(uint8_t addr) {
sendtype_[hc] = SendType::TEMP;
EMSESP::logger().warning("remotetemp timeout hc%d, stop sending roomtemperature to thermostat", hc);
}
if (switch_off_[hc] || (uuid::get_uptime() - send_time_[hc]) > SEND_INTERVAL) { // check interval
if (type_[hc] == RC100H || type_[hc] == RT800 || type_[hc] == RC120RF) {
if (sendtype_[hc] == SendType::HUMI) { // send humidity
if (switch_off_[hc]) {
remotehum_[hc] = EMS_VALUE_UINT8_NOTSET;
}
send_time_[hc] = uuid::get_uptime();
humidity(addr, 0x10, hc);
sendtype_[hc] = SendType::TEMP;
@@ -123,6 +135,7 @@ void Roomctrl::send(uint8_t addr) {
} else {
send_time_[hc] = uuid::get_uptime();
}
temperature(addr, 0x10, hc); // send to master-thermostat
}
} else if (type_[hc] == RC200 || type_[hc] == RC100 || type_[hc] == FB10) {
@@ -132,6 +145,7 @@ void Roomctrl::send(uint8_t addr) {
send_time_[hc] = uuid::get_uptime();
temperature(addr, 0x00, hc); // send to all
}
if (remotehum_[hc] == EMS_VALUE_UINT8_NOTSET && switch_off_[hc]) {
switch_off_[hc] = false;
type_[hc] = RemoteType::NONE;
@@ -149,22 +163,27 @@ void Roomctrl::check(uint8_t addr, const uint8_t * data, const uint8_t length) {
if (EMSESP::system_.readonly_mode()) {
return;
}
uint8_t hc = get_hc(addr);
if (hc >= HCS || length < 5) {
return;
}
if (type_[hc] == SENSOR) {
return;
}
// no reply if the temperature is not set
if (remotetemp_[hc] == EMS_VALUE_INT16_NOTSET) {
return;
}
// reply to writes with write nack byte
if ((addr & 0x80) == 0) { // it's a write to us
ack_write(); // accept writes, don't care.
return;
}
addr &= 0x7F;
// reads: for now we only reply to version and remote temperature
// empty message back if temperature not set or unknown message type
@@ -382,6 +401,7 @@ void Roomctrl::humidity(uint8_t addr, uint8_t dst, uint8_t hc) {
if (dew != EMS_VALUE_INT16_NOTSET) {
dew8 = static_cast<int8_t>((dew >= 0 ? dew + 5 : dew - 5) / 10);
}
uint8_t data[11];
data[0] = addr | EMSbus::ems_mask();
data[1] = dst & 0x7F;
@@ -436,6 +456,7 @@ void Roomctrl::replyF7(uint8_t addr, uint8_t dst, uint8_t offset, uint8_t typehh
data[7] = 0;
data[8] = 0;
}
data[9] = EMSbus::calculate_crc(data, 9); // append CRC
EMSuart::transmit(data, 10);
}
+2 -1
View File
@@ -34,6 +34,7 @@ class Roomctrl {
static bool is_remote(const uint8_t hc) {
return (hc < 4 && remotetemp_[hc] != EMS_VALUE_INT16_NOTSET);
}
static void set_timeout(uint8_t t);
private:
@@ -63,4 +64,4 @@ class Roomctrl {
} // namespace emsesp
#endif
#endif
+5
View File
@@ -46,6 +46,7 @@ void Shower::start() {
LOG_WARNING("Coldshot failed. Shower not active");
force_coldshot = false;
}
return true;
},
FL_(coldshot_cmd),
@@ -90,6 +91,7 @@ void Shower::loop() {
set_shower_state(true);
LOG_DEBUG("hot water still running, starting shower timer");
}
// check if the shower has been on too long
else if ((shower_alert_ && ((time_now - timer_start_) > next_alert_)) || force_coldshot) {
shower_alert_start();
@@ -138,6 +140,7 @@ void Shower::loop() {
set_shower_state(false);
}
}
return;
}
@@ -178,6 +181,7 @@ void Shower::set_shower_state(bool state, bool force) {
if ((shower_state_ == old_shower_state_) && !force) {
return;
}
old_shower_state_ = shower_state_;
// always publish as a string - see https://github.com/emsesp/EMS-ESP/issues/369
@@ -205,6 +209,7 @@ void Shower::create_ha_discovery() {
} else {
snprintf(str, sizeof(str), "shower_active"); // v3.4 compatible
}
doc["uniq_id"] = str;
doc["def_ent_id"] = (std::string) "binary_sensor." + str;
doc["stat_t"] = "~/shower_active";
+1
View File
@@ -41,6 +41,7 @@ class Shower {
void shower_alert(bool enable) {
shower_alert_ = enable;
}
void ha_reset() {
ha_configdone_ = false;
}
+56
View File
@@ -41,6 +41,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
i += (*p == '{') ? 1 : (*p == '}') ? -1 : 0;
++p;
}
const auto s = std::string(b, p);
tokens.emplace_back(Token::Type::String, s, -3);
if (*p == '\0') {
@@ -84,6 +85,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
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, -3);
--p;
@@ -93,6 +95,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
while (*p && *p != '"') {
++p;
}
const auto s = std::string(b, p);
tokens.emplace_back(Token::Type::String, s, -3);
if (*p == '\0') {
@@ -104,6 +107,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
while (*p && *p != '\'') {
++p;
}
const auto s = std::string(b, p);
tokens.emplace_back(Token::Type::String, s, -3);
if (*p == '\0') {
@@ -114,6 +118,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
while (isdigit(*p) || *p == '.') {
++p;
}
const auto s = std::string(b, p);
tokens.emplace_back(Token::Type::Number, s, -2);
--p;
@@ -160,6 +165,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
tokens.pop_back();
continue;
}
token = Token::Type::Unary;
c = 'm';
precedence = 5;
@@ -167,6 +173,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
// otherwise, it's binary '-'
precedence = 2;
}
break;
case '&':
if (p[1] == '&')
@@ -189,12 +196,14 @@ std::deque<Token> exprToTokens(const std::string & expr) {
precedence = 2;
token = Token::Type::Unary;
}
break;
case '<':
if (p[1] == '=') {
++p;
c = '{';
}
precedence = 1;
token = Token::Type::Compare;
break;
@@ -203,6 +212,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
++p;
c = '}';
}
precedence = 1;
token = Token::Type::Compare;
break;
@@ -213,6 +223,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
token = Token::Type::Compare;
break;
}
const auto s = std::string(1, c);
tokens.emplace_back(token, s, precedence, rightAssociative);
}
@@ -321,6 +332,7 @@ std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
queue.push_back(std::move(stack.back()));
stack.pop_back();
}
return queue;
}
@@ -329,9 +341,11 @@ bool isnum(const std::string & s) {
if (s.empty() || s.find_first_of("0123456789") == std::string::npos) {
return false;
}
if (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;
}
@@ -350,17 +364,20 @@ std::string commands(std::string & expr, bool quotes) {
if (e == std::string::npos) {
e = expr.length();
}
char cmd[COMMAND_MAX_LENGTH];
size_t l = e - f;
if (l >= sizeof(cmd) - 1) {
break;
}
expr_new.copy(cmd, l, f);
cmd[l] = '\0';
if (strstr(cmd, "/value") == nullptr) {
strlcat(cmd, "/value", sizeof(cmd) - 6);
}
JsonDocument doc_out;
JsonDocument doc_in;
JsonObject output = doc_out.to<JsonObject>();
@@ -373,6 +390,7 @@ std::string commands(std::string & expr, bool quotes) {
if (return_code != CommandRet::OK && return_code != CommandRet::NO_VALUE) {
return expr = "";
}
std::string data;
if (output["api_data"].is<std::string>()) {
data = output["api_data"].as<std::string>();
@@ -383,12 +401,14 @@ std::string commands(std::string & expr, bool quotes) {
} else {
serializeJson(output, data);
}
expr.replace(f, l, data);
e = f + data.length();
expr_new = Helpers::toLower(expr);
f = expr_new.find(d, e);
}
}
if (quotes) {
// remove double quotes
auto f = expr.find("\"\"");
@@ -397,6 +417,7 @@ std::string commands(std::string & expr, bool quotes) {
f = expr.find("\"\"");
}
}
return expr;
}
@@ -406,6 +427,7 @@ int to_logic(const std::string & s) {
if (!Helpers::value2bool(s.c_str(), value_b)) {
return -1; // invalid
}
return value_b;
// if (s.empty()) {
@@ -427,9 +449,11 @@ std::string to_string(double d) {
while (!s.empty() && s.back() == '0') {
s.pop_back();
}
if (!s.empty() && s.back() == '.') {
s.pop_back();
}
return s;
}
@@ -476,6 +500,7 @@ std::string calculate(const std::string & expr) {
if (stack.empty()) {
return "";
}
const auto rhs = stack.back();
stack.pop_back();
if (token.str[0] == '!') {
@@ -489,20 +514,25 @@ std::string calculate(const std::string & expr) {
EMSESP::logger().warning("'!' needs a boolean or numeric operand, got '%s'. Literal text must be quoted", rhs.c_str());
return "";
}
break;
}
if (token.str[0] == 'h') {
// hex string to number
if (rhs.empty() || rhs.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos) {
return "";
}
stack.push_back(to_string(std::stoi(rhs, 0, 16)));
break;
}
// bail out on non-numeric operand
if (!isnum(rhs)) {
return "";
}
auto rhd = std::stod(rhs);
switch (token.str[0]) {
default:
@@ -551,6 +581,7 @@ std::string calculate(const std::string & expr) {
if (stack.size() < 2) {
return "";
}
const auto rhs = stack.back();
stack.pop_back();
const auto lhs = stack.back();
@@ -564,6 +595,7 @@ 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");
break;
case '{':
@@ -571,6 +603,7 @@ 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");
break;
case '>':
@@ -578,6 +611,7 @@ 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");
break;
case '}':
@@ -585,6 +619,7 @@ 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");
break;
case '=':
@@ -592,6 +627,7 @@ std::string calculate(const std::string & expr) {
stack.push_back((std::stod(lhs) == std::stod(rhs)) ? "1" : "0");
break;
}
// compare strings lower case
stack.push_back((Helpers::toLower(lhs) == Helpers::toLower(rhs)) ? "1" : "0");
break;
@@ -600,6 +636,7 @@ std::string calculate(const std::string & expr) {
stack.push_back((std::stod(lhs) != std::stod(rhs)) ? "1" : "0");
break;
}
// compare strings lower case
stack.push_back((Helpers::toLower(lhs) != Helpers::toLower(rhs)) ? "1" : "0");
break;
@@ -610,6 +647,7 @@ std::string calculate(const std::string & expr) {
if (stack.size() < 2) {
return "";
}
const auto rhs = to_logic(stack.back());
stack.pop_back();
const auto lhs = to_logic(stack.back());
@@ -617,6 +655,7 @@ std::string calculate(const std::string & expr) {
if (rhs < 0 || lhs < 0) {
return "";
}
switch (token.str[0]) {
default:
return "";
@@ -634,6 +673,7 @@ std::string calculate(const std::string & expr) {
if (stack.size() < 2) {
return "";
}
const auto rhs = stack.back();
stack.pop_back();
const auto lhs = stack.back();
@@ -642,10 +682,12 @@ std::string calculate(const std::string & expr) {
stack.push_back(lhs + rhs);
break;
}
// bail out on non-numeric operands
if (!isnum(lhs) || !isnum(rhs)) {
return "";
}
auto lhd = std::stod(lhs);
auto rhd = std::stod(rhs);
switch (token.str[0]) {
@@ -686,6 +728,7 @@ std::string calculate(const std::string & expr) {
for (const auto & s : stack) {
result += s;
}
return result;
}
@@ -704,6 +747,7 @@ static size_t find_unquoted(const std::string & s, char c, size_t from = 0) {
return i;
}
}
return std::string::npos;
}
@@ -725,6 +769,7 @@ std::string compute(const std::string & expr) {
i++;
}
}
std::string cmd = expr_new.substr(f, e - f).c_str();
JsonDocument doc;
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
@@ -747,6 +792,7 @@ std::string compute(const std::string & expr) {
keys_s = p.key().c_str();
}
}
std::string value = doc[value_s] | "";
std::string method = doc[method_s] | "GET";
std::string result;
@@ -758,6 +804,7 @@ std::string compute(const std::string & expr) {
if (doc[keys_s].is<JsonArray>()) {
keys_doc.set(doc[keys_s].as<JsonArray>());
}
JsonArray keys = keys_doc.as<JsonArray>();
if (key.length() || !keys.isNull()) {
doc.clear();
@@ -775,17 +822,21 @@ std::string compute(const std::string & expr) {
break; // type mismatch
}
}
result = json.as<std::string>();
}
expr_new.replace(f, e - f, result.c_str());
}
}
expr_new.replace(f, e - f, result);
} else if (httpResult != 0) {
// httpResult of 0 means no url
EMSESP::logger().warning("URL command failed with https code: %d, response: %s", httpResult, result.c_str());
}
}
f = expr_new.find_first_of('{', e);
}
@@ -799,15 +850,18 @@ std::string compute(const std::string & expr) {
q1 = find_unquoted(expr_new, '?', q1 + 1);
c1 = find_unquoted(expr_new, ':', c1 + 1);
}
if (c1 == std::string::npos) {
return ""; // error: missing colon
}
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 "";
@@ -821,6 +875,7 @@ std::string compute(const std::string & expr) {
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') {
@@ -828,6 +883,7 @@ std::string compute(const std::string & expr) {
} else {
return ""; // error
}
q = find_unquoted(expr_new, '?'); // search next instance
}
+153
View File
@@ -122,9 +122,11 @@ static void show_addresses(uuid::console::Shell & shell, NetworkInterface & neti
for (const auto & dns : Network::dns_servers(netif)) {
shell.printfln(" %s DNS Server: %s", dns.type() == IPv4 ? "IPv4" : "IPv6", uuid::printable_to_string(dns).c_str());
}
for (const auto & ipv6 : Network::ipv6_addresses(netif)) {
shell.printfln(" IPv6 address: %s (%s)", uuid::printable_to_string(ipv6.ip).c_str(), ipv6.scope);
}
for (const auto & gateway : Network::ipv6_gateways(netif)) {
shell.printfln(" IPv6 gateway: %s", uuid::printable_to_string(gateway).c_str());
}
@@ -170,6 +172,7 @@ uint8_t System::language_index() {
return i;
}
}
return 0; // EN only
}
@@ -198,6 +201,7 @@ bool System::command_sendmail(const char * value, const int8_t) {
if (!enabled) {
return false;
}
LOG_DEBUG("Command sendmail port %d%s called with '%s'",
port,
security == EMAIL_SECURITY::SSL ? " (SSL)"
@@ -219,6 +223,7 @@ bool System::command_sendmail(const char * value, const int8_t) {
body = doc["body"] | body;
}
}
// keep the original body if the calculator returns nothing, so unquoted literal text still gets sent
std::string computed_body = compute(body.c_str());
if (!computed_body.empty()) {
@@ -269,6 +274,7 @@ bool System::command_sendmail(const char * value, const int8_t) {
cleanup();
return false;
}
if (!smtp->isConnected()) {
LOG_ERROR("send email connection error: %s", smtp->status().text.c_str());
cleanup();
@@ -284,6 +290,7 @@ bool System::command_sendmail(const char * value, const int8_t) {
return false;
}
}
SMTPMessage & msg = smtp->getMessage();
msg.headers.add(rfc822_subject, subject);
msg.headers.add(rfc822_from, sender);
@@ -324,6 +331,7 @@ std::string System::languages_string() {
languages_string += ",";
}
}
languages_string += ")";
return languages_string;
}
@@ -338,6 +346,7 @@ bool System::command_response(const char * value, const int8_t id, JsonObject ou
} else {
output["response"] = Mqtt::get_response();
}
return true;
}
@@ -454,6 +463,7 @@ bool System::command_watch(const char * value, const int8_t id) {
if (w == 0 || EMSESP::watch() == EMSESP::Watch::WATCH_OFF) {
EMSESP::watch_id(0);
}
if (Mqtt::publish_single() && w != EMSESP::watch()) {
if (Mqtt::publish_single2cmd()) {
Mqtt::queue_publish("system/watch", EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : (FL_(list_watch)[w]));
@@ -461,6 +471,7 @@ bool System::command_watch(const char * value, const int8_t id) {
Mqtt::queue_publish("system_data/watch", EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : (FL_(list_watch)[w]));
}
}
EMSESP::watch(w);
return true;
} else if (i) {
@@ -471,12 +482,15 @@ bool System::command_watch(const char * value, const int8_t id) {
Mqtt::queue_publish("system_data/watch", Helpers::hextoa(i));
}
}
EMSESP::watch_id(i);
if (EMSESP::watch() == EMSESP::Watch::WATCH_OFF) {
EMSESP::watch(EMSESP::Watch::WATCH_ON);
}
return true;
}
return false;
}
@@ -484,6 +498,7 @@ void System::store_nvs_values() {
if (Command::find_command(EMSdevice::DeviceType::BOILER, 0, "nompower", 0) != nullptr) {
Command::call(EMSdevice::DeviceType::BOILER, "nompower", "-1"); // trigger a write
}
EMSESP::analogsensor_.store_counters();
EMSESP::nvs_.end();
}
@@ -543,6 +558,7 @@ void System::get_partition_info() {
if (EMSESP::nvs_.isKey(part->label)) {
p_info.version = EMSESP::nvs_.getString(part->label).c_str();
}
char c[20];
snprintf(c, sizeof(c), "d_%s", (const char *)part->label);
time_t d = EMSESP::nvs_.getULong(c, 0);
@@ -556,12 +572,14 @@ void System::get_partition_info() {
} else {
p_info.size = 0;
}
partition_info_[part->label] = p_info;
}
}
it = esp_partition_next(it); // loop to next partition
}
esp_partition_iterator_release(it);
#endif
}
@@ -577,14 +595,17 @@ void System::recovery_installed() {
if (EMSESP::nvs_.isKey(factory->label)) {
EMSESP::nvs_.remove(factory->label);
}
const time_t now = time(nullptr);
if (now > 1500000000L) {
EMSESP::nvs_.putULong(c, now); // UTC epoch, read by the recovery app. Only if the clock is set (NTP)
} else if (EMSESP::nvs_.isKey(c)) {
EMSESP::nvs_.remove(c); // clock not set, the date of the previous firmware is wrong
}
partition_info_.erase(factory->label); // on the AsyncTCP task, same as the web handlers reading it
}
LOG_INFO("Recovery firmware v%s installed", recovery_version().c_str());
#endif
}
@@ -697,6 +718,7 @@ void System::system_restart(const char * partitionname) {
LOG_ERROR("Partition '%s' not found", partitionname);
return;
}
if (strcmp(partition->label, partitionname) && strcmp(partitionname, "boot") != 0) {
partition = esp_ota_get_next_update_partition(partition);
if (!partition || strcmp(partition->label, partitionname)) {
@@ -704,6 +726,7 @@ void System::system_restart(const char * partitionname) {
return;
}
}
// error if partition is empty
uint64_t buffer;
esp_partition_read(partition, 0, &buffer, 8);
@@ -711,6 +734,7 @@ void System::system_restart(const char * partitionname) {
LOG_ERROR("Partition '%s' is empty, not bootable", partition->label);
return;
}
// set the boot partition
esp_ota_set_boot_partition(partition);
}
@@ -728,6 +752,7 @@ void System::system_restart(const char * partitionname) {
Shell::loop_all();
delay(10); // give telnet TCP stack time to transmit
}
Serial.flush(); // wait for hardware TX buffer to drain
Mqtt::disconnect(); // gracefully disconnect MQTT (flushes the DISCONNECT before reboot, needed for QOS1)
@@ -767,6 +792,7 @@ void System::syslog_init() {
syslog_.mark_interval(0);
// syslog_.destination("");
}
if (Mqtt::publish_single()) {
if (Mqtt::publish_single2cmd()) {
Mqtt::queue_publish("system/syslog", syslog_enabled_ ? (FL_(list_syslog_level)[syslog_level_ + 1]) : "off");
@@ -806,6 +832,7 @@ void System::modbus_init() {
delete EMSESP::modbus_;
EMSESP::modbus_ = nullptr;
}
modbus_enabled_ = settings.modbus_enabled;
modbus_port_ = settings.modbus_port;
modbus_max_clients_ = settings.modbus_max_clients;
@@ -871,6 +898,7 @@ void System::check_crash_loop() {
if (factory == nullptr || factory == esp_ota_get_running_partition()) {
return;
}
// fails if the factory partition doesn't hold a valid image, then keep trying the current one
if (esp_ota_set_boot_partition(factory) == ESP_OK) {
crash_count_ = 0;
@@ -889,6 +917,7 @@ void System::check_firmware_health() {
if (firmware_healthy_ || uuid::get_uptime_sec() < FIRMWARE_HEALTHY_UPTIME) {
return;
}
firmware_healthy_ = true;
crash_count_ = 0;
if (OtaUpdater::confirmRunningApp()) {
@@ -905,6 +934,7 @@ void System::start() {
if (crash_count_) {
LOG_WARNING("Restarted after a crash (%u in a row)", crash_count_);
}
const esp_partition_t * invalid = esp_ota_get_last_invalid_partition();
if (invalid != nullptr) {
LOG_WARNING("Firmware in partition %s failed to start and was rolled back", invalid->label);
@@ -986,6 +1016,7 @@ void System::button_OnLongPress(PButton & b) {
LOG_NOTICE("reset disabled, can't boot");
return;
}
LOG_NOTICE("Button pressed - long press - restart EMS-ESP");
EMSESP::system_.system_restart();
}
@@ -997,6 +1028,7 @@ void System::button_OnVLongPress(PButton & b) {
LOG_NOTICE("reset disabled, can't boot");
return;
}
LOG_NOTICE("Button pressed - very long press - perform factory reset in Recovery Mode");
EMSESP::system_.system_restart("boot");
// EMSESP::led_.start_led_fast_flash(5); // Start LED flash timer for 5 seconds
@@ -1009,6 +1041,7 @@ void System::button_init() {
LOG_WARNING("Multi-functional button not detected");
return;
}
LOG_DEBUG("Multi-functional button enabled");
myPButton_.onClick(BUTTON_Debounce, button_OnClick);
@@ -1077,6 +1110,7 @@ void System::send_info_mqtt() {
if (!Mqtt::connected() || connection == _connection) {
return;
}
_connection = connection;
JsonDocument doc;
@@ -1270,6 +1304,7 @@ int8_t System::wifi_quality(int8_t dBm) {
if (dBm >= -50) {
return 100;
}
return 2 * (dBm + 100);
}
@@ -1358,12 +1393,14 @@ void System::show_system(uuid::console::Shell & shell) {
} else {
shell.printfln(" PSRAM: not available");
}
// GPIOs
shell.println(" GPIOs:");
shell.printf(" allowed:");
for (const auto & gpio : valid_system_gpios_) {
shell.printf(" %d", gpio);
}
shell.printfln(" [total %d]", valid_system_gpios_.size());
shell.printf(" in use:");
auto sorted_gpios = used_gpios_;
@@ -1371,12 +1408,14 @@ void System::show_system(uuid::console::Shell & shell) {
for (const auto & gpio : sorted_gpios) {
shell.printf(" %d(%s)", gpio.pin, gpio.source.c_str());
}
shell.printfln(" [total %d]", used_gpios_.size());
auto available = available_gpios();
shell.printf(" available:");
for (const auto & gpio : available) {
shell.printf(" %d", gpio);
}
shell.printfln(" [total %d]", available.size());
// List all partitions and their version info
shell.println(" Partitions:");
@@ -1394,6 +1433,7 @@ void System::show_system(uuid::console::Shell & shell) {
if (partition.second.version.empty()) {
continue; // no version, empty string
}
std::string installed;
if (partition.second.install_date > 0) {
char time_string[25];
@@ -1401,11 +1441,13 @@ void System::show_system(uuid::console::Shell & shell) {
strftime(time_string, sizeof(time_string), "%FT%T", localtime(&d));
installed = std::string(", installed on ") + time_string;
}
const bool is_running = strcmp(running_label, partition.first.c_str()) == 0;
const bool is_boot = strcmp(boot_label, partition.first.c_str()) == 0;
if (is_boot) {
boot_listed = true;
}
const char * mark = is_running && is_boot ? "** active, boot **" : is_running ? "** active **" : is_boot ? "** boot **" : "";
shell.printfln(" %s: v%s (%d KB%s) %s", partition.first.c_str(), partition.second.version.c_str(), partition.second.size, installed.c_str(), mark);
}
@@ -1421,12 +1463,14 @@ void System::show_system(uuid::console::Shell & shell) {
if (is_boot) {
boot_listed = true;
}
int size_kb = 0;
esp_image_metadata_t meta = {};
esp_partition_pos_t part_pos = {.offset = factory->address, .size = factory->size};
if (esp_image_verify(ESP_IMAGE_VERIFY_SILENT, &part_pos, &meta) == ESP_OK) {
size_kb = static_cast<int>(meta.image_len / 1024);
}
const char * mark = is_running && is_boot ? "** active, boot **" : is_running ? "** active **" : is_boot ? "** boot **" : "";
shell.printfln(" %s: Recovery v%s (%d KB) %s", factory->label, desc.version, size_kb, mark);
}
@@ -1477,11 +1521,14 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" = unknown");
break;
}
err = nvs_entry_next(&it);
}
if (it != nullptr) {
nvs_release_iterator(it); // just in case
}
if (err == ESP_OK) {
shell.println();
}
@@ -1538,6 +1585,7 @@ void System::show_system(uuid::console::Shell & shell) {
if (ethernet_status != nullptr) {
shell.printfln(" Ethernet Status: %s", ethernet_status);
}
if (EMSESP::network_.ethernet_connected()) {
shell.printfln(" Ethernet MAC address: %s", ETH.macAddress().c_str());
shell.printfln(" Hostname: %s", ETH.getHostname());
@@ -1616,24 +1664,31 @@ bool System::check_restore() {
}
}
}
// continue processing the rest of the sections
saveSettings(EMSESP_SETTINGS_FILE, section);
}
if (section_type == "commands") {
saveSettings(EMSESP_COMMANDS_FILE, section);
}
if (section_type == "schedule") {
saveSettings(EMSESP_SCHEDULER_FILE, section);
}
if (section_type == "customizations") {
saveSettings(EMSESP_CUSTOMIZATION_FILE, section);
}
if (section_type == "entities") {
saveSettings(EMSESP_CUSTOMENTITY_FILE, section);
}
if (section_type == "modules") {
saveSettings(EMSESP_MODULES_FILE, section);
}
if (section_type == "customSupport") {
// it's a custom support, extract json and write to /config/customSupport.json file
File customSupportFile = LittleFS.open(EMSESP_CUSTOMSUPPORT_FILE, "w");
@@ -1660,6 +1715,7 @@ bool System::check_restore() {
EMSESP::nvs_.putChar(key.c_str(), v);
LOG_DEBUG("Restored NVS value: %s = %d", key.c_str(), v);
}
break;
case NVS_TYPE_U8:
if (entry["value"].is<JsonVariantConst>()) {
@@ -1667,6 +1723,7 @@ bool System::check_restore() {
EMSESP::nvs_.putUChar(key.c_str(), v);
LOG_DEBUG("Restored NVS value: %s = %d", key.c_str(), v);
}
break;
case NVS_TYPE_I32:
if (entry["value"].is<JsonVariantConst>()) {
@@ -1674,6 +1731,7 @@ bool System::check_restore() {
EMSESP::nvs_.putInt(key.c_str(), v);
LOG_DEBUG("Restored NVS value: %s = %d", key.c_str(), v);
}
break;
case NVS_TYPE_U32:
if (entry["value"].is<JsonVariantConst>()) {
@@ -1681,6 +1739,7 @@ bool System::check_restore() {
EMSESP::nvs_.putUInt(key.c_str(), v);
LOG_DEBUG("Restored NVS value: %s = %d", key.c_str(), v);
}
break;
case NVS_TYPE_I64:
if (entry["value"].is<JsonVariantConst>()) {
@@ -1688,6 +1747,7 @@ bool System::check_restore() {
EMSESP::nvs_.putLong64(key.c_str(), v);
LOG_DEBUG("Restored NVS value: %s = %d", key.c_str(), v);
}
break;
case NVS_TYPE_U64:
if (entry["value"].is<JsonVariantConst>()) {
@@ -1695,6 +1755,7 @@ bool System::check_restore() {
EMSESP::nvs_.putULong64(key.c_str(), v);
LOG_DEBUG("Restored NVS value: %s = %d", key.c_str(), v);
}
break;
case NVS_TYPE_BLOB:
// used for double values
@@ -1703,6 +1764,7 @@ bool System::check_restore() {
EMSESP::nvs_.putDouble(key.c_str(), v);
LOG_DEBUG("Restored NVS value: %s = %d", key.c_str(), v);
}
break;
case NVS_TYPE_STR:
case NVS_TYPE_ANY:
@@ -1712,6 +1774,7 @@ bool System::check_restore() {
EMSESP::nvs_.putString(key.c_str(), v.c_str());
LOG_DEBUG("Restored NVS value: %s = %s", key.c_str(), v.c_str());
}
break;
}
}
@@ -1824,6 +1887,7 @@ bool System::check_upgrade() {
LOG_INFO("Upgrade: setting MQTT Entity ID format to v3.6 format (4)");
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
});
}
@@ -1840,11 +1904,13 @@ bool System::check_upgrade() {
LOG_INFO("Upgrade: setting WiFi TX Power to Auto");
changed = StateUpdateResult::CHANGED;
}
if (networkSettings.nosleep != true) {
networkSettings.nosleep = true;
LOG_INFO("Upgrade: disabling WiFi nosleep");
changed = StateUpdateResult::CHANGED;
}
return changed;
});
EMSESP::network_.reconnect();
@@ -1873,6 +1939,7 @@ bool System::check_upgrade() {
LOG_INFO("Upgrade: setting AP provision mode to on disconnect");
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
});
// Scheduler name is now mandatory, update FS if name is empty
@@ -1885,6 +1952,7 @@ bool System::check_upgrade() {
schedule_changed = true;
}
}
return schedule_changed ? StateUpdateResult::CHANGED : StateUpdateResult::UNCHANGED;
});
#endif
@@ -1923,9 +1991,11 @@ bool System::check_upgrade() {
} else {
snprintf(ci.name, sizeof(ci.name), "schedule_%d", idx);
}
commands.commandItems.push_back(ci);
idx++;
}
return StateUpdateResult::CHANGED;
});
@@ -1937,12 +2007,14 @@ bool System::check_upgrade() {
if (it == scheduler.scheduleItems.end()) {
break;
}
// flag 132 (0x84) is the old IMMEDIATE format which has no command - erase the entry
if (item["flags"].as<uint8_t>() == 0x84) {
it = scheduler.scheduleItems.erase(it);
idx++;
continue;
}
const char * nm = item["name"];
char cmd_name[sizeof(it->name)];
if (nm != nullptr && nm[0] != '\0') {
@@ -1951,10 +2023,12 @@ bool System::check_upgrade() {
snprintf(cmd_name, sizeof(cmd_name), "schedule_%d", idx);
strlcpy(it->name, cmd_name, sizeof(it->name)); // keep entry name consistent with its command
}
it->cmd_name = cmd_name;
++it;
idx++;
}
return StateUpdateResult::CHANGED;
});
@@ -1975,11 +2049,13 @@ bool System::check_upgrade() {
return StateUpdateResult::CHANGED;
}
}
// Migrate language from cz to cs
if (settings.locale == "cz") {
settings.locale = "cs";
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
});
} else if (this_version < settings_version) {
@@ -2060,8 +2136,10 @@ void System::exportSettings(const std::string & type, [[maybe_unused]] const cha
LOG_ERROR("Failed to deserialize settings file %s", filename);
return;
}
output[section].set(jsonDocument.as<JsonObject>());
}
LOG_DEBUG("Exported %s settings from file %s", section, filename);
} else {
LOG_ERROR("No settings file for %s found", filename);
@@ -2177,6 +2255,7 @@ void System::exportSystemBackup(JsonObject output) {
entry["value"] = EMSESP::nvs_.getString(info.key);
break;
}
err = nvs_entry_next(&it);
}
@@ -2279,6 +2358,7 @@ bool System::command_service(const char * cmd, const char * value) {
if (ok) {
LOG_INFO("System command '%s' with value '%s'", cmd, value);
}
return ok;
}
@@ -2288,9 +2368,11 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
LOG_ERROR("empty system command");
return false;
}
if (!strcmp(cmd, "restart")) { // restart is a command, not an entity
return false;
}
// check for hardcoded "info"/"value"
if (!strcmp(cmd, F_(info)) || !strcmp(cmd, F_(values))) {
return command_info("", 0, output);
@@ -2303,6 +2385,7 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
output["api_data"] = metrics;
return true;
}
return false;
}
@@ -2323,6 +2406,7 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
}
}
}
return true;
}
@@ -2344,9 +2428,11 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
for (JsonPair p1 : p.value().as<JsonObject>()) {
output[p1.key().c_str()] = p1.value().as<std::string>();
}
return true;
}
}
return false;
}
@@ -2363,6 +2449,7 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
output["api_data"] = p1.value().as<std::string>();
return true;
}
get_value_json(output, p_key, p1_key, p1.value());
return true;
}
@@ -2370,6 +2457,7 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
} // else skip, but we don't have value pairs in system root
}
}
return false;
}
@@ -2378,6 +2466,7 @@ void System::get_value_json(JsonObject output, const std::string & circuit, cons
if (circuit.length()) {
output["circuit"] = circuit;
}
output["readable"] = true;
output["writeable"] =
(name == "txpause" || name == "showerTimer" || name == "showerAlert" || name == "enabled" || name == "hideLed" || name == "analogEnabled");
@@ -2420,6 +2509,7 @@ std::string System::get_metrics_prometheus() {
escaped += c;
}
}
return escaped;
};
@@ -2435,6 +2525,7 @@ std::string System::get_metrics_prometheus() {
c = '_';
}
}
return sanitized;
};
@@ -2446,6 +2537,7 @@ std::string System::get_metrics_prometheus() {
c = tolower(c);
}
}
return result;
};
@@ -2454,14 +2546,17 @@ std::string System::get_metrics_prometheus() {
if (path == "system" && key == "uptime") {
return true;
}
if (path == "ntp" && key == "timestamp") {
return true;
}
if (path.find("devices[") != std::string::npos) {
if (key == "handlersReceived" || key == "handlersFetched" || key == "handlersPending" || key == "handlersIgnored") {
return true;
}
}
return false;
};
@@ -2519,11 +2614,14 @@ std::string System::get_metrics_prometheus() {
if (!first) {
result += ", ";
}
result += label.first + "=\"" + escape_label(label.second) + "\"";
first = false;
}
result += "}";
}
result += " " + std::to_string(device["productID"].as<int>()) + "\n";
}
@@ -2544,11 +2642,14 @@ std::string System::get_metrics_prometheus() {
if (!first) {
result += ", ";
}
result += label.first + "=\"" + escape_label(label.second) + "\"";
first = false;
}
result += "}";
}
result += " " + std::to_string(device["entities"].as<int>()) + "\n";
}
}
@@ -2578,6 +2679,7 @@ std::string System::get_metrics_prometheus() {
snprintf(val_str, sizeof(val_str), "%.2f", p.value().as<float>());
result += val_str;
}
result += "\n";
} else if (is_string) {
// collect string for info metric (skip dynamic strings like uptime and timestamp)
@@ -2592,6 +2694,7 @@ std::string System::get_metrics_prometheus() {
break;
}
}
if (!key_exists) {
local_info_labels.push_back({lower_key, val});
}
@@ -2617,11 +2720,14 @@ std::string System::get_metrics_prometheus() {
if (!first) {
result += ", ";
}
result += label.first + "=\"" + escape_label(label.second) + "\"";
first = false;
}
result += "}";
}
result += " 1\n";
}
};
@@ -2692,6 +2798,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["psramSize"] = EMSESP::system_.PSram();
node["freePsram"] = ESP.getFreePsram() / 1024;
}
node["model"] = EMSESP::system_.getBBQKeesGatewayDetails();
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
node["temperature"] = EMSESP::system_.temperature();
@@ -2705,8 +2812,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
if (!gpios_allowed_str.empty()) {
gpios_allowed_str += ", ";
}
gpios_allowed_str += Helpers::itoa(gpio);
}
node["gpios_allowed"] = gpios_allowed_str;
std::string gpios_in_use_str;
@@ -2716,8 +2825,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
if (!gpios_in_use_str.empty()) {
gpios_in_use_str += ", ";
}
gpios_in_use_str += Helpers::itoa(gpio.pin);
}
node["gpios_in_use"] = gpios_in_use_str;
std::string gpios_available_str;
@@ -2725,8 +2836,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
if (!gpios_available_str.empty()) {
gpios_available_str += ", ";
}
gpios_available_str += Helpers::itoa(gpio);
}
node["gpios_available"] = gpios_available_str;
// Network Status
@@ -2765,6 +2878,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
if (WiFi.status() == WL_CONNECTED && !settings.bssid.isEmpty()) {
node["BSSID"] = "set"; // we don't disclose the name
}
node["TxPowerSetting"] = settings.tx_power;
node["staticIP"] = settings.staticIPConfig;
node["lowBandwidth"] = settings.bandwidth20;
@@ -2819,6 +2933,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::esp32React.getMqttSettingsService()->read([&](const MqttSettings & settings) {
node["enabled"] = settings.enabled;
node["clientID"] = settings.clientId;
@@ -2911,6 +3026,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["busStatus"] = "unknown";
break;
}
node["busProtocol"] = EMSbus::is_ht3() ? "HT3" : "Buderus";
node["busTelegramsReceived"] = EMSESP::rxservice_.telegram_count();
node["busReads"] = EMSESP::txservice_.telegram_read_count();
@@ -2935,6 +3051,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["showerAlertColdshot"] = settings.shower_alert_coldshot; // seconds
node["showerAlertTrigger"] = settings.shower_alert_trigger; // minutes
}
if (settings.board_profile == "CUSTOM") {
node["phyType"] = settings.phy_type;
if (settings.phy_type != PHY_type::PHY_TYPE_NONE) {
@@ -2942,6 +3059,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["ethPhyAddr"] = settings.eth_phy_addr;
node["ethClockMmode"] = settings.eth_clock_mode;
}
node["rxGPIO"] = settings.rx_gpio;
node["txGPIO"] = settings.tx_gpio;
node["dallasGPIO"] = settings.dallas_gpio;
@@ -2949,6 +3067,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["ledGPIO"] = settings.led_gpio;
node["ledType"] = settings.led_type;
}
node["hideLed"] = settings.hide_led;
node["noTokenApi"] = settings.notoken_api;
node["readonlyMode"] = settings.readonly_mode;
@@ -2994,14 +3113,17 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
if (result[0] != '\0') {
obj["handlersReceived"] = result; // don't show handlers if there aren't any
}
(void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::FETCHED);
if (result[0] != '\0') {
obj["handlersFetched"] = result;
}
(void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::PENDING);
if (result[0] != '\0') {
obj["handlersPending"] = result;
}
(void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::IGNORED);
if (result[0] != '\0') {
obj["handlersIgnored"] = result;
@@ -3018,24 +3140,28 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
obj["name"] = F_(temperaturesensor);
obj["entities"] = EMSESP::temperaturesensor_.count_entities();
}
if (EMSESP::analogsensor_.count_entities()) {
JsonObject obj = devices.add<JsonObject>();
obj["type"] = F_(analogsensor);
obj["name"] = F_(analogsensor);
obj["entities"] = EMSESP::analogsensor_.count_entities();
}
if (EMSESP::webSchedulerService.count_entities()) {
JsonObject obj = devices.add<JsonObject>();
obj["type"] = F_(scheduler);
obj["name"] = F_(scheduler);
obj["entities"] = EMSESP::webSchedulerService.count_entities();
}
if (EMSESP::webCommandService.count_entities()) {
JsonObject obj = devices.add<JsonObject>();
obj["type"] = F_(commands);
obj["name"] = F_(commands);
obj["entities"] = EMSESP::webCommandService.count_entities();
}
if (EMSESP::webCustomEntityService.count_entities()) {
JsonObject obj = devices.add<JsonObject>();
obj["type"] = F_(custom);
@@ -3200,6 +3326,7 @@ bool System::command_txpause(const char * value, const int8_t) {
#endif
}
}
return true;
}
@@ -3331,6 +3458,7 @@ String System::getBBQKeesGatewayDetails([[maybe_unused]] uint8_t detail) {
uint32_t model : 4;
uint32_t mfg : 4;
};
uint32_t reg;
} gw;
@@ -3422,6 +3550,7 @@ bool System::uploadFirmwareURL(const char * url) {
ssl_client.setBufferSizes(HttpClient::TLS_RX_BUFFER_SIZE, HttpClient::TLS_TX_BUFFER_SIZE);
ssl_client.setSessionTimeout(HttpClient::TLS_SESSION_TIMEOUT_S);
}
basic_client.setTimeout(FIRMWARE_UPLOAD_READ_TIMEOUT_S * 1000); // socket-level read timeout, in ms
ssl_client.setTimeout(FIRMWARE_UPLOAD_READ_TIMEOUT_S); // Stream::readBytes timeout used by Update
ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP
@@ -3479,10 +3608,12 @@ bool System::uploadFirmwareURL(const char * url) {
if (line.isEmpty()) {
break; // end of headers
}
int colon = line.indexOf(':');
if (colon < 0) {
continue;
}
String name = line.substring(0, colon);
name.toLowerCase();
String val = line.substring(colon + 1);
@@ -3501,6 +3632,7 @@ bool System::uploadFirmwareURL(const char * url) {
LOG_ERROR("Firmware upload failed - too many redirects");
return false;
}
String lower_loc = location;
lower_loc.toLowerCase();
if (lower_loc.startsWith("https://") || lower_loc.startsWith("http://")) {
@@ -3511,6 +3643,7 @@ bool System::uploadFirmwareURL(const char * url) {
LOG_ERROR("Firmware upload failed - cross-scheme redirect to %s", location.c_str());
return false;
}
url_remain = location.substring(new_is_https ? 8 : 7);
} else if (location.startsWith("/")) {
url_remain = host + location; // relative redirect, same host
@@ -3518,6 +3651,7 @@ bool System::uploadFirmwareURL(const char * url) {
LOG_ERROR("Firmware upload failed - unsupported redirect to %s", location.c_str());
return false;
}
LOG_DEBUG("Following redirect to %s", url_remain.c_str());
continue;
}
@@ -3540,6 +3674,7 @@ bool System::uploadFirmwareURL(const char * url) {
while (ssl_client.connected() && !ssl_client.available() && millis() - body_wait < FIRMWARE_UPLOAD_RESPONSE_TIMEOUT) {
delay(1);
}
if (!ssl_client.available()) {
ssl_client.stop();
LOG_ERROR("Firmware upload failed - no body received");
@@ -3577,6 +3712,7 @@ bool System::uploadFirmwareURL(const char * url) {
LOG_ERROR("The recovery firmware can't be installed on this board");
return false; // error
}
if (!ota.beginFirmware(firmware_size, factory, false)) {
LOG_ERROR("Recovery firmware upload failed - %s", ota.errorString());
return false; // error
@@ -3611,6 +3747,7 @@ bool System::uploadFirmwareURL(const char * url) {
} else {
Update.abort(); // release the OTA partition handle so a later attempt can start cleanly
}
ssl_client.stop(); // drop the connection
saved_url.clear(); // prevent it from downloading again
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
@@ -3624,13 +3761,16 @@ bool System::uploadFirmwareURL(const char * url) {
if (!ssl_client.connected()) {
break;
}
if (millis() - wait_start > FIRMWARE_UPLOAD_STALL_TIMEOUT) {
break;
}
// also bail out promptly if a cancel arrives mid-stall
if (EMSESP::system_.systemStatus() < SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING) {
break;
}
delay(1);
}
@@ -3639,10 +3779,12 @@ bool System::uploadFirmwareURL(const char * url) {
if (EMSESP::system_.systemStatus() < SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING) {
continue;
}
LOG_ERROR("Firmware upload failed - read stalled at %u of %d bytes", (unsigned)total_read, firmware_size);
if (is_recovery) {
ota.abort();
}
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
return false;
}
@@ -3658,6 +3800,7 @@ bool System::uploadFirmwareURL(const char * url) {
if (is_recovery) {
ota.abort();
}
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
return false;
}
@@ -3670,18 +3813,22 @@ bool System::uploadFirmwareURL(const char * url) {
if (is_recovery) {
ota.abort();
}
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
return false;
}
// the recovery firmware must be for this chip, and really be the recovery
if (is_recovery && !(OtaUpdater::isCompatibleFirmware(buf, n) && OtaUpdater::isRecoveryFirmware(buf, n))) {
LOG_ERROR("Recovery firmware upload failed - not a recovery firmware image for this chip");
if (is_recovery) {
ota.abort();
}
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
return false;
}
magic_ok = true;
}
@@ -3694,6 +3841,7 @@ bool System::uploadFirmwareURL(const char * url) {
if (is_recovery) {
ota.abort();
}
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
return false;
}
@@ -3775,6 +3923,7 @@ bool System::readCommand(const char * data) {
} else if (num_args == 2) {
length = Helpers::atoint(value); // decimal
}
num_args++;
}
}
@@ -4063,6 +4212,7 @@ std::vector<uint8_t> System::available_gpios() {
gpios.push_back(gpio); // didn't find it in used_gpios_, so it's available
}
}
return gpios;
}
@@ -4071,6 +4221,7 @@ void System::make_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<int8
for (const auto & usage : used_gpios_) {
u_gpios.push_back(usage.pin);
}
for (const auto & gpio : valid_system_gpios_) {
s_gpios.push_back(gpio);
}
@@ -4099,6 +4250,7 @@ void System::listDir([[maybe_unused]] const char * dirname, [[maybe_unused]] uin
LOG_DEBUG("Failed to open directory %s", dirname);
return;
}
if (!root.isDirectory()) {
LOG_DEBUG("%s is not a directory", dirname);
return;
@@ -4118,6 +4270,7 @@ void System::listDir([[maybe_unused]] const char * dirname, [[maybe_unused]] uin
std::string line = " (file) " + std::string(file.name()) + " (" + std::to_string(file.size()) + " bytes)";
LOG_DEBUG("%s", line.c_str());
}
file = root.openNextFile();
}
#endif
+7
View File
@@ -308,6 +308,7 @@ class System {
void PSram(uint32_t psram) {
psram_ = psram / 1024;
}
uint32_t PSram() {
return psram_;
}
@@ -315,6 +316,7 @@ class System {
uint32_t appFree() {
return appfree_;
}
uint32_t appUsed() {
return appused_;
}
@@ -323,9 +325,11 @@ class System {
static uint32_t getMaxAllocMem() {
return max_alloc_mem_;
}
static uint32_t getHeapMem() {
return heap_mem_;
}
// All-time low watermark of free internal heap (KB).
// Unlike getHeapMem() (sampled now), this captures the *lowest* free heap
// has ever been since boot — i.e. the worst transient dip during MQTT
@@ -335,6 +339,7 @@ class System {
static uint32_t getMinFreeMem() {
return min_free_mem_;
}
static void refreshHeapMem() {
#ifndef EMSESP_STANDALONE
max_alloc_mem_ = ESP.getMaxAllocHeap() / 1024;
@@ -346,6 +351,7 @@ class System {
static bool test_set_all_active() {
return test_set_all_active_;
}
static void test_set_all_active(bool n) {
#if defined(EMSESP_TEST)
if (n) {
@@ -453,6 +459,7 @@ class System {
uint8_t pin;
std::string source;
};
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
static std::vector<GpioUsage, AllocatorPSRAM<GpioUsage>> used_gpios_; // list of GPIOs used by the application
+29 -1
View File
@@ -56,6 +56,7 @@ uint8_t EMSbus::calculate_crc(const uint8_t * data, const uint8_t length) {
crc = ems_crc_table[crc];
crc ^= data[i++];
}
return crc;
}
@@ -110,6 +111,7 @@ std::string Telegram::to_string() const {
data[2] = this->type_id;
length = 4;
}
for (uint8_t i = 0; i < this->message_length; i++) {
data[length++] = this->message_data[i];
}
@@ -159,12 +161,14 @@ void RxService::add(uint8_t * data, uint8_t length) {
if (!bus_connected()) {
return;
}
if (data[0] != EMSuart::last_tx_src()) { // do not count echos as errors
telegram_error_count_++;
LOG_WARNING("Incomplete Rx: %s (crc: %02X)", Helpers::data_to_hex(data, length).c_str(), crc); // include CRC
} else {
LOG_TRACE("Incomplete Rx: %s (crc: %02X)", Helpers::data_to_hex(data, length).c_str(), crc); // include CRC
}
return;
}
@@ -207,11 +211,13 @@ void RxService::add(uint8_t * data, uint8_t length) {
message_data = data + 6;
message_length = length - 7;
}
// EMS+ boilers broadcast UBAMonitorFastPlus (0xE4); EMS/HT3 use 0x18.
// Extended telegrams alone are not enough: Junkers HT3 also uses them (e.g. 0x16F).
if (type_id == 0xE4) {
set_ems2();
}
// if we're watching and "raw" print out actual telegram as bytes to the console
// including the CRC at the end
if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
@@ -293,8 +299,10 @@ uint8_t TxService::get_send_id() {
count = 0;
return tx_telegrams_.empty() ? ems_bus_id() : tx_telegrams_.front().telegram_->src;
}
return tx_telegrams_.front().telegram_->src;
}
count = 0;
return ems_bus_id();
}
@@ -312,6 +320,7 @@ void TxService::send() {
send_poll();
return;
}
delayed_send_ = 0;
// if we're in read-only mode (tx_mode 0) forget the Tx call
@@ -334,6 +343,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
if (ems_mask() != EMS_MASK_UNSET) {
src ^= ems_mask();
}
telegram_raw[0] = src;
// dest - for READ the MSB must be set
@@ -343,6 +353,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
if (telegram->operation == Telegram::Operation::TX_READ) {
dest |= 0x80; // read has 8th bit set for the destination
}
telegram_raw[1] = dest;
uint8_t message_p = 0; // this is the position in the telegram where we want to put our message data
@@ -390,6 +401,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
telegram_raw[message_p++] = telegram->message_data[i];
}
}
// make a copy of the telegram with new dest (without read-flag)
telegram_last_ = std::make_shared<Telegram>(
telegram->operation, telegram->src, dest & 0x7F, telegram->type_id, telegram->offset, telegram->message_data, telegram->message_length);
@@ -424,6 +436,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
} else {
increment_telegram_write_fail_count(); // another Tx fail
}
tx_state(Telegram::Operation::NONE); // nothing send, tx not in wait state
return;
}
@@ -437,8 +450,10 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
EMSESP::set_read_id(telegram->type_id);
}
}
return;
}
tx_state(telegram->operation); // tx now in a wait state
}
@@ -463,6 +478,7 @@ void TxService::add(const uint8_t operation,
} else {
telegram_read_fail_count_++;
}
tx_telegrams_.pop_front();
}
#endif
@@ -472,6 +488,7 @@ void TxService::add(const uint8_t operation,
} else {
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false, validateid); // add to back of queue
}
if (validateid != 0) {
EMSESP::wait_validate(validateid);
}
@@ -543,6 +560,7 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
} else {
telegram_read_fail_count_++;
}
tx_telegrams_.pop_front();
}
@@ -553,6 +571,7 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
} else {
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false, validate_id); // add to back of queue
}
if (validate_id != 0) {
EMSESP::wait_validate(validate_id);
}
@@ -576,6 +595,7 @@ void TxService::read_request(const uint16_t type_id, const uint8_t dest, const u
if (length > 0 && length < message_data) {
message_data = length;
}
add(Telegram::Operation::TX_READ, dest, type_id, offset, &message_data, 1, 0, front);
}
@@ -597,6 +617,7 @@ bool TxService::send_raw(const char * telegram_data) {
data[count++] = (uint8_t)strtol(p, 0, 16);
p = strtok(nullptr, " ,");
}
free(telegram);
// check valid length
if (count < 4) {
@@ -621,8 +642,10 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
EMSESP::set_response_id(0);
EMSESP::set_read_id(0);
}
return;
}
increment_telegram_read_fail_count(); // another Tx fail
} else {
increment_telegram_write_fail_count(); // another Tx fail
@@ -636,6 +659,7 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
if (operation == Telegram::Operation::TX_READ) {
EMSESP::rxservice_.add_empty(telegram_last_->dest, telegram_last_->src, telegram_last_->type_id, telegram_last_->offset);
}
return;
}
@@ -652,6 +676,7 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
EMSESP::wait_validate(0); // do not wait for validation
return;
}
// for the last try wait 2 sec before sending.
delayed_send_ = (retry_count_ < MAXIMUM_TX_RETRIES) ? 0 : (uuid::get_uptime() + POST_SEND_DELAY);
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram_last_), true, get_post_send_query());
@@ -672,14 +697,17 @@ uint16_t TxService::read_next_tx(const uint8_t offset, const uint8_t length) {
if (offset + old_length >= telegram_last_->offset + telegram_last_->message_data[0]) {
return 0;
}
// we request all and get a short telegram with requested offset
if ((next_length + next_offset) == 0xFF && old_length < max_length - 4 && offset <= telegram_last_->offset) {
return 0;
}
if (offset >= telegram_last_->offset && old_length > 0 && next_length > 0) {
add(Telegram::Operation::TX_READ, telegram_last_->dest, telegram_last_->type_id, next_offset, &next_length, 1, 0, true);
return telegram_last_->type_id;
}
return 0;
}
@@ -713,4 +741,4 @@ uint16_t TxService::post_send_query() {
return post_typeid;
}
} // namespace emsesp
} // namespace emsesp
+9 -1
View File
@@ -210,6 +210,7 @@ class EMSbus {
if ((uuid::get_uptime() - last_bus_activity_) > EMS_BUS_TIMEOUT) {
bus_connected_ = false;
}
return bus_connected_;
#endif
}
@@ -220,6 +221,7 @@ class EMSbus {
if (!last_bus_activity_) {
bus_uptime_start_ = timestamp;
}
last_bus_activity_ = timestamp;
bus_connected_ = true;
}
@@ -229,12 +231,14 @@ class EMSbus {
if (!bus_uptime_start_) {
return 0; // not yet initialized
}
return (uint32_t)((uuid::get_uptime() - bus_uptime_start_) / 1000ULL);
}
static uint8_t tx_state() {
return tx_state_;
}
static void tx_state(uint8_t tx_state) {
tx_state_ = tx_state;
}
@@ -280,6 +284,7 @@ class RxService : public EMSbus {
if (telegram_error_count_ == 0) {
return 100; // all good, 100%
}
uint8_t q = (telegram_error_count_ * 100 / (telegram_count_ + telegram_error_count_));
return (q <= EMS_BUS_QUALITY_RX_THRESHOLD ? 100 : 100 - q);
}
@@ -359,6 +364,7 @@ class TxService : public EMSbus {
uint32_t telegram_read_count() const {
return telegram_read_count_;
}
uint32_t telegram_write_count() const {
return telegram_write_count_;
}
@@ -396,6 +402,7 @@ class TxService : public EMSbus {
if (telegram_read_fail_count_ == 0) {
return 100; // all good, 100%
}
return (100 - (uint8_t)(telegram_read_fail_count_ * 100 / (telegram_read_fail_count_ + telegram_read_count_)));
}
@@ -403,6 +410,7 @@ class TxService : public EMSbus {
if (telegram_write_fail_count_ == 0) {
return 100; // all good, 100%
}
return (100 - (uint8_t)(telegram_write_fail_count_ * 100 / (telegram_write_fail_count_ + telegram_write_count_)));
}
@@ -464,4 +472,4 @@ class TxService : public EMSbus {
} // namespace emsesp
#endif
#endif
+27 -1
View File
@@ -97,6 +97,7 @@ void TemperatureSensor::loop() {
}
}
}
last_activity_ = time_now;
}
} else if (state_ == State::READING) {
@@ -127,6 +128,7 @@ void TemperatureSensor::loop() {
if (!parasite_) {
bus_.depower();
}
if (bus_.crc8(addr, ADDR_LEN - 1) == addr[ADDR_LEN - 1]) {
switch (addr[0]) {
case TYPE_DS18B20:
@@ -147,11 +149,13 @@ void TemperatureSensor::loop() {
publish_sensor(sensor);
changed_ |= true;
}
sensor.read = true;
found = true;
break;
}
}
// add new sensor. this will create the id string, empty name and offset
if (!found && (sensors_.size() < (MAX_SENSORS - 1))) {
// LOG_NOTICE("Adding new sensor for %s", Sensor(addr).id().c_str());
@@ -169,6 +173,7 @@ void TemperatureSensor::loop() {
} else {
sensorfails_++;
}
break;
default:
@@ -184,6 +189,7 @@ void TemperatureSensor::loop() {
if (!parasite_) {
bus_.depower();
}
// check for missing sensors after some samples
if (++scancnt_ > SCAN_MAX) {
for (auto & sensor : sensors_) {
@@ -191,8 +197,10 @@ void TemperatureSensor::loop() {
sensor.temperature_c = EMS_VALUE_INT16_NOTSET;
changed_ = true;
}
sensor.read = false;
}
scancnt_ = 0;
} else if (scancnt_ == SCAN_START + 1) { // startup
firstscan_ = sensors_.size();
@@ -208,11 +216,13 @@ void TemperatureSensor::loop() {
}
}
}
s->set_name("gateway_temperature");
s->set_is_system(true); // mark as internal system temperature sensor
if (!EMSESP::nvs_.isKey("intTemp")) {
EMSESP::nvs_.putString("intTemp", s->id());
}
LOG_DEBUG("Adding system sensor for gateway_temperature");
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
auto newSensor = SensorCustomization();
@@ -224,11 +234,13 @@ void TemperatureSensor::loop() {
return StateUpdateResult::CHANGED;
});
}
// LOG_NOTICE("Adding %d sensor(s) from first scan", firstscan_);
} else if ((scancnt_ <= 0) && (firstscan_ != sensors_.size())) { // check 2 times for no change of sensor #
scancnt_ = SCAN_START;
sensors_.clear(); // restart scanning and clear to get correct numbering
}
state_ = State::IDLE;
}
}
@@ -241,6 +253,7 @@ bool TemperatureSensor::temperature_convert_complete(const uint32_t time) {
if (parasite_) {
return time > CONVERSION_MS; // don't care, use the datasheet time
}
return bus_.read_bit() == 1;
#else
return time > CONVERSION_MS;
@@ -300,6 +313,7 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
break;
}
}
raw_value = ((int32_t)raw_value * 625 + 500) / 1000; // round to 0.1
return raw_value;
#else
@@ -338,6 +352,7 @@ bool TemperatureSensor::update(const char * id, const char * name, int16_t offse
break;
}
}
if (!found) {
auto newSensor = SensorCustomization();
strlcpy(newSensor.id, id, sizeof(newSensor.id));
@@ -347,6 +362,7 @@ bool TemperatureSensor::update(const char * id, const char * name, int16_t offse
settings.sensorCustomizations.push_back(newSensor);
LOG_DEBUG("Adding new customization for sensor ID %s", id);
}
sensor.ha_registered = false; // it's changed so we may need to recreate the HA config
return StateUpdateResult::CHANGED;
});
@@ -363,6 +379,7 @@ bool TemperatureSensor::updated_values() {
changed_ = false;
return true;
}
return false;
}
@@ -380,6 +397,7 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
output[sensor.name()] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
}
}
return true;
}
@@ -387,6 +405,7 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
for (const auto & sensor : sensors_) {
get_value_json(output[sensor.name()].to<JsonObject>(), sensor);
}
return true;
}
@@ -396,6 +415,7 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
output["api_data"] = metrics;
return true;
}
return false;
}
@@ -425,6 +445,7 @@ std::string TemperatureSensor::get_metrics_prometheus() {
result +=
(std::string) "emsesp_" + sensor.name() + " " + Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0) + "\n";
}
return result;
}
@@ -455,9 +476,11 @@ void TemperatureSensor::publish_sensor(const Sensor & sensor) {
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", F_(temperaturesensor), "_data", sensor.name());
}
char payload[10];
Mqtt::queue_publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
}
char cmd[COMMAND_MAX_LENGTH];
snprintf(cmd, sizeof(cmd), "%s/%s", F_(temperaturesensor), sensor.name());
EMSESP::webSchedulerService.onChange(cmd);
@@ -491,6 +514,7 @@ void TemperatureSensor::publish_values(const bool force) {
for (const auto & sensor : sensors_) {
publish_sensor(sensor);
}
return;
} else if (!EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
return; // wait for first time period
@@ -609,6 +633,7 @@ const char * TemperatureSensor::Sensor::name() const {
if (name_[0] == '\0') {
return id_;
}
return name_;
}
@@ -628,6 +653,7 @@ bool TemperatureSensor::Sensor::apply_customization() {
}
}
}
return false;
});
@@ -669,4 +695,4 @@ void TemperatureSensor::load_test_data() {
}
#endif
} // namespace emsesp
} // namespace emsesp
+4 -1
View File
@@ -48,6 +48,7 @@ class TemperatureSensor {
bool is_system() const {
return is_system_;
}
void set_is_system(const bool is_system) {
is_system_ = is_system;
}
@@ -59,6 +60,7 @@ class TemperatureSensor {
int16_t offset() const {
return offset_;
}
void set_offset(const int16_t offset) {
offset_ = offset;
}
@@ -124,6 +126,7 @@ class TemperatureSensor {
if (exclude_disabled_system) {
return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) { return !sensor.is_system(); });
}
return sensors_.size();
}
@@ -192,4 +195,4 @@ class TemperatureSensor {
} // namespace emsesp
#endif
#endif
+2 -1
View File
@@ -32,9 +32,10 @@ Alert::Alert(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setFlowTemp_, DeviceValueType::UINT8, FL_(setFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setBurnPow_, DeviceValueType::UINT8, FL_(setBurnPow), DeviceValueUOM::PERCENT);
}
// UBASetPoint 0x1A
void Alert::process_UBASetPoints(const std::shared_ptr<const Telegram> & telegram) {
has_update(telegram, setFlowTemp_, 0); // boiler set temp from thermostat
has_update(telegram, setBurnPow_, 1); // max burner power in %
}
} // namespace emsesp
} // namespace emsesp
+114 -2
View File
@@ -282,6 +282,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
0,
10);
}
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&heatingActivated_,
DeviceValueType::BOOL,
@@ -753,6 +754,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
DeviceValueUOM::NONE,
MAKE_CF_CB(set_maxHeatDhw));
}
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&auxHeaterSource_,
DeviceValueType::ENUM,
@@ -1102,6 +1104,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
120,
240);
}
register_device_value(DeviceValueTAG::TAG_DHW1,
&wwCircMode_,
DeviceValueType::ENUM,
@@ -1192,12 +1195,15 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
if (nrgHeatF_ < 0 || nrgHeatF_ >= EMS_VALUE_UINT32_NOTSET) {
nrgHeatF_ = 0;
}
if (nrgWwF_ < 0 || nrgWwF_ >= EMS_VALUE_UINT32_NOTSET) {
nrgWwF_ = 0;
}
if (nomPower_ == EMS_VALUE_UINT8_NOTSET) {
nomPower_ = 0;
}
store_energy();
// update/publish the values
has_update(nrgHeat_, (uint32_t)(nrgHeatF_ + 0.5));
@@ -1211,12 +1217,15 @@ void Boiler::store_energy() {
if (nrgHeatF_ != EMSESP::nvs_.getDouble("nrgheat")) {
EMSESP::nvs_.putDouble("nrgheat", nrgHeatF_);
}
if (nrgWwF_ != EMSESP::nvs_.getDouble("nrgww")) {
EMSESP::nvs_.putDouble("nrgww", nrgWwF_);
}
if (nomPower_ != EMSESP::nvs_.getUChar("nompower")) {
EMSESP::nvs_.putUChar("nompower", nomPower_);
}
// LOG_DEBUG("energy values stored");
}
@@ -1250,6 +1259,7 @@ void Boiler::check_active() {
if (Helpers::hasValue(wwCurFlow_) && (wwCurFlow_ > 0) && (wwType_ == 1)) {
flowsensor = true;
}
if (flowsensor) {
b = ((wwCurFlow_ > 0) && ((boilerState_ & 0x0A) == 0x0A));
} else {
@@ -1271,6 +1281,7 @@ void Boiler::check_active() {
EMSESP::webSettingsService.read([&](WebSettings & settings) { forceHeatingOff_ = settings.boiler_heatingoff ? EMS_VALUE_BOOL_ON : 0; });
has_update(&forceHeatingOff_);
}
static uint32_t lastSendHeatingOff = 0;
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && (uuid::get_uptime_sec() - lastSendHeatingOff) >= 60) {
lastSendHeatingOff = uuid::get_uptime_sec();
@@ -1278,6 +1289,7 @@ void Boiler::check_active() {
uint8_t data[] = {1, 0, 0, 1, 1};
write_command(EMS_TYPE_UBASetPoints2, 0, data, sizeof(data), 0);
}
uint8_t data[] = {0, 0, 0, 0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
}
@@ -1304,6 +1316,7 @@ void Boiler::check_active() {
} else if (curBurnPow_ == 0 && (heatBurnPow + wwBurnPow) > 0) { // on burner switch off
store_energy();
}
// store new modulation and time
heatBurnPow = heatingActive_ && !tapwaterActive_ ? curBurnPow_ : 0;
wwBurnPow = tapwaterActive_ ? curBurnPow_ : 0;
@@ -1319,6 +1332,7 @@ void Boiler::process_UBAFactory(const std::shared_ptr<const Telegram> & telegram
if (telegram->offset > 4 || telegram->offset + telegram->message_length < 7) {
return;
}
toggle_fetch(telegram->type_id, false); // only read once
uint8_t min = 0;
uint8_t max = 0;
@@ -1330,6 +1344,7 @@ void Boiler::process_UBAFactory(const std::shared_ptr<const Telegram> & telegram
if (nomPower > 0 && nomPower_ == 0) {
has_update(nomPower_, nomPower);
}
if (min <= max) {
// set_minmax(&burnMinPower_, 0, max); // removed #2918
set_minmax(&burnMaxPower_, min, max);
@@ -1389,6 +1404,7 @@ void Boiler::process_UBAMonitorFast(const std::shared_ptr<const Telegram> & tele
curBurnPow = 100;
selBurnPow = 100;
}
has_update(selBurnPow_, selBurnPow);
has_update(curBurnPow_, curBurnPow);
@@ -1464,6 +1480,7 @@ void Boiler::process_UBAParameterWW(const std::shared_ptr<const Telegram> & tele
} else {
wwComfort = EMS_VALUE_UINT8_NOTSET;
}
has_update(wwComfort_, wwComfort);
}
}
@@ -1520,16 +1537,18 @@ void Boiler::process_UBAMonitorFastPlus(const std::shared_ptr<const Telegram> &
if (syspress == 0) {
syspress = EMS_VALUE_UINT8_NOTSET;
}
has_update(sysPress_, syspress);
has_update(sysPress_, syspress);
//has_update(telegram, temperatur_, 13); // unknown temperature
has_update(telegram, heatblock_, 23); // see #1317
has_update(telegram, headertemp_, 25); // see #1317
//has_update(telegram, temperatur_, 27); // unknown temperature
telegram->read_value(exhaustTemp1_, 31);
if (Helpers::hasValue(exhaustTemp1_)) {
has_update(exhaustTemp_, exhaustTemp1_);
}
// heating sends a second 0xE4 with 0xFFFF here, which would clear a valid flow, #3224
uint16_t pc0flow = pc0Flow_;
if (telegram->read_value(pc0flow, 36) && Helpers::hasValue(pc0flow)) { // see https://github.com/emsesp/EMS-ESP32/issues/2001
@@ -1597,10 +1616,12 @@ void Boiler::process_UBAMonitorSlowPlus2(const std::shared_ptr<const Telegram> &
if (telegram->read_value(state, 2)) {
boilerState_ |= state == 1 ? 0x09 : 0; // heating 0/1
}
state = EMS_VALUE_UINT8_NOTSET;
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
}
}
@@ -1620,6 +1641,7 @@ void Boiler::process_UBAMonitorSlowPlus(const std::shared_ptr<const Telegram> &
if (!Helpers::hasValue(exhaustTemp1_)) {
has_update(telegram, exhaustTemp_, 6);
}
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, burn2WorkMin_, 16, 3); // force to 3 bytes
@@ -1686,6 +1708,7 @@ void Boiler::process_UBAParameterWWPlus(const std::shared_ptr<const Telegram> &
} else {
wwComfort1 = EMS_VALUE_UINT8_NOTSET;
}
has_update(wwComfort1_, wwComfort1);
}
}
@@ -1779,6 +1802,7 @@ void Boiler::process_HpPower(const std::shared_ptr<const Telegram> & telegram) {
if (coolingType_ == 3) {
has_bitupdate(telegram, hp4wayValve_, 0, 3); // https://github.com/emsesp/EMS-ESP32/issues/2844#issuecomment-3869770845
}
has_bitupdate(telegram, elHeatStep1_, 3, 0);
has_bitupdate(telegram, elHeatStep2_, 3, 1);
has_bitupdate(telegram, elHeatStep3_, 3, 2);
@@ -1797,7 +1821,7 @@ void Boiler::process_HpPower(const std::shared_ptr<const Telegram> & telegram) {
has_bitupdate(telegram, hpInput[2].state, 1, 6);
has_bitupdate(telegram, hpInput[3].state, 1, 7);
// from https://github.com/emsesp/EMS-ESP32/issues/2669
// https://github.com/emsesp/EMS-ESP32/issues/2669
has_update(telegram, hpTargetSpd_, 22);
has_update(telegram, receiverValveVr0_, 15);
has_update(telegram, expansionValveVr1_, 16);
@@ -1860,11 +1884,13 @@ void Boiler::process_HpInConfig(const std::shared_ptr<const Telegram> & telegram
telegram->read_value(option[j], index[j] + i);
option[j] = option[j] == 1 ? '1' : '0';
}
option[12] = atoi(&hpInput[i].option[12]);
telegram->read_value(option[12], 27 + i); // modulation
Helpers::smallitoa(&option[12], (uint16_t)option[12]);
has_update(hpInput[i].option, option, 16);
}
// input 4 <inv>[<comp><aux><cool><heat><dhw><pv><prot><pres><mod>]
uint8_t index4[] = {42, 43, 44, 45, 46, 47, 52, 50, 49, 48};
for (uint8_t j = 0; j < 9; j++) {
@@ -1872,6 +1898,7 @@ void Boiler::process_HpInConfig(const std::shared_ptr<const Telegram> & telegram
telegram->read_value(option[j], index4[j]);
option[j] = option[j] == 1 ? '1' : '0';
}
option[9] = atoi(&hpInput[3].option[9]);
telegram->read_value(option[9], 48); // modulation
Helpers::smallitoa(&option[9], (uint16_t)option[9]);
@@ -1893,6 +1920,7 @@ void Boiler::process_HpHeaterConfig(const std::shared_ptr<const Telegram> & tele
has_enumupdate(telegram, maxHeatDhw_, 4, {0, 2, 4, 5});
return;
}
has_update(telegram, maxHeatComp_, 2);
has_update(telegram, maxHeatHeat_, 3);
has_update(telegram, maxHeatDhw_, 4);
@@ -1973,6 +2001,7 @@ void Boiler::process_UBAErrorMessage(const std::shared_ptr<const Telegram> & tel
if (telegram->offset > 0 || telegram->message_length < 11) {
return;
}
// data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr
static uint32_t lastCodeDate_ = 0; // last code date
uint8_t code[3] = {telegram->message_data[0], telegram->message_data[1], 0};
@@ -1992,8 +2021,10 @@ void Boiler::process_UBAErrorMessage(const std::shared_ptr<const Telegram> & tel
has_update(lastCode_, newCode, sizeof(lastCode_));
lastCodeDate_ = 1;
}
return;
}
// store only the newest code from telegrams 10 and 11
if (date > lastCodeDate_) {
lastCodeDate_ = date;
@@ -2063,6 +2094,7 @@ void Boiler::process_UBAErrorMessage2(const std::shared_ptr<const Telegram> & te
snprintf(&code[3], sizeof(code) - 3, "(%d) @uptime %lu - %lu min", codeNo, starttime, endtime);
date = starttime;
}
if (date > lastCodeDate_) {
lastCodeDate_ = date;
has_update(lastCode_, code, sizeof(lastCode_));
@@ -2129,6 +2161,7 @@ void Boiler::process_UBAErrorMessage3(const std::shared_ptr<const Telegram> & te
snprintf(&code[3], sizeof(code) - 3, "(%d) @uptime %lu - %lu min", codeNo, starttime, endtime);
date = starttime;
}
if (date > lastCodeDate_) {
lastCodeDate_ = date;
has_update(lastCode_, code, sizeof(lastCode_));
@@ -2357,6 +2390,7 @@ bool Boiler::set_keepWarmTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x772, 1, v, 0x772);
return true;
}
@@ -2366,6 +2400,7 @@ bool Boiler::set_returnTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x772, 2, v, 0x772);
return true;
}
@@ -2529,6 +2564,7 @@ bool Boiler::set_ww_disinfect_duration(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v, 120, 240)) { // HPC410 menu: 120-240 min in steps of 10
return false;
}
write_command(EMS_TYPE_UBAParameterWWPlus, 15, (v + 5) / 10, EMS_TYPE_UBAParameterWWPlus);
return true;
}
@@ -2539,6 +2575,7 @@ bool Boiler::set_ww_prio(const char * value, const int8_t) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x2CC, 3, b ? 0xFF : 0, 0x2CC);
return true;
}
@@ -2557,6 +2594,7 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) {
if (has_telegram_id(0xE4)) {
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints2, 1, &v1, 1, 0, false);
}
// always write to 0x1A
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, &v1, 1, 0, false);
return true;
@@ -2569,6 +2607,7 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) {
} else {
write_command(EMS_TYPE_UBASetPoints, 0, v, 0x18);
}
return true;
}
@@ -2585,6 +2624,7 @@ bool Boiler::set_burn_power(const char * value, const int8_t id) {
} else {
write_command(EMS_TYPE_UBASetPoints, 1, v);
}
return true;
}
@@ -2778,6 +2818,7 @@ bool Boiler::set_pumpMode(const char * value, const int8_t id) {
write_command(EMS_TYPE_UBAParameters, 11, v, EMS_TYPE_UBAParameters);
return true;
}
return false;
}
@@ -2787,6 +2828,7 @@ bool Boiler::set_pumpCharacter(const char * value, const int8_t id) {
write_command(EMS_TYPE_UBAParametersPlus, 15, v, EMS_TYPE_UBAParametersPlus);
return true;
}
return false;
}
@@ -2889,6 +2931,7 @@ bool Boiler::set_ww_mode(const char * value, const int8_t id) {
return true;
}
}
return false;
}
@@ -3074,6 +3117,7 @@ bool Boiler::set_reset(const char * value, const int8_t id) {
write_command(0x05, 6, 154);
return true;
}
return false;
}
@@ -3082,6 +3126,7 @@ bool Boiler::set_manDefrost(const char * value, const int8_t) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x05, 52, b ? 1 : 0);
return true;
}
@@ -3091,6 +3136,7 @@ bool Boiler::set_chimneysweeper(const char * value, const int8_t) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x05, 0x04, b ? 100 : 0);
return true;
}
@@ -3122,6 +3168,7 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
LOG_WARNING("Setting maintenance: wrong format %d.%d.%d", day, month, year + 2000);
return false;
}
return true;
}
@@ -3145,6 +3192,7 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
LOG_WARNING("Setting maintenance: wrong format");
return false;
}
//maintenance
bool Boiler::set_maintenancetime(const char * value, const int8_t id) {
int hrs;
@@ -3156,6 +3204,7 @@ bool Boiler::set_maintenancetime(const char * value, const int8_t id) {
return true;
}
}
LOG_WARNING("Setting maintenance: wrong format");
return false;
}
@@ -3174,6 +3223,7 @@ bool Boiler::set_maintenancedate(const char * value, const int8_t id) {
LOG_WARNING("Setting maintenance: wrong format %d.%d.%d", day, month, year + 2000);
return false;
}
return true;
}
@@ -3187,6 +3237,7 @@ bool Boiler::set_pool_temp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
uint8_t v2 = ((v * 2) + 0.5);
// LOG_INFO("Setting pool temperature to %d.%d C", v2 >> 1, (v2 & 0x01) * 5);
write_command(0x48A, 1, v2, 0x48A);
@@ -3200,6 +3251,7 @@ bool Boiler::set_pool_on(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x48A, 0, v ? 0x01 : 0x00, 0x48A);
return true;
}
@@ -3220,6 +3272,7 @@ bool Boiler::set_emergency_ops(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(EMS_TYPE_UBAParametersPlus, 18, v ? 0x01 : 0x00, EMS_TYPE_UBAParametersPlus);
return true;
}
@@ -3228,11 +3281,13 @@ bool Boiler::set_HpInLogic(const char * value, const int8_t id) {
if (id == 0 || id > 4 || strlen(value) > 15) {
return false;
}
bool v;
if (strlen(value) == 1 && Helpers::value2bool(value, v)) {
write_command(0x486, id == 4 ? 42 : id - 1, v ? 1 : 0, 0x486);
return true;
}
char option[] = {"xxxxxxxxxxxxxxx"};
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>]
@@ -3243,9 +3298,11 @@ bool Boiler::set_HpInLogic(const char * value, const int8_t id) {
write_command(0x486, index[i] + id - 1, option[i] - '0');
}
}
if (option[12] >= '0' && option[12] <= '9') {
write_command(0x486, 27, (uint8_t)atoi(&option[12]));
}
return true;
}
@@ -3256,9 +3313,11 @@ bool Boiler::set_HpInLogic(const char * value, const int8_t id) {
write_command(0x486, index4[i], option[i] - '0');
}
}
if (option[9] >= '0' && option[9] <= '9') {
write_command(0x486, 48, (uint8_t)atoi(&option[9]));
}
return true;
}
@@ -3279,6 +3338,7 @@ bool Boiler::set_maxHeat(const char * value, const int8_t id) {
return false;
}
}
write_command(0x492, id, v, 0x492);
return true;
}
@@ -3288,6 +3348,7 @@ bool Boiler::set_silentMode(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_silentMode))) {
return false;
}
write_command(0x484, 10, v, 0x484);
return true;
}
@@ -3296,6 +3357,7 @@ bool Boiler::set_silentFrom(const char * value, const int8_t id) {
if (value == nullptr || value[0] < '0' || value[0] > '9') {
return false;
}
auto v = Helpers::string2minutes(value);
write_command(0x484, 52, v / 15, 0x484);
return true;
@@ -3305,6 +3367,7 @@ bool Boiler::set_silentTo(const char * value, const int8_t id) {
if (value == nullptr || value[0] < '0' || value[0] > '9') {
return false;
}
auto v = Helpers::string2minutes(value);
write_command(0x484, 53, v / 15, 0x484);
return true;
@@ -3316,6 +3379,7 @@ bool Boiler::set_minTempSilent(const char * value, const int8_t id) {
write_command(0x484, 11, v, 0x484);
return true;
}
return false;
}
@@ -3325,6 +3389,7 @@ bool Boiler::set_additionalHeaterOnly(const char * value, const int8_t id) {
write_command(0x491, model() == EMSdevice::EMS_DEVICE_FLAG_CS6800 ? 3 : 1, v ? 1 : 0, 0x491);
return true;
}
return false;
}
@@ -3334,6 +3399,7 @@ bool Boiler::set_additionalHeater(const char * value, const int8_t id) {
write_command(0x491, 2, v ? 1 : 0, 0x491);
return true;
}
return false;
}
@@ -3343,6 +3409,7 @@ bool Boiler::set_tempParMode(const char * value, const int8_t id) {
write_command(0x491, 5, v, 0x491);
return true;
}
return false;
}
@@ -3354,6 +3421,7 @@ bool Boiler::set_additionalHeaterDelay(const char * value, const int8_t id) {
write_command(0x491, 16, data, 2, 0x491);
return true;
}
return false;
}
@@ -3363,6 +3431,7 @@ bool Boiler::set_auxHeatMode(const char * value, const int8_t id) {
write_command(0x491, 4, v, 0x491);
return true;
}
return false;
}
@@ -3372,6 +3441,7 @@ bool Boiler::set_auxLimit(const char * value, const int8_t id) {
write_command(0x491, id, (uint8_t)(v * 10), 0x491);
return true;
}
return false;
}
@@ -3381,6 +3451,7 @@ bool Boiler::set_auxHeaterSource(const char * value, const int8_t) {
write_command(0x491, 0, v, 0x491);
return true;
}
return false;
}
@@ -3390,6 +3461,7 @@ bool Boiler::set_pvCooling(const char * value, const int8_t id) {
write_command(0x485, 21, v ? 1 : 0, 0x485);
return true;
}
return false;
}
@@ -3401,6 +3473,7 @@ bool Boiler::set_hpHyst(const char * value, const int8_t id) {
write_command(0x484, id, data, 2, 0x484);
return true;
}
return false;
}
@@ -3410,6 +3483,7 @@ bool Boiler::set_tempDiff(const char * value, const int8_t id) {
write_command(0x48B, id, (uint8_t)(v * 10), 0x48B);
return true;
}
return false;
}
@@ -3420,6 +3494,7 @@ bool Boiler::set_wwOffTemp(const char * value, const int8_t id) {
write_command(0x499, id, v, 0x499);
return true;
}
return false;
}
@@ -3429,6 +3504,7 @@ bool Boiler::set_wwDiffTemp(const char * value, const int8_t id) {
write_command(0x499, id, v, 0x499);
return true;
}
return false;
}
@@ -3438,6 +3514,7 @@ bool Boiler::set_hpCircPumpWw(const char * value, const int8_t id) {
write_command(0x484, 46, v ? 1 : 0, 0x484);
return true;
}
return false;
}
@@ -3447,6 +3524,7 @@ bool Boiler::set_hpPumpMode(const char * value, const int8_t id) {
write_command(0x48B, 18, v, 0x48B);
return true;
}
return false;
}
@@ -3456,6 +3534,7 @@ bool Boiler::set_hpMaxPower(const char * value, const int8_t id) {
write_command(0x484, 31, v, 0x484);
return true;
}
return false;
}
@@ -3465,6 +3544,7 @@ bool Boiler::set_pvMaxComp(const char * value, const int8_t) {
write_command(0x484, 54, (uint8_t)(v * 10), 0x484);
return true;
}
return false;
}
@@ -3474,6 +3554,7 @@ bool Boiler::set_hpDiffPress(const char * value, const int8_t id) {
write_command(0x2CC, 9, (uint8_t)(v / 50), 0x2CC);
return true;
}
return false;
}
@@ -3484,6 +3565,7 @@ bool Boiler::set_hpPowerLimit(const char * value, const int8_t id) {
write_command(0x4A7, 0, data, 2, 0x4A7);
return true;
}
return false;
}
@@ -3493,6 +3575,7 @@ bool Boiler::set_powerReduction(const char * value, const int8_t) {
write_command(0x484, 64, v / 10, 0x484);
return true;
}
return false;
}
@@ -3502,6 +3585,7 @@ bool Boiler::set_vp_cooling(const char * value, const int8_t id) {
write_command(0x49C, 3, v ? 1 : 0, 0x49C);
return true;
}
return false;
}
@@ -3511,6 +3595,7 @@ bool Boiler::set_heatCable(const char * value, const int8_t id) {
write_command(0x49D, 2, v ? 1 : 0, 0x49D);
return true;
}
return false;
}
@@ -3520,6 +3605,7 @@ bool Boiler::set_VC0valve(const char * value, const int8_t id) {
write_command(0x49D, 3, v ? 1 : 0, 0x49D);
return true;
}
return false;
}
@@ -3529,6 +3615,7 @@ bool Boiler::set_primePump(const char * value, const int8_t id) {
write_command(0x49D, 4, v ? 1 : 0, 0x49D);
return true;
}
return false;
}
@@ -3538,6 +3625,7 @@ bool Boiler::set_primePumpMod(const char * value, const int8_t id) {
write_command(0x49D, 5, v, 0x49D);
return true;
}
return false;
}
@@ -3547,6 +3635,7 @@ bool Boiler::set_hp3wayValve(const char * value, const int8_t id) {
write_command(0x49D, 6, v ? 1 : 0, 0x49D);
return true;
}
return false;
}
@@ -3556,6 +3645,7 @@ bool Boiler::set_elHeatStep(const char * value, const int8_t id) {
write_command(0x49D, 6 + id, v ? 1 : 0, 0x49D);
return true;
}
return false;
}
@@ -3565,6 +3655,7 @@ bool Boiler::set_wwAlternatingOper(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(EMS_TYPE_UBAParameterWWPlus, 14, v ? 1 : 0, EMS_TYPE_UBAParameterWWPlus);
return true;
}
@@ -3576,6 +3667,7 @@ bool Boiler::set_wwAltOpPrio(const char * value, const int8_t id) {
write_command(0x484, id, v, 0x484);
return true;
}
return false;
}
@@ -3588,15 +3680,18 @@ bool Boiler::set_forceHeatingOff(const char * value, const int8_t id) {
uint8_t data[] = {1, heatingTemp_, (Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100), 1, 1};
write_command(EMS_TYPE_UBASetPoints2, 0, data, sizeof(data), 0);
}
uint8_t data[] = {heatingTemp_,
(Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100),
(Helpers::hasValue(pumpModMax_) ? pumpModMax_ : (uint8_t)100),
0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
}
has_update(forceHeatingOff_, v);
return true;
}
return false;
}
@@ -3606,10 +3701,12 @@ bool Boiler::set_nrgHeat(const char * value, const int8_t id) {
if (!Helpers::value2float(value, v)) {
return false;
}
if (v >= 0) {
nrgHeatF_ = v * 100;
has_update(nrgHeat_, (uint32_t)(nrgHeatF_ + 0.5));
}
store_energy();
return true;
}
@@ -3619,10 +3716,12 @@ bool Boiler::set_nrgWw(const char * value, const int8_t id) {
if (!Helpers::value2float(value, v)) {
return false;
}
if (v >= 0) {
nrgWwF_ = v * 100;
has_update(nrgWw_, (uint32_t)(nrgWwF_ + 0.5));
}
store_energy();
return true;
}
@@ -3632,9 +3731,11 @@ bool Boiler::set_nomPower(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
if (v > 0 && v < EMS_VALUE_UINT8_NOTSET) {
has_update(nomPower_, (uint8_t)v);
}
store_energy();
return true;
}
@@ -3645,6 +3746,7 @@ bool Boiler::set_curveOn(const char * value, const int8_t id) {
write_command(0x28, 0, v ? 0xFF : 0);
return true;
}
return false;
}
@@ -3653,6 +3755,7 @@ bool Boiler::set_curveBase(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x28, 2, v);
return true;
}
@@ -3662,6 +3765,7 @@ bool Boiler::set_curveEnd(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x28, 1, v);
return true;
}
@@ -3671,6 +3775,7 @@ bool Boiler::set_summerTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x28, 3, v);
return true;
}
@@ -3681,6 +3786,7 @@ bool Boiler::set_nofrost(const char * value, const int8_t id) {
write_command(0x28, 4, v ? 0xFF : 0);
return true;
}
return false;
}
@@ -3689,6 +3795,7 @@ bool Boiler::set_nofrostTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x28, 5, v);
return true;
}
@@ -3699,6 +3806,7 @@ bool Boiler::set_fan(const char * value, const int8_t id) {
write_command(0x4A5, 9, (uint8_t)v, 0x4A5);
return true;
}
return false;
}
@@ -3708,6 +3816,7 @@ bool Boiler::set_shutdown(const char * value, const int8_t id) {
write_command(0x484, 58, b ? 1 : 0, 0x484);
return true;
}
return false;
}
@@ -3717,6 +3826,7 @@ bool Boiler::set_pumpKickHour(const char * value, const int8_t) {
write_command(0xEB, 0, v, 0xEB);
return true;
}
return false;
}
@@ -3726,6 +3836,7 @@ bool Boiler::set_pumpKickDay(const char * value, const int8_t) {
write_command(0xEB, 1, v + 1, 0xEB);
return true;
}
return false;
}
@@ -3736,6 +3847,7 @@ bool Boiler::set_pumpKickDelay(const char * value, const int8_t) {
write_command(0xEB, 2, data, 2, 0xEB);
return true;
}
return false;
}
+32 -1
View File
@@ -34,6 +34,7 @@ class Boiler : public EMSdevice {
inline uint8_t model() const {
return (flags() & 0x0F);
}
inline bool isHeatPump() const {
return flags() & 0x08;
}
@@ -454,9 +455,11 @@ class Boiler : public EMSdevice {
inline bool set_hyst2_on(const char * value, const int8_t id) {
return set_hyst_on(value, 2);
}
inline bool set_hyst2_off(const char * value, const int8_t id) {
return set_hyst_off(value, 2);
}
bool set_burn_period(const char * value, const int8_t id);
bool set_pump_delay(const char * value, const int8_t id);
bool set_reset(const char * value, const int8_t id);
@@ -476,25 +479,32 @@ class Boiler : public EMSdevice {
inline bool set_HpIn1Logic(const char * value, const int8_t id) {
return set_HpInLogic(value, 1);
}
inline bool set_HpIn2Logic(const char * value, const int8_t id) {
return set_HpInLogic(value, 2);
}
inline bool set_HpIn3Logic(const char * value, const int8_t id) {
return set_HpInLogic(value, 3);
}
inline bool set_HpIn4Logic(const char * value, const int8_t id) {
return set_HpInLogic(value, 4);
}
bool set_maxHeat(const char * value, const int8_t id);
inline bool set_maxHeatComp(const char * value, const int8_t id) {
return set_maxHeat(value, 2);
}
inline bool set_maxHeatHeat(const char * value, const int8_t id) {
return set_maxHeat(value, 3);
}
inline bool set_maxHeatDhw(const char * value, const int8_t id) {
return set_maxHeat(value, 4);
}
bool set_silentMode(const char * value, const int8_t id);
bool set_minTempSilent(const char * value, const int8_t id);
bool set_silentFrom(const char * value, const int8_t id);
@@ -518,55 +528,71 @@ class Boiler : public EMSdevice {
inline bool set_auxMaxLimit(const char * value, const int8_t id) {
return set_auxLimit(value, 14);
}
inline bool set_auxLimitStart(const char * value, const int8_t id) {
return set_auxLimit(value, 15);
}
bool set_hpHyst(const char * value, const int8_t id);
inline bool set_hpHystHeat(const char * value, const int8_t id) {
return set_hpHyst(value, 37);
}
inline bool set_hpHystCool(const char * value, const int8_t id) {
return set_hpHyst(value, 35);
}
inline bool set_hpHystPool(const char * value, const int8_t id) {
return set_hpHyst(value, 33);
}
bool set_tempDiff(const char * value, const int8_t id);
inline bool set_tempDiffHeat(const char * value, const int8_t id) {
return set_tempDiff(value, 4);
}
inline bool set_tempDiffCool(const char * value, const int8_t id) {
return set_tempDiff(value, 3);
}
bool set_wwOffTemp(const char * value, const int8_t id);
inline bool set_wwComfOffTemp(const char * value, const int8_t id) {
return set_wwOffTemp(value, 1);
}
inline bool set_wwEcoOffTemp(const char * value, const int8_t id) {
return set_wwOffTemp(value, 0);
}
inline bool set_wwEcoPlusOffTemp(const char * value, const int8_t id) {
return set_wwOffTemp(value, 5);
}
bool set_wwDiffTemp(const char * value, const int8_t id);
inline bool set_wwComfDiffTemp(const char * value, const int8_t id) {
return set_wwDiffTemp(value, 12);
}
inline bool set_wwEcoDiffTemp(const char * value, const int8_t id) {
return set_wwDiffTemp(value, 13);
}
inline bool set_wwEcoPlusDiffTemp(const char * value, const int8_t id) {
return set_wwDiffTemp(value, 14);
}
inline bool set_wwComfStopTemp(const char * value, const int8_t id) {
return set_wwOffTemp(value, 8);
}
inline bool set_wwEcoStopTemp(const char * value, const int8_t id) {
return set_wwOffTemp(value, 9);
}
inline bool set_wwEcoPlusStopTemp(const char * value, const int8_t id) {
return set_wwOffTemp(value, 10);
}
bool set_vp_cooling(const char * value, const int8_t id);
bool set_heatCable(const char * value, const int8_t id);
bool set_VC0valve(const char * value, const int8_t id);
@@ -577,20 +603,25 @@ class Boiler : public EMSdevice {
inline bool set_elHeatStep1(const char * value, const int8_t id) {
return set_elHeatStep(value, 1);
}
inline bool set_elHeatStep2(const char * value, const int8_t id) {
return set_elHeatStep(value, 2);
}
inline bool set_elHeatStep3(const char * value, const int8_t id) {
return set_elHeatStep(value, 3);
}
bool set_wwAlternatingOper(const char * value, const int8_t id);
bool set_wwAltOpPrio(const char * value, const int8_t id);
inline bool set_wwAltOpPrioHeat(const char * value, const int8_t id) {
return set_wwAltOpPrio(value, 2);
}
inline bool set_wwAltOpPrioWw(const char * value, const int8_t id) {
return set_wwAltOpPrio(value, 3);
}
bool set_forceHeatingOff(const char * value, const int8_t id);
/*
@@ -622,4 +653,4 @@ class Boiler : public EMSdevice {
} // namespace emsesp
#endif
#endif
+26
View File
@@ -27,6 +27,7 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
if (device_id != EMSdevice::EMS_DEVICE_ID_RFBASE) { // Modems have no entities
return;
}
register_telegram_type(0xD1, "RFOutdoorTemp", false, MAKE_PF_CB(process_OutdoorTemp));
register_telegram_type(0x06, "RCTime", false, MAKE_PF_CB(process_RCTime));
@@ -45,6 +46,7 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
register_telegram_type(0x1230 + i, "Roomparams", false, MAKE_PF_CB(process_roomThermostatParam)); // fetch for active circuits
register_telegram_type(0x1244 + i, "Roomdata", false, MAKE_PF_CB(process_roomThermostatData)); // broadcasted
}
register_telegram_type(0x0B65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule));
register_telegram_type(0xF7, "MenuConfig", false, MAKE_PF_CB(process_roomConfig));
// 0x2040, broadcast 36 bytes:
@@ -57,6 +59,7 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
register_device_values_room(new_room);
#endif
}
/*
* OutdoorTemp - type 0xD1 - external temperature
*/
@@ -70,6 +73,7 @@ void Connect::process_RCTime(const std::shared_ptr<const Telegram> & telegram) {
if (telegram->offset || telegram->message_length < 10) {
return;
}
char time[sizeof(dateTime_)];
snprintf(time,
sizeof(time),
@@ -106,9 +110,11 @@ std::shared_ptr<Connect::RoomCircuit> Connect::room_circuit(const uint8_t num, c
return room_circuit;
}
}
if (!create) {
return nullptr;
}
// create a new circuit object and add to the list
auto new_room = std::make_shared<Connect::RoomCircuit>(num);
room_circuits_.push_back(new_room);
@@ -128,12 +134,14 @@ void Connect::process_roomThermostat(const std::shared_ptr<const Telegram> & tel
if (rc == nullptr) {
return;
}
has_update(telegram, rc->temp_, 0);
has_update(telegram, rc->humidity_, 2); // could show -3 if not set
// make sure we have read mode and icon, needed for ha climate
if (!Mqtt::ha_enabled() || (Helpers::hasValue(rc->mode_) && Helpers::hasValue(rc->icon_))) {
has_update(telegram, rc->seltemp_, 3);
}
// calculate dew temperature
has_update(rc->dewtemp_, Helpers::calc_dew(rc->temp_, rc->humidity_));
}
@@ -145,12 +153,14 @@ void Connect::process_roomThermostatName(const std::shared_ptr<const Telegram> &
if (rc == nullptr) {
return;
}
has_update(telegram, rc->icon_, 0);
for (uint8_t i = telegram->offset; i < telegram->message_length + telegram->offset && i < 100; i++) {
if ((i > 1) && (i % 2) == 0) {
rc->name_[(i - 2) / 2] = telegram->message_data[i - telegram->offset];
}
}
rc->name_[50] = '\0'; // make sure name is terminated
}
@@ -161,6 +171,7 @@ void Connect::process_roomThermostatSettings(const std::shared_ptr<const Telegra
if (rc == nullptr) {
return;
}
// has_enumupdate(telegram, rc->mode_, 0, {3, 1, 0}); // modes off, manual auto
// has_update(telegram, rc->mode_, 0); // modes: auto, heat, cool, off
// has_update(telegram, rc->tempautotemp_, 1); // FF means off
@@ -215,6 +226,7 @@ void Connect::process_roomSchedule(const std::shared_ptr<const Telegram> & teleg
return;
}
}
toggle_fetch(telegram->type_id, false); // fetch only once if all is initialized
}
@@ -227,6 +239,7 @@ void Connect::process_roomConfig(const std::shared_ptr<const Telegram> & telegra
if (rc == nullptr) {
return;
}
uint8_t bits = telegram->message_data[3];
if (rc->mode_ == 2) {
if (bits & 0x02) {
@@ -235,6 +248,7 @@ void Connect::process_roomConfig(const std::shared_ptr<const Telegram> & telegra
rc->coolmode_ = 1;
}
}
/*
switch (bits & 0x6A) {
case 0: // cooling off
@@ -272,12 +286,14 @@ bool Connect::set_mode(const char * value, const int8_t id) {
if (rc == nullptr) {
return false;
}
uint8_t v;
if (!Helpers::value2enum(value, v, FL_(enum_mode2), {3, 1, 0})) {
if (!Helpers::value2enum(value, v, FL_(enum_mode_ha), {3, 1, 0})) {
return false;
}
}
write_command(0xBB5 + rc->room(), rc->coolmode_ == 1 ? 4 : 0, v); // no validate, mode change is broadcasted
return true;
}
@@ -287,6 +303,7 @@ bool Connect::set_seltemp(const char * value, const int8_t id) {
if (rc == nullptr) {
return false;
}
float v;
if (Helpers::value2temperature(value, v)) {
// depends on mode, auto (2 for enum_mode2, 0 for enum_mode8) set in offset 1
@@ -295,8 +312,10 @@ bool Connect::set_seltemp(const char * value, const int8_t id) {
} else {
write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
}
return true;
}
return false;
}
@@ -305,6 +324,7 @@ bool Connect::set_name(const char * value, const int8_t id) {
if (rc == nullptr || value == nullptr || strlen(value) > 50) {
return false;
}
Helpers::utf8tolatin1(rc->name_, value, sizeof(rc->name_));
uint8_t len = strlen(rc->name_) * 2 + 2;
uint8_t data[len];
@@ -312,6 +332,7 @@ bool Connect::set_name(const char * value, const int8_t id) {
data[2 * i] = 0;
data[2 * i + 1] = rc->name_[i];
}
uint8_t ofs = 0;
while (len > 0) {
uint8_t part = len > 25 ? 25 : len;
@@ -319,6 +340,7 @@ bool Connect::set_name(const char * value, const int8_t id) {
ofs += part;
len -= part;
}
return true;
}
@@ -327,11 +349,13 @@ bool Connect::set_childlock(const char * value, const int8_t id) {
if (rc == nullptr) {
return false;
}
bool b;
if (Helpers::value2bool(value, b)) {
write_command(0xBB5 + rc->room(), 7, b ? 1 : 0, 0xBB5 + rc->room());
return true;
}
return false;
}
@@ -340,11 +364,13 @@ bool Connect::set_icon(const char * value, const int8_t id) {
if (rc == nullptr) {
return false;
}
uint8_t v;
if (Helpers::value2enum(value, v, FL_(enum_icons))) {
write_command(0x0B3D + rc->room(), 0, v, 0x0B3D + rc->room());
return true;
}
return false;
}
+1
View File
@@ -32,6 +32,7 @@ class Connect : public EMSdevice {
RoomCircuit(const uint8_t num)
: room_(num) {
}
~RoomCircuit() = default;
int16_t temp_;
uint8_t humidity_;
+2 -1
View File
@@ -36,6 +36,7 @@ void Controller::process_dateTime(const std::shared_ptr<const Telegram> & telegr
if (telegram->offset > 0 || telegram->message_length < 5) {
return;
}
char newdatetime[sizeof(dateTime_)];
// publish as dd.mm.yyyy hh:mm
snprintf(newdatetime,
@@ -49,4 +50,4 @@ void Controller::process_dateTime(const std::shared_ptr<const Telegram> & telegr
has_update(dateTime_, newdatetime, sizeof(dateTime_));
}
} // namespace emsesp
} // namespace emsesp
+5
View File
@@ -28,6 +28,7 @@ Extension::Extension(uint8_t device_type, uint8_t device_id, uint8_t product_id,
// no entities for T1RF outdoor sensor, values are coming from RFbase 0x50 (connect)
return;
}
// Extension module EM100 device_id 0x12
register_telegram_type(0x935, "EM100SetMessage", true, MAKE_PF_CB(process_EM100SetMessage), 5);
register_telegram_type(0x936, "EM100OutMessage", false, MAKE_PF_CB(process_EM100OutMessage));
@@ -108,6 +109,7 @@ bool Extension::set_minV(const char * value, const int8_t id) {
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0x935, 1, (uint8_t)(v * 10));
return true;
}
@@ -117,6 +119,7 @@ bool Extension::set_maxV(const char * value, const int8_t id) {
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0x935, 2, (uint8_t)(v * 10));
return true;
}
@@ -126,6 +129,7 @@ bool Extension::set_minT(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x935, 3, v);
return true;
}
@@ -135,6 +139,7 @@ bool Extension::set_maxT(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x935, 4, v);
return true;
}
+29
View File
@@ -381,119 +381,148 @@ bool Heatpump::set_controlStrategy(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_hybridStrategy1))) {
return false;
}
write_command(0x998, 0, v, 0x998);
return true;
}
bool Heatpump::set_lowNoiseMode(const char * value, const int8_t id) {
uint8_t v;
if (!Helpers::value2enum(value, v, FL_(enum_lowNoiseMode))) {
return false;
}
write_command(0x998, 9, v, 0x998);
return true;
}
bool Heatpump::set_lowNoiseStart(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v, 0, 23)) {
return false;
}
write_command(0x998, 10, v, 0x998);
return true;
}
bool Heatpump::set_lowNoiseStop(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v, 0, 23)) {
return false;
}
write_command(0x998, 11, v, 0x998);
return true;
}
bool Heatpump::set_hybridDHW(const char * value, const int8_t id) {
uint8_t v;
if (!Helpers::value2enum(value, v, FL_(enum_comfort2))) {
return false;
}
write_command(0x998, 1, v, 0x998);
return true;
}
bool Heatpump::set_energyPriceGas(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0x998, 2, v, 0x998);
return true;
}
bool Heatpump::set_energyPriceEl(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0x998, 5, v, 0x998);
return true;
}
bool Heatpump::set_energyPricePV(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0x998, 6, v, 0x998);
return true;
}
bool Heatpump::set_switchOverTemp(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0x998, 14, v, 0x998);
return true;
}
// Function tests:
bool Heatpump::set_airPurgeMode(const char * value, const int8_t id) {
bool v;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x999, 0, v, 0x999);
return true;
}
bool Heatpump::set_heatPumpOutput(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v, 0, 100)) {
return false;
}
write_command(0x999, 2, v, 0x999);
return true;
}
bool Heatpump::set_coolingCircuit(const char * value, const int8_t id) {
bool v;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x999, 6, v, 0x999);
return true;
}
bool Heatpump::set_compStartMod(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v, 0, 100)) {
return false;
}
write_command(0x999, 7, v, 0x999);
return true;
}
bool Heatpump::set_heatDrainPan(const char * value, const int8_t id) {
bool v;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x999, 9, v, 0x999);
return true;
}
bool Heatpump::set_heatCable(const char * value, const int8_t id) {
bool v;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x999, 10, v, 0x999);
return true;
}
+17 -1
View File
@@ -187,6 +187,7 @@ bool Heatsource::set_vr2Config(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_vr2Config))) {
return false;
}
write_command(0x54C, 12, v, 0x54C);
return true;
}
@@ -197,6 +198,7 @@ bool Heatsource::set_ahsActivated(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x54C, 0, v, 0x54C);
return true;
}
@@ -207,6 +209,7 @@ bool Heatsource::set_aPumpConfig(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x54C, 4, v, 0x54C);
return true;
}
@@ -217,6 +220,7 @@ bool Heatsource::set_aPumpSignal(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_aPumpSignal))) {
return false;
}
write_command(0x54C, 3, v, 0x54C);
return true;
}
@@ -227,6 +231,7 @@ bool Heatsource::set_aPumpMin(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0x54C, 21, v, 0x54C);
return true;
}
@@ -237,6 +242,7 @@ bool Heatsource::set_tempRise(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x54C, 1, v, 0x54C);
return true;
}
@@ -247,6 +253,7 @@ bool Heatsource::set_setReturnTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x54C, 6, v, 0x54C);
return true;
}
@@ -257,6 +264,7 @@ bool Heatsource::set_mixRuntime(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
uint8_t data[2] = {(uint8_t)(v >> 8), (uint8_t)v};
write_command(0x54C, 8, data, 2, 0x54C);
return true;
@@ -268,6 +276,7 @@ bool Heatsource::set_setFlowTemp(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0x54C, 7, v, 0x54C);
return true;
}
@@ -278,6 +287,7 @@ bool Heatsource::set_bufBypass(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_bufBypass))) {
return false;
}
write_command(0x54C, 2, v, 0x54C);
return true;
}
@@ -288,6 +298,7 @@ bool Heatsource::set_bufMixRuntime(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
uint8_t data[2] = {(uint8_t)(v >> 8), (uint8_t)v};
write_command(0x54C, 10, data, 2, 0x54C);
return true;
@@ -299,6 +310,7 @@ bool Heatsource::set_bufConfig(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_bufConfig))) {
return false;
}
write_command(0x54C, 20, v, 0x54C);
return true;
}
@@ -309,6 +321,7 @@ bool Heatsource::set_blockMode(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_blockMode))) {
return false;
}
write_command(0x54C, 16, v, 0x54C);
return true;
}
@@ -319,6 +332,7 @@ bool Heatsource::set_blockTerm(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_blockTerm))) {
return false;
}
write_command(0x54C, 17, v, 0x54C);
return true;
}
@@ -329,6 +343,7 @@ bool Heatsource::set_blockHyst(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v, true)) {
return false;
}
write_command(0x54C, 14, v, 0x54C);
return true;
}
@@ -339,6 +354,7 @@ bool Heatsource::set_releaseWait(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0x54C, 15, v, 0x54C);
return true;
}
@@ -363,4 +379,4 @@ bool Heatsource::set_valveReturn(const char * value, const int8_t id) {
}
*/
} // namespace emsesp
} // namespace emsesp
+18
View File
@@ -190,19 +190,23 @@ bool Mixer::set_flowSetTemp(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
write_command(0xAC, 0, v, 0xAB);
return true;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x123, 0, v, 0x10C);
return true;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
uint8_t hc = device_id() - 0x20;
write_command(0x2E1 + hc, 1, v, 0x2D7 + hc);
return true;
}
return false;
}
@@ -211,21 +215,25 @@ bool Mixer::set_pump(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
// AC telegram can only be written with offset 0
uint8_t dat[2] = {flowSetTemp_, b ? (uint8_t)0x64 : (uint8_t)0};
write_command(0xAC, 0, dat, sizeof(dat), 0xAB);
return true;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x123, 1, b ? 0x64 : 0, 0x10C);
return true;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
uint8_t hc = device_id() - 0x20;
write_command(0x2E1 + hc, 2, b ? 0x64 : 0, 0x2D7 + hc);
return true;
}
return false;
}
@@ -234,15 +242,18 @@ bool Mixer::set_activated(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
write_command(0xAA, 0, b ? 0xFF : 0, 0xAA);
return true;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
uint8_t hc = device_id() - 0x20;
write_command(0x2CD + hc, 0, b ? 0xFF : 0, 0x2CD + hc);
return true;
}
return false;
}
@@ -251,17 +262,20 @@ bool Mixer::set_setValveTime(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
v = (v + 5) / 10;
write_command(0xAA, 1, v, 0xAA);
return true;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
v = (v + 5) / 10;
uint8_t hc = device_id() - 0x20;
write_command(0x2CD + hc, 1, v, 0x2CD + hc);
return true;
}
return false;
}
@@ -270,11 +284,13 @@ bool Mixer::set_flowTempOffset(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
uint8_t hc = device_id() - 0x20;
write_command(0x2CD + hc, 2, v, 0x2CD + hc);
return true;
}
return false;
}
@@ -283,6 +299,7 @@ bool Mixer::set_pressure(const char * value, const int8_t) {
if (!Helpers::value2number(value, v)) {
return false;
}
uint8_t hc = device_id() - 0x20;
write_command(0x2CD + hc, 9, v / 50, 0x2CD + hc);
return true;
@@ -293,6 +310,7 @@ bool Mixer::set_wwprio(const char * value, const int8_t) {
if (!Helpers::value2bool(value, b)) {
return false;
}
uint8_t hc = device_id() - 0x20;
write_command(0x2CD + hc, 3, b ? 0xFF : 0, 0x2CD + hc);
return true;
+1
View File
@@ -50,6 +50,7 @@ void Pool::process_HpPoolStatus(const std::shared_ptr<const Telegram> & telegram
if (telegram->read_value(request, 4)) {
has_update(poolRequest_, request == 0 ? EMS_VALUE_BOOL_ON : EMS_VALUE_BOOL_OFF);
}
has_update(telegram, poolTemp_, 0);
has_update(telegram, poolShunt_, 3); // 0-100%, 0% = pool / 100% = heat
telegram->read_value(poolShuntStatus__, 2);
+46
View File
@@ -137,6 +137,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &data0_, DeviceValueType::UINT8, FL_(data0), DeviceValueUOM::NONE);
*/
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&cylMiddleTemp_,
@@ -482,6 +483,7 @@ void Solar::process_SM10Monitor(const std::shared_ptr<const Telegram> & telegram
if (solarpumpmod == 100 && solarPumpMod_ == 0 && solarPumpMinMod_ > 0) {
solarpumpmod = solarPumpMinMod_; // set to minimum
}
has_update(solarPumpMod_, solarpumpmod);
if (!Helpers::hasValue(maxFlow_)) {
@@ -497,11 +499,13 @@ void Solar::process_SM10Monitor(const std::shared_ptr<const Telegram> & telegram
if (energy.size() >= 60) {
energy.pop_front();
}
energy.push_back(solarPower_);
int32_t sum = 0;
for (auto e : energy) {
sum += e;
}
energyLastHour_ = sum > 0 ? sum / 6 : 0; // counts in 0.1 Wh
has_update(&solarPower_);
has_update(&energyLastHour_);
@@ -667,10 +671,12 @@ void Solar::process_SM100Status(const std::shared_ptr<const Telegram> & telegram
if (solarpumpmod == 100 && solarPumpMod_ == 0 && solarPumpMinMod_ > 0) {
solarpumpmod = solarPumpMinMod_ * 5;
}
has_update(solarPumpMod_, solarpumpmod);
if (cylinderpumpmod == 100 && cylPumpMod_ == 0 && solarPumpMinMod_ > 0) {
cylinderpumpmod = solarPumpMinMod_ * 5;
}
has_update(cylPumpMod_, cylinderpumpmod);
has_bitupdate(telegram, cylHeated_, 3, 1); // issue #422
@@ -683,6 +689,7 @@ void Solar::process_SM100Status(const std::shared_ptr<const Telegram> & telegram
if (solarpumpmod == 100 && solarPump2Mod_ == 0 && solarPumpMinMod_ > 0) {
solarpumpmod = solarPumpMinMod_ * 5; // set to minimum
}
has_update(solarPump2Mod_, solarpumpmod);
has_update(telegram, transferPumpMod_, 14);
@@ -720,6 +727,7 @@ void Solar::process_SM100CollectorConfig(const std::shared_ptr<const Telegram> &
collector2Area_ = EMS_VALUE_UINT16_NOTSET;
collector2Type_ = EMS_VALUE_UINT8_NOTSET;
}
// has_enumupdate(telegram, collector2Type_, 8, 1);
}
@@ -795,11 +803,13 @@ bool Solar::set_CollectorMaxTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 3, (uint8_t)temperature, 0x96);
} else {
write_command(0x35A, 0, (uint8_t)temperature, 0x35A);
}
return true;
}
@@ -809,11 +819,13 @@ bool Solar::set_CollectorMinTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 4, (uint8_t)temperature, 0x96);
} else {
write_command(0x35A, 4, (uint8_t)temperature, 0x35A);
}
return true;
}
@@ -823,6 +835,7 @@ bool Solar::set_cylMaxTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 5, (uint8_t)temperature, 0x96);
} else if (flags() == EMSdevice::EMS_DEVICE_FLAG_ISM) {
@@ -830,6 +843,7 @@ bool Solar::set_cylMaxTemp(const char * value, const int8_t id) {
} else {
write_command(0x35A, 3, (uint8_t)temperature, 0x35A);
}
return true;
}
@@ -839,11 +853,13 @@ bool Solar::set_PumpMinMod(const char * value, const int8_t id) {
if (!Helpers::value2number(value, modulation)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 2, (uint8_t)modulation, 0x96);
} else {
write_command(0x35A, 6, (uint8_t)(modulation + 2) / 5, 0x35A);
}
return true;
}
@@ -853,6 +869,7 @@ bool Solar::set_Pump2MinMod(const char * value, const int8_t id) {
if (!Helpers::value2number(value, modulation)) {
return false;
}
// write_command(0x35D, x, (uint8_t)(modulation), 0x35D);
return true;
}
@@ -863,6 +880,7 @@ bool Solar::set_wwMinTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x96, 6, (uint8_t)temperature, 0x96);
return true;
}
@@ -873,11 +891,13 @@ bool Solar::set_TurnoffDiff(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature, true)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 8, (uint8_t)temperature, 0x96);
} else {
write_command(0x35A, 7, (uint8_t)(temperature * 10), 0x35A);
}
return true;
}
@@ -887,11 +907,13 @@ bool Solar::set_TurnonDiff(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature, true)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 7, (uint8_t)temperature, 0x96);
} else {
write_command(0x35A, 8, (uint8_t)(temperature * 10), 0x35A);
}
return true;
}
@@ -901,6 +923,7 @@ bool Solar::set_TurnoffDiff2(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature, true)) {
return false;
}
write_command(0x361, 3, (uint8_t)(temperature * 10), 0x361);
return true;
}
@@ -911,6 +934,7 @@ bool Solar::set_TurnonDiff2(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature, true)) {
return false;
}
write_command(0x361, 4, (uint8_t)(temperature * 10), 0x361);
return true;
}
@@ -921,6 +945,7 @@ bool Solar::set_SM10MaxFlow(const char * value, const int8_t id) {
if (!Helpers::value2float(value, flow)) {
return false;
}
maxFlow_ = (flow * 10);
EMSESP::webSettingsService.update([&](WebSettings & settings) {
settings.solar_maxflow = maxFlow_;
@@ -935,6 +960,7 @@ bool Solar::set_heatTransferSystem(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 5, b ? 0xFF : 0x00); // change is broadcasted
return true;
}
@@ -945,6 +971,7 @@ bool Solar::set_externalCyl(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 9, b ? 0xFF : 0x00); // change is broadcasted
return true;
}
@@ -955,6 +982,7 @@ bool Solar::set_thermalDisinfect(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 10, b ? 0xFF : 0x00); // change is broadcasted
return true;
}
@@ -965,6 +993,7 @@ bool Solar::set_heatMetering(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 14, b ? 0xFF : 0x00); // change is broadcasted
return true;
}
@@ -975,11 +1004,13 @@ bool Solar::set_solarEnabled(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 0, b ? 0xFF : 0x00, 0x96);
} else {
write_command(0x358, 19, b ? 0xFF : 0x00); // change is broadcasted
}
return true;
}
@@ -989,6 +1020,7 @@ bool Solar::set_solarMode(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, num, FL_(enum_solarmode))) {
return false;
}
write_command(0x35A, 5, num, 0x35A);
return true;
}
@@ -1010,6 +1042,7 @@ bool Solar::set_solarPumpKick(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 9, b ? 0x01 : 0x00, 0x35A);
return true;
}
@@ -1020,6 +1053,7 @@ bool Solar::set_solarPump2Kick(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35D, 0, b ? 0x01 : 0x00, 0x35D);
return true;
}
@@ -1030,6 +1064,7 @@ bool Solar::set_plainWaterMode(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 10, b ? 0x01 : 0x00, 0x35A);
return true;
}
@@ -1040,6 +1075,7 @@ bool Solar::set_doubleMatchFlow(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 11, b ? 0x01 : 0x00, 0x35A);
return true;
}
@@ -1050,6 +1086,7 @@ bool Solar::set_climateZone(const char * value, const int8_t id) {
if (!Helpers::value2number(value, zone)) {
return false;
}
write_command(0x380, 0, zone, 0x380);
return true;
}
@@ -1060,6 +1097,7 @@ bool Solar::set_collector1Area(const char * value, const int8_t id) {
if (!Helpers::value2float(value, area)) {
return false;
}
write_command(0x380, 3, (uint16_t)(area * 10), 0x380);
return true;
}
@@ -1070,6 +1108,7 @@ bool Solar::set_collector2Area(const char * value, const int8_t id) {
if (!Helpers::value2float(value, area)) {
return false;
}
write_command(0x380, 6, (uint16_t)(area * 10), 0x380);
return true;
}
@@ -1080,6 +1119,7 @@ bool Solar::set_collector1Type(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, num, FL_(enum_collectortype))) {
return false;
}
write_command(0x380, 5, num + 1, 0x380);
return true;
}
@@ -1090,6 +1130,7 @@ bool Solar::set_collector2Type(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, num, FL_(enum_collectortype))) {
return false;
}
write_command(0x380, 8, num + 1, 0x380);
return true;
}
@@ -1100,6 +1141,7 @@ bool Solar::set_cylPriority(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, num, FL_(enum_cylprio))) {
return false;
}
write_command(0x35F, 3, num, 0x35F);
return true;
}
@@ -1109,6 +1151,7 @@ bool Solar::set_heatAssist(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x35C, 0, (uint8_t)(temperature * 10), 0x35C);
return true;
}
@@ -1118,6 +1161,7 @@ bool Solar::set_diffControl(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x361, 4, (uint8_t)(temperature * 10), 0x361);
return true;
}
@@ -1127,6 +1171,7 @@ bool Solar::set_solarHeatAssistOn(const char * value, const int8_t) {
if (!Helpers::value2float(value, t)) {
return false;
}
write_command(0x35C, 0, (uint8_t)(t * 10), 0x35C);
return true;
}
@@ -1136,6 +1181,7 @@ bool Solar::set_solarHeatAssistOff(const char * value, const int8_t) {
if (!Helpers::value2float(value, t)) {
return false;
}
write_command(0x35C, 1, (uint8_t)(t * 10), 0x35C);
return true;
}
File diff suppressed because it is too large Load Diff
+38 -1
View File
@@ -32,6 +32,7 @@ class Thermostat : public EMSdevice {
: hc_num_(hc_num)
, model_(model) {
}
~HeatingCircuit() = default;
int16_t selTemp;
@@ -190,6 +191,7 @@ class Thermostat : public EMSdevice {
DhwCircuit(const uint8_t offset)
: dhw_num_(offset) {
}
~DhwCircuit() = default;
uint8_t wwExtra_;
uint8_t wwMode_;
@@ -507,6 +509,7 @@ class Thermostat : public EMSdevice {
inline bool set_vacation(const char * value, const int8_t id) {
return set_holiday(value, id, true);
}
bool set_pause(const char * value, const int8_t id);
bool set_party(const char * value, const int8_t id);
bool set_holidaymode(const char * value, const int8_t id);
@@ -537,78 +540,103 @@ class Thermostat : public EMSdevice {
inline bool set_temp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::AUTO);
}
inline bool set_nighttemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::NIGHT);
}
inline bool set_daytemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::DAY);
}
inline bool set_daylowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::DAYLOW);
}
inline bool set_daymidtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::DAYMID);
}
inline bool set_comforttemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::COMFORT);
}
inline bool set_nofrosttemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::NOFROST);
}
inline bool set_ecotemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::ECO);
}
inline bool set_heattemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::HEAT);
}
inline bool set_summertemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::SUMMER);
}
inline bool set_designtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::DESIGN);
}
inline bool set_offsettemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::OFFSET);
}
inline bool set_holidaytemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::HOLIDAY);
}
inline bool set_offtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::OFF);
}
inline bool set_manualtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL);
}
inline bool set_tempautotemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::TEMPAUTO);
}
inline bool set_noreducetemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::NOREDUCE);
}
inline bool set_reducetemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::REDUCE);
}
inline bool set_vacreducetemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::VACREDUCE);
}
inline bool set_flowtempoffset(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::FLOWOFFSET, true);
}
inline bool set_maxflowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MAXFLOW);
}
inline bool set_minflowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MINFLOW);
}
inline bool set_baseflowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::BASEFLOW);
}
inline bool set_roominfluence(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::ROOMINFLUENCE, true);
}
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);
}
@@ -636,31 +664,40 @@ class Thermostat : public EMSdevice {
inline bool set_wwVacation(const char * value, const int8_t id) {
return set_holiday(value, DeviceValueTAG::TAG_DHW1, true);
}
inline bool set_wwHoliday(const char * value, const int8_t id) {
return set_holiday(value, DeviceValueTAG::TAG_DHW1);
}
bool set_RC30Vacation(const char * value, const int8_t id);
inline bool set_RC30Vacation1(const char * value, const int8_t id) {
return set_RC30Vacation(value, 1);
}
inline bool set_RC30Vacation2(const char * value, const int8_t id) {
return set_RC30Vacation(value, 2);
}
inline bool set_RC30Vacation3(const char * value, const int8_t id) {
return set_RC30Vacation(value, 3);
}
inline bool set_RC30Vacation4(const char * value, const int8_t id) {
return set_RC30Vacation(value, 4);
}
inline bool set_RC30Vacation5(const char * value, const int8_t id) {
return set_RC30Vacation(value, 5);
}
inline bool set_RC30Vacation6(const char * value, const int8_t id) {
return set_RC30Vacation(value, 6);
}
inline bool set_RC30Vacation7(const char * value, const int8_t id) {
return set_RC30Vacation(value, 7);
}
bool set_datetime(const char * value, const int8_t id);
bool set_minexttemp(const char * value, const int8_t id);
bool set_clockoffset(const char * value, const int8_t id);
@@ -710,4 +747,4 @@ class Thermostat : public EMSdevice {
} // namespace emsesp
#endif
#endif
+3
View File
@@ -97,6 +97,7 @@ bool Ventilation::set_ventMode(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, v, FL_(enum_ventMode))) {
return false;
}
write_command(0x56B, 0, v - 1, 0x56B);
return true;
}
@@ -106,6 +107,7 @@ bool Ventilation::set_filter(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
// write_command(0x5xx, 0, v, 0x5xx);
return true;
}
@@ -115,6 +117,7 @@ bool Ventilation::set_bypass(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x55C, 1, b ? 1 : 0, 0x587);
return true;
}
+29
View File
@@ -76,6 +76,7 @@ Water::Water(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
} else if (tag == DeviceValueTAG::TAG_DHW4) {
register_telegram_type(0x07C3, "SM100wwCommand", false, MAKE_PF_CB(process_SM100wwCommand)); // command from dhw3->dhw4
}
// device values...
register_device_value(tag, &wwTemp_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwTemp2_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwStorageTemp1), DeviceValueUOM::DEGREES);
@@ -133,6 +134,7 @@ void Water::process_SM100wwParam(const std::shared_ptr<const Telegram> & telegra
// (daily heating time thermostat 2F5, offset 9, offset 8 on/off)
has_update(telegram, errorDisp_, 19);
}
// SM100wwParam2 - 0x07AC, Solar Module(0x2A) -> (0x00)
void Water::process_SM100wwParam2(const std::shared_ptr<const Telegram> & telegram) {
has_update(telegram, wwDeltaTRet_, 1);
@@ -248,6 +250,7 @@ bool Water::set_wwSelTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x35, 3, (uint8_t)temperature, 0x34);
} else if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
@@ -255,6 +258,7 @@ bool Water::set_wwSelTemp(const char * value, const int8_t id) {
} else { // SM100
write_command(0x7A6, 9, (uint8_t)temperature, 0x7A6);
}
return true;
}
@@ -263,6 +267,7 @@ bool Water::set_wwMaxTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
return false;
} else if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
@@ -270,6 +275,7 @@ bool Water::set_wwMaxTemp(const char * value, const int8_t id) {
} else { // SM100
write_command(0x7A6, 8, (uint8_t)temperature, 0x7A6);
}
return true;
}
@@ -278,6 +284,7 @@ bool Water::set_wwRedTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
return false;
} else if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
@@ -285,6 +292,7 @@ bool Water::set_wwRedTemp(const char * value, const int8_t id) {
} else { // SM100
write_command(0x7A6, 10, (uint8_t)temperature, 0x7A6);
}
return true;
}
@@ -293,6 +301,7 @@ bool Water::set_wwHotTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 7, (uint8_t)temperature, 0x7A6);
return true;
}
@@ -302,6 +311,7 @@ bool Water::set_wwDailyTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 6, (uint8_t)temperature, 0x7A6);
return true;
}
@@ -311,6 +321,7 @@ bool Water::set_wwDisinfectionTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 8, (uint8_t)temperature, 0x33);
} else if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
@@ -318,6 +329,7 @@ bool Water::set_wwDisinfectionTemp(const char * value, const int8_t id) {
} else { // SM100
write_command(0x7A6, 12, (uint8_t)temperature, 0x7A6);
}
return true;
}
@@ -326,6 +338,7 @@ bool Water::set_wwCirc(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 6, b ? 0xFF : 0x00, 0x33);
} else if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
@@ -333,6 +346,7 @@ bool Water::set_wwCirc(const char * value, const int8_t id) {
} else { // SM100
write_command(0x7A5, 0, b ? 0xFF : 0x00, 0x7A5);
}
return true;
}
@@ -341,6 +355,7 @@ bool Water::set_wwCircMode(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, num, FL_(enum_wwCircMode))) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 7, num, 0x33);
} else if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
@@ -348,6 +363,7 @@ bool Water::set_wwCircMode(const char * value, const int8_t id) {
} else { // SM100
write_command(0x7A5, 3, num, 0x7A5);
}
return true;
}
@@ -357,11 +373,13 @@ bool Water::set_wwCircTc(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
write_command(0x33B + dhw_, 4, b ? 0x01 : 0x00, 0x33B + dhw_);
} else { // SM100
write_command(0x7A5, 4, b ? 0xFF : 0x00, 0x7A5);
}
return true;
}
@@ -370,6 +388,7 @@ bool Water::set_wwKeepWarm(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x7AE, 0, b ? 0xFF : 0x00, 0x7AE);
return true;
}
@@ -379,10 +398,12 @@ bool Water::set_wwDiffTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
write_command(0x313 + dhw_, 7, (int8_t)(v * 10), 0x313 + dhw_);
return true;
}
return false;
}
@@ -391,10 +412,12 @@ bool Water::set_wwRequiredTemp(const char * value, const int8_t id) {
if (!Helpers::value2temperature(value, v)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
write_command(0x313 + dhw_, 4, (uint8_t)v, 0x313 + dhw_);
return true;
}
return false;
}
@@ -403,6 +426,7 @@ bool Water::set_wwFlowTempOffset(const char * value, const int8_t id) {
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 5, n, 0x33);
return true;
}
@@ -412,6 +436,7 @@ bool Water::set_wwHystOn(const char * value, const int8_t id) {
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 3, n, 0x33);
return true;
}
@@ -421,6 +446,7 @@ bool Water::set_wwHystOff(const char * value, const int8_t id) {
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 4, n, 0x33);
return true;
}
@@ -430,6 +456,7 @@ bool Water::set_wwRetValve(const char * value, const int8_t id) {
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x7AD, 1, n, 0x7AD);
return true;
}
@@ -439,6 +466,7 @@ bool Water::set_errorDisp(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, n, FL_(enum_errorDisp))) {
return false;
}
write_command(0x7A6, 19, n, 0x7A6);
return true;
}
@@ -448,6 +476,7 @@ bool Water::set_wwDeltaTRet(const char * value, const int8_t id) {
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x7AC, 1, n, 0x7AC);
return true;
}
+15
View File
@@ -316,6 +316,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
id1 = Helpers::atoint(id1_s.c_str());
}
}
if (!id2_s.empty()) {
id2 = Helpers::atoint(id2_s.c_str());
}
@@ -326,6 +327,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 (deviceID 0x%02X, product_id %d), with all values...", id1, id2);
test("add", id1, id2);
shell.invoke_command("show devices");
@@ -694,6 +696,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
if (json.size() != 0) {
serializeJson(doc, Serial);
}
shell.println();
Serial.print(COLOR_RESET);
@@ -715,6 +718,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
Serial.println(" **");
}
}
ok = true;
}
@@ -983,6 +987,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
if (id1 == -1) {
id1 = 0;
}
shell.printfln("Testing healthcheck with %d", id1);
EMSESP::system_.healthcheck(id1);
ok = true;
@@ -2212,6 +2217,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
if (!pass) {
failed++;
}
shell.printfln("%s %-14s -> %-14s expected %-9s got %-9s", pass ? "PASS" : "FAIL", test.settings, test.current, test.expected, actual);
}
@@ -2238,6 +2244,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
snprintf(value, 7, "val%03d", i);
doc[key] = value;
}
doc.shrinkToFit();
JsonObject jo = doc.as<JsonObject>();
shell.printfln("Length of JSON payload = %d", measureJson(jo));
@@ -2528,11 +2535,13 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
for (const auto & d : response._data) {
shell.printf(" %d", d);
}
if (response._data.size() == 5 && response._data[3] == 0 && response._data[4] == (uint8_t)EMS_VALUE_DEFAULT_INT8_DUMMY) {
shell.printf(" [OK]");
} else {
shell.printf(" [ERROR - invalid response]");
}
shell.println();
} else {
shell.printf("mintempsilent [MODBUS ERROR %d]\n", response.getError());
@@ -2553,6 +2562,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
for (const auto & d : response._data) {
shell.printf(" %d", d);
}
shell.println(" [OK]");
} else {
shell.printf("selflowtemp [MODBUS ERROR %d]\n", response.getError());
@@ -2574,6 +2584,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
for (const auto & d : response._data) {
shell.printf(" %d", d);
}
shell.println(" [OK]");
} else {
shell.printf("remotetemp [MODBUS ERROR %d]\n", response.getError());
@@ -2691,6 +2702,7 @@ void Test::rx_telegram(const std::vector<uint8_t> & rx_data) {
data[i] = rx_data[i];
i++;
}
data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::rxservice_.add(data, len + 1);
@@ -2706,6 +2718,7 @@ void Test::uart_telegram(const std::vector<uint8_t> & rx_data) {
data[i] = rx_data[i];
i++;
}
data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::incoming_telegram(data, i + 1);
@@ -2719,6 +2732,7 @@ void Test::uart_telegram_withCRC(const char * rx_data) {
for (uint8_t i = 0; i < strlen(rx_data); i++) {
telegram[i] = rx_data[i];
}
telegram[strlen(rx_data)] = '\0'; // make sure its terminated
uint8_t count = 0;
@@ -2758,6 +2772,7 @@ void Test::uart_telegram(const char * rx_data) {
for (uint8_t i = 0; i < strlen(rx_data); i++) {
telegram[i] = rx_data[i];
}
telegram[strlen(rx_data)] = '\0'; // make sure its terminated
uint8_t count = 0;
+6
View File
@@ -54,6 +54,7 @@ void EMSuart::uart_event_task(void * pvParameters) {
if (length == 2 || (length >= 6 && length <= EMS_MAXBUFFERSIZE)) {
EMSESP::incoming_telegram(telegram, (uint8_t)(length - 1));
}
length = 0;
} else if (event.type == UART_BUFFER_FULL) {
uart_flush_input(EMSUART_NUM);
@@ -61,6 +62,7 @@ void EMSuart::uart_event_task(void * pvParameters) {
}
}
}
vTaskDelete(NULL);
}
@@ -118,6 +120,7 @@ void EMSuart::start(const uint8_t tx_mode, const uint8_t rx_gpio, const uint8_t
gpio_pullup_en((gpio_num_t)rx_gpio);
vTaskResume(xHandle);
}
tx_mode_ = tx_mode;
uart_enable_intr_mask(EMSUART_NUM, UART_BRK_DET_INT_ENA | UART_RXFIFO_FULL_INT_ENA);
}
@@ -181,6 +184,7 @@ uint8_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
uart_write_bytes(EMSUART_NUM, &buf[i], 1);
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
}
uart_gen_break(EMSUART_TX_BRK_PLUS);
return EMS_TX_STATUS_OK;
}
@@ -191,6 +195,7 @@ uint8_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
uart_write_bytes(EMSUART_NUM, &buf[i], 1);
delayMicroseconds(EMSUART_TX_WAIT_HT3);
}
uart_gen_break(EMSUART_TX_BRK_HT3);
return EMS_TX_STATUS_OK;
}
@@ -206,6 +211,7 @@ uint8_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
uart_get_buffered_data_len(EMSUART_NUM, &rx1);
} while ((rx1 == rx0) && (--timeoutcnt));
}
uart_gen_break(EMSUART_TX_BRK_EMS);
return EMS_TX_STATUS_OK;
}
+9
View File
@@ -44,6 +44,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant j
request->send(429); // too many requests
return;
}
busy = true;
JsonDocument input_doc; // has no body JSON so create dummy as empty input object
JsonObject input;
@@ -58,6 +59,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant j
input = input_doc.to<JsonObject>();
input["data"] = json.as<std::string>();
}
parse(request, input);
busy = false;
}
@@ -95,21 +97,27 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
if (request->hasParam(F_(device))) {
input["device"] = request->getParam(F_(device))->value().c_str();
}
if (request->hasParam(F_(cmd))) {
input["cmd"] = request->getParam(F_(cmd))->value().c_str();
}
if (request->hasParam(F_(data))) {
input["data"] = request->getParam(F_(data))->value().c_str();
}
if (request->hasParam(F_(value))) {
input["value"] = request->getParam(F_(value))->value().c_str();
}
if (request->hasParam(F_(id))) {
input["id"] = Helpers::atoint(request->getParam(F_(id))->value().c_str());
}
if (request->hasParam(F_(hc))) {
input["hc"] = Helpers::atoint(request->getParam(F_(hc))->value().c_str());
}
if (request->hasParam(F_(dhw))) {
input["dhw"] = Helpers::atoint(request->getParam(F_(dhw))->value().c_str());
}
@@ -205,6 +213,7 @@ void WebAPIService::storeResponse(JsonObject response) {
storeResponseDoc_.clear(); // clear it, so can only recall once
storeResponseDoc_.add(response); // add the object to our doc
}
const char * WebAPIService::getResponse() {
static std::string buffer;
serializeJson(storeResponseDoc_, buffer);
+3 -1
View File
@@ -59,6 +59,7 @@ void WebActivityService::webActivityService(AsyncWebServerRequest * request) {
statJson["q"] =
EMSESP::temperaturesensor_.reads() == 0 ? 100 : 100 - (uint8_t)((100 * EMSESP::temperaturesensor_.fails()) / EMSESP::temperaturesensor_.reads());
}
if (EMSESP::analog_enabled()) {
statJson = statsJson.add<JsonObject>();
statJson["id"] = 4;
@@ -66,6 +67,7 @@ void WebActivityService::webActivityService(AsyncWebServerRequest * request) {
statJson["f"] = EMSESP::analogsensor_.fails();
statJson["q"] = EMSESP::analogsensor_.reads() == 0 ? 100 : 100 - (uint8_t)((100 * EMSESP::analogsensor_.fails()) / EMSESP::analogsensor_.reads());
}
if (Mqtt::enabled()) {
statJson = statsJson.add<JsonObject>();
statJson["id"] = 5;
@@ -100,4 +102,4 @@ void WebActivityService::webActivityService(AsyncWebServerRequest * request) {
request->send(response);
}
} // namespace emsesp
} // namespace emsesp
+21
View File
@@ -73,6 +73,7 @@ bool WebCommandService::queueCommand(const char * name, const char * value) {
if (job == nullptr) {
return false;
}
job->name = name ? name : "";
job->has_value = (value != nullptr);
job->value = value ? value : "";
@@ -81,6 +82,7 @@ bool WebCommandService::queueCommand(const char * name, const char * value) {
delete job;
return false;
}
return true;
}
#endif
@@ -95,6 +97,7 @@ bool WebCommandService::isUrlCommand(const std::string & command) {
if (deserializeJson(doc, command) != DeserializationError::Ok) {
return false;
}
std::string url = doc["url"] | "";
auto lower_url = Helpers::toLower(url.c_str());
return lower_url.starts_with("http://") || lower_url.starts_with("https://");
@@ -123,6 +126,7 @@ bool WebCommandService::valueContainsUrl(const std::string & value) {
i++;
}
}
JsonDocument doc;
if (deserializeJson(doc, value.substr(f, e - f)) == DeserializationError::Ok) {
for (JsonPairConst p : doc.as<JsonObjectConst>()) {
@@ -131,8 +135,10 @@ bool WebCommandService::valueContainsUrl(const std::string & value) {
}
}
}
f = value.find_first_of('{', e);
}
return false;
}
@@ -149,6 +155,7 @@ bool WebCommandService::dispatchCommand(const char * name, const char * value) {
if (isUrlCommand(ci->cmd.c_str())) {
return queueCommand(name, value);
}
// internal command whose value embeds a {url} fetch (e.g. system/message) - the value is
// resolved by compute() at execution time and would block, so offload it to the worker task
// the effective value is the override if given, else the command's stored default
@@ -209,6 +216,7 @@ StateUpdateResult WebCommands::update(JsonObject root, WebCommands & webCommands
FL_(command_cmd),
CommandFlag::ADMIN_ONLY);
}
return StateUpdateResult::CHANGED;
}
@@ -217,12 +225,14 @@ const CommandItem * WebCommandService::find(const char * name) {
if (name == nullptr || name[0] == '\0') {
return nullptr;
}
auto lower_name = Helpers::toLower(name);
for (const CommandItem & ci : *commandItems_) {
if (ci.name[0] != '\0' && Helpers::toLower(ci.name) == lower_name) {
return &ci;
}
}
return nullptr;
}
@@ -233,6 +243,7 @@ bool WebCommandService::executeCommand(const char * name, const char * value) {
EMSESP::logger().warning("Command '%s' not found", name ? name : "");
return false;
}
// if there is a value use it, otherwise use the command's default value
std::string cmd_value = value ? value : ci->value.c_str();
return executeCommand(ci->name, std::string(ci->cmd.c_str()), cmd_value);
@@ -268,6 +279,7 @@ bool WebCommandService::executeCommand(const char * name, const std::string & co
commands(s, false);
url.replace(q + 1, l, s);
}
// the cmd's embedded value only gets entity substitution (commands), the passed value is fully computed
std::string value = doc["value"] | computed_data;
std::string method = doc["method"] | "GET";
@@ -308,6 +320,7 @@ bool WebCommandService::executeCommand(const char * name, const std::string & co
if (data.empty() && output.size()) {
Mqtt::queue_publish("response", output);
}
return true;
}
@@ -317,6 +330,7 @@ bool WebCommandService::executeCommand(const char * name, const std::string & co
} else {
snprintf(error, sizeof(error), "Command '%s': %s failed with error %s", name, cmd.c_str(), Command::return_code_string(return_code));
}
EMSESP::logger().warning(error);
return false;
}
@@ -332,6 +346,7 @@ bool WebCommandService::get_value_info(JsonObject output, const char * cmd) {
output[(const char *)ci.name] = ci.value.c_str();
}
}
return true;
}
@@ -341,6 +356,7 @@ bool WebCommandService::get_value_info(JsonObject output, const char * cmd) {
output[(const char *)ci.name] = std::string(ci.cmd + ": " + ci.value);
}
}
return true;
}
@@ -350,6 +366,7 @@ bool WebCommandService::get_value_info(JsonObject output, const char * cmd) {
get_value_json(output[ci.name].to<JsonObject>(), ci);
}
}
return true;
}
@@ -359,6 +376,7 @@ bool WebCommandService::get_value_info(JsonObject output, const char * cmd) {
output["api_data"] = metrics;
return true;
}
return false;
}
@@ -381,10 +399,12 @@ std::string WebCommandService::get_metrics_prometheus() {
if (ci.name[0] == '\0') {
continue;
}
result += (std::string) "# HELP emsesp_cmd_" + ci.name + " " + ci.name + ", readable, writable, visible\n";
result += (std::string) "# TYPE emsesp_cmd_" + ci.name + " gauge\n";
result += (std::string) "emsesp_cmd_" + ci.name + " 1\n";
}
return result;
}
@@ -403,6 +423,7 @@ void WebCommandService::publish(const bool force) {
if (!Mqtt::enabled() || commandItems_->empty()) {
return;
}
if (force && !EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
return;
}
+50
View File
@@ -74,6 +74,7 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
if (entityItem.raw) {
delete[] entityItem.raw;
}
if (entityItem.ram == 2) { // NVS
char key[sizeof(entityItem.name) + 2];
snprintf(key, sizeof(key), "c:%s", entityItem.name);
@@ -81,10 +82,12 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
EMSESP::nvs_.remove(key);
}
}
if (entityItem.ram) { // save name/value pairs for change checking
doc[entityItem.name] = entityItem.value;
}
}
webCustomEntity.customEntityItems.clear();
EMSESP::webCustomEntityService.ha_reset();
@@ -110,6 +113,7 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
entityItem.value_type = DeviceValueType::STRING;
entityItem.writeable = true;
}
entityItem.raw = nullptr;
if (entityItem.ram == 0 && entityItem.value_type == DeviceValueType::STRING) {
entityItem.raw = new uint8_t[(size_t)entityItem.factor + 1];
@@ -134,6 +138,7 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
if (entityItem.factor == 0) {
entityItem.factor = 1;
}
if (entityItem.ram == 2) { // NVS
char key[sizeof(entityItem.name) + 2];
snprintf(key, sizeof(key), "c:%s", entityItem.name);
@@ -141,6 +146,7 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
entityItem.data = EMSESP::nvs_.getString(key).c_str();
}
}
webCustomEntity.customEntityItems.push_back(entityItem); // add to list
if (entityItem.writeable && entityItem.name[0] != '\0') {
@@ -161,6 +167,7 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
}
}
}
return StateUpdateResult::CHANGED;
}
@@ -179,6 +186,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
if (entityItem.data == value) {
return true; // no change
}
if (entityItem.ram == 1) {
entityItem.data = value;
} else if (entityItem.ram == 2) { // NVS
@@ -198,6 +206,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
data[count++] = (uint8_t)strtol(p, 0, 16);
p = strtok(nullptr, " ,");
}
free(telegram);
uint8_t offset = entityItem.offset;
uint8_t * dat = data;
@@ -208,6 +217,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
count -= len;
dat += len;
}
delete[] data;
// validate telegram
EMSESP::send_read_request(entityItem.type_id, entityItem.device_id, entityItem.offset, length);
@@ -217,12 +227,14 @@ 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 ? (uint8_t)entityItem.factor : 0, entityItem.type_id);
} else {
float f;
if (!Helpers::value2float(value, f)) {
return false;
}
int v = (f / entityItem.factor + 0.5);
if (entityItem.value_type == DeviceValueType::UINT8 || entityItem.value_type == DeviceValueType::INT8) {
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v, entityItem.type_id);
@@ -239,12 +251,14 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
if (EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
publish();
}
char cmd[COMMAND_MAX_LENGTH];
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entityItem.name);
EMSESP::webSchedulerService.onChange(cmd);
return true;
}
}
return false;
}
@@ -263,30 +277,35 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem co
Mqtt::add_value_bool(output, name, (uint8_t)entity.value != 0);
}
}
break;
case DeviceValueType::INT8:
if ((int8_t)entity.value != EMS_VALUE_INT8_NOTSET) {
std::string v = Helpers::render_value(payload, entity.factor * (int8_t)entity.value, 2);
output[name] = add_uom ? serialized(v + ' ' + EMSdevice::uom_to_string(entity.uom)) : serialized(v);
}
break;
case DeviceValueType::UINT8:
if ((uint8_t)entity.value != EMS_VALUE_UINT8_NOTSET) {
std::string v = Helpers::render_value(payload, entity.factor * (int8_t)entity.value, 2);
output[name] = add_uom ? serialized(v + ' ' + EMSdevice::uom_to_string(entity.uom)) : serialized(v);
}
break;
case DeviceValueType::INT16:
if ((int16_t)entity.value != EMS_VALUE_INT16_NOTSET) {
std::string v = Helpers::render_value(payload, entity.factor * (int16_t)entity.value, 2);
output[name] = add_uom ? serialized(v + ' ' + EMSdevice::uom_to_string(entity.uom)) : serialized(v);
}
break;
case DeviceValueType::UINT16:
if ((uint16_t)entity.value != EMS_VALUE_UINT16_NOTSET) {
std::string v = Helpers::render_value(payload, entity.factor * (uint16_t)entity.value, 2);
output[name] = add_uom ? serialized(v + ' ' + EMSdevice::uom_to_string(entity.uom)) : serialized(v);
}
break;
case DeviceValueType::UINT24:
case DeviceValueType::TIME:
@@ -295,6 +314,7 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem co
std::string v = Helpers::render_value(payload, entity.factor * entity.value, 2);
output[name] = add_uom ? serialized(v + ' ' + EMSdevice::uom_to_string(entity.uom)) : serialized(v);
}
break;
// case DeviceValueType::STRING:
default:
@@ -302,6 +322,7 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem co
if (entity.data.length() > 0) {
output[name] = add_uom ? entity.data + ' ' + EMSdevice::uom_to_string(entity.uom) : entity.data;
}
break;
}
}
@@ -335,6 +356,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
render_value(output, entity);
}
}
return true;
}
@@ -344,6 +366,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
auto nest = output[entity.name].to<JsonObject>();
get_value_json(nest, entity);
}
return true;
}
@@ -353,6 +376,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
output["api_data"] = metrics;
return true;
}
return false;
}
@@ -364,6 +388,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
return Command::get_attribute(output, cmd, attribute_s);
}
}
return false; // not found
}
@@ -376,10 +401,12 @@ std::string WebCustomEntityService::get_metrics_prometheus() {
if (entity.hide || entity.name[0] == '\0') {
continue;
}
result += (std::string) "# HELP emsesp_" + entity.name + " " + entity.name;
if (entity.uom != 0) {
result += (std::string) ", " + EMSdevice::uom_to_string(entity.uom);
}
result += (std::string) ", readable, visible" + (entity.writeable ? ", writable\n" : "\n");
result += (std::string) "# TYPE emsesp_" + entity.name + " gauge\n";
result += (std::string) "emsesp_" + entity.name + " ";
@@ -408,10 +435,13 @@ std::string WebCustomEntityService::get_metrics_prometheus() {
if (entity.data.length() > 0) {
result += entity.data;
}
break;
}
result += (std::string) "\n";
}
return result;
}
@@ -466,11 +496,13 @@ void WebCustomEntityService::publish(const bool force) {
if (!Mqtt::enabled() || customEntityItems_->empty()) {
return;
}
if (force) {
if (Mqtt::publish_single()) {
for (CustomEntityItem & entityItem : *customEntityItems_) {
publish_single(entityItem);
}
return;
} else if (!EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
return; // wait for first time period
@@ -485,6 +517,7 @@ void WebCustomEntityService::publish(const bool force) {
if (entityItem.hide) {
continue;
}
render_value(output, entityItem);
// create HA config
if (Mqtt::ha_enabled() && !ha_configdone_) {
@@ -524,6 +557,7 @@ void WebCustomEntityService::publish(const bool force) {
} else {
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name);
}
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", F_(custom), entityItem.name);
config["cmd_t"] = command_topic;
@@ -559,6 +593,7 @@ void WebCustomEntityService::publish(const bool force) {
snprintf(topic, sizeof(topic), "%s_data", F_(custom));
Mqtt::queue_publish(topic, output);
}
// EMSESP::logger().debug("publish %d custom entities", output.size());
}
@@ -591,6 +626,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output, const bool is
if (entity.hide) {
continue;
}
bool include = false;
JsonObject root_obj = nodes.add<JsonObject>(); // create the object, we know there is a value
@@ -627,29 +663,34 @@ void WebCustomEntityService::generate_value_web(JsonObject output, const bool is
l.add(Helpers::render_boolean(s, true, true));
break;
}
case DeviceValueType::INT8:
if ((int8_t)entity.value != EMS_VALUE_INT8_NOTSET) {
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);
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);
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);
include = true;
}
break;
case DeviceValueType::UINT24:
case DeviceValueType::TIME:
@@ -658,12 +699,14 @@ void WebCustomEntityService::generate_value_web(JsonObject output, const bool is
obj["v"] = Helpers::transformNumFloat(entity.factor * entity.value);
include = true;
}
break;
case DeviceValueType::STRING:
if (entity.data.length() > 0) {
obj["v"] = entity.data;
include = true;
}
break;
default:
break;
@@ -696,6 +739,7 @@ void WebCustomEntityService::fetch() {
break;
}
}
if (needFetch) {
EMSESP::send_read_request(entity.type_id,
entity.device_id,
@@ -722,6 +766,7 @@ bool WebCustomEntityService::get_value(const std::shared_ptr<const Telegram> & t
message_length -= entity.offset - telegram->offset;
offset = 0;
}
auto length = std::min(offset + message_length, (int)entity.factor);
auto rest = std::min((int)entity.factor - offset, (int)message_length);
if (rest > 0) {
@@ -734,6 +779,7 @@ bool WebCustomEntityService::get_value(const std::shared_ptr<const Telegram> & t
} else if (EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
has_change = true;
}
char cmd[COMMAND_MAX_LENGTH];
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entity.name);
EMSESP::webSchedulerService.onChange(cmd);
@@ -745,10 +791,12 @@ bool WebCustomEntityService::get_value(const std::shared_ptr<const Telegram> & t
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()) {
@@ -756,10 +804,12 @@ bool WebCustomEntityService::get_value(const std::shared_ptr<const Telegram> & t
} else if (EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
has_change = true;
}
char cmd[COMMAND_MAX_LENGTH];
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entity.name);
EMSESP::webSchedulerService.onChange(cmd);
}
// EMSESP::logger().debug("custom entity %s received with value %d", entity.name, (int)entity.val);
}
}
+6
View File
@@ -110,6 +110,7 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
*p = '_';
}
}
customizations.sensorCustomizations.push_back(sensor); // add to list
}
}
@@ -126,6 +127,7 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
EMSESP::logger().warning("Analog sensor: Invalid GPIO %d for %s. Skipping.", analogJson["gpio"].as<uint8_t>(), analog_sensor_name);
continue;
}
auto analog = AnalogCustomization();
strlcpy(analog.name, analogJson["name"].as<const char *>(), sizeof(analog.name));
analog.gpio = analogJson["gpio"];
@@ -137,6 +139,7 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
if (_start && analog.type == EMSESP::analogsensor_.AnalogType::DIGITAL_OUT && analog.uom > DeviceValue::DeviceValueUOM::NONE) {
analog.offset = analog.uom > 1 ? 1 : 0;
}
customizations.analogCustomizations.push_back(analog); // add to list
}
}
@@ -332,11 +335,13 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req
break;
}
}
if (!is_set) {
entity_ids.push_back(entity_id);
}
}
}
break;
}
}
@@ -499,6 +504,7 @@ void WebCustomizationService::load_test_data() {
dv.state = DeviceValueState::DV_FAVORITE; // set as favorite
}
}
break;
}
}
+2 -1
View File
@@ -55,6 +55,7 @@ class AnalogCustomization {
bool operator==(const AnalogCustomization & a) const {
return gpio == a.gpio;
}
bool operator!=(const AnalogCustomization & a) const {
return !operator==(a);
}
@@ -111,4 +112,4 @@ class WebCustomizationService : public StatefulService<WebCustomization> {
} // namespace emsesp
#endif
#endif
+14 -1
View File
@@ -115,15 +115,18 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
if (Helpers::hasValue(sensor.temperature_c)) {
obj["t"] = (float)sensor.temperature_c * 0.18 + 32;
}
obj["u"] = DeviceValueUOM::FAHRENHEIT;
obj["o"] = (float)sensor.offset() * 0.18;
} else {
if (Helpers::hasValue(sensor.temperature_c)) {
obj["t"] = (float)sensor.temperature_c / 10;
}
obj["u"] = DeviceValueUOM::DEGREES;
obj["o"] = (float)(sensor.offset()) / 10;
}
obj["s"] = sensor.is_system();
}
}
@@ -188,6 +191,7 @@ void WebDataService::device_data(AsyncWebServerRequest * request) {
for (uint16_t i = 0; i < (TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) {
delay(1);
}
EMSESP::wait_validate(0); // reset in case of timeout
#ifndef EMSESP_STANDALONE
JsonObject output = response->getRoot();
@@ -263,14 +267,17 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
break;
}
}
break;
}
if (device_type == EMSdevice::DeviceType::UNKNOWN) {
EMSESP::logger().warning("Write command failed, bad device id: %d", unique_id);
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
request->send(response);
return;
}
// create JSON for output
auto * response = new PsramAsyncJsonResponse(false);
JsonObject output = response->getRoot();
@@ -282,6 +289,7 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
if (device_type >= EMSdevice::DeviceType::BOILER) {
cmd = Command::parse_command_string(cmd, id); // extract hc or dhw
}
if (data.is<const char *>()) {
return_code = Command::call(device_type, cmd, data.as<const char *>(), true, id, output);
} else if (data.is<int>()) {
@@ -413,6 +421,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
if (sensor.is_system()) {
continue;
}
JsonObject node = nodes.add<JsonObject>();
node["id"] = (EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID * 100) + count++;
@@ -422,11 +431,13 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
if (Helpers::hasValue(sensor.temperature_c)) {
dv["v"] = (float)sensor.temperature_c * 0.18 + 32;
}
dv["u"] = DeviceValueUOM::FAHRENHEIT;
} else {
if (Helpers::hasValue(sensor.temperature_c)) {
dv["v"] = (float)sensor.temperature_c / 10;
}
dv["u"] = DeviceValueUOM::DEGREES;
}
}
@@ -444,6 +455,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
if (sensor.is_system()) {
continue;
}
JsonObject node = nodes.add<JsonObject>();
node["id"] = (EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID * 100) + count++;
@@ -469,6 +481,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
dv["v"] = Helpers::transformNumFloat(sensor.value());
dv["u"] = sensor.uom();
}
if (sensor.type() == AnalogSensor::AnalogType::COUNTER
|| (sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT && sensor.type() <= AnalogSensor::AnalogType::PWM_2)
|| sensor.type() == AnalogSensor::AnalogType::RGB || sensor.type() == AnalogSensor::AnalogType::PULSE
@@ -535,4 +548,4 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
request->send(response);
}
} // namespace emsesp
} // namespace emsesp
+5 -1
View File
@@ -48,6 +48,7 @@ void WebLogService::start() {
if (settings.weblog_buffer) {
maximum_log_messages_ = settings.weblog_buffer;
}
limit_log_messages_ = maximum_log_messages_;
compact_ = settings.weblog_compact;
level_ = (uuid::log::Level)settings.weblog_level;
@@ -166,6 +167,7 @@ char * WebLogService::messagetime(char * out, const uint64_t t, const size_t buf
strftime(timestr, 25, "%FT%T", localtime(&t1));
snprintf(out, bufsize, "%s.%03d", timestr, (int)(wall_ms % 1000));
}
return out;
}
@@ -188,6 +190,7 @@ void WebLogService::transmit(const QueuedLogMessage & message) {
if (scratch_buf_.capacity() < len + 1) {
scratch_buf_.reserve(len + 1);
}
scratch_buf_.resize(len);
serializeJson(jsonDocument, scratch_buf_.data(), len + 1);
events_.send(scratch_buf_.c_str(), "message", message.id_);
@@ -223,6 +226,7 @@ void WebLogService::getSetValues(AsyncWebServerRequest * request, JsonVariant js
if (max_messages) {
maximum_log_messages_ = max_messages;
}
compact_ = json["compact"];
uuid::log::Logger::register_handler(this, level_);
@@ -247,4 +251,4 @@ void WebLogService::getSetValues(AsyncWebServerRequest * request, JsonVariant js
request->send(200); // OK
}
} // namespace emsesp
} // namespace emsesp
+20
View File
@@ -71,6 +71,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
EMSESP::nvs_.remove(key);
}
}
webScheduler.scheduleItems.clear();
EMSESP::webSchedulerService.ha_reset();
@@ -95,6 +96,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
if (EMSESP::nvs_.isKey(key)) {
webScheduler.scheduleItems.back().active = EMSESP::nvs_.getBool(key);
}
Command::add(
EMSdevice::DeviceType::SCHEDULER,
webScheduler.scheduleItems.back().name,
@@ -104,6 +106,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
FL_(schedule_cmd),
CommandFlag::ADMIN_ONLY);
}
return StateUpdateResult::CHANGED;
}
@@ -134,6 +137,7 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id,
return true;
}
}
return false;
}
@@ -150,6 +154,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
Mqtt::add_value_bool(output, scheduleItem.name, scheduleItem.active);
}
}
return true;
}
@@ -160,6 +165,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
strlcpy(name, scheduleItem.name[0] == '\0' ? Helpers::smallitoa(name, i++) : scheduleItem.name, sizeof(name));
get_value_json(output[name].to<JsonObject>(), scheduleItem);
}
return true;
}
@@ -169,6 +175,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
output["api_data"] = metrics;
return true;
}
return false;
}
@@ -191,10 +198,12 @@ std::string WebSchedulerService::get_metrics_prometheus() {
if (scheduleItem.name[0] == '\0') {
continue;
}
result += (std::string) "# HELP emsesp_" + scheduleItem.name + " " + scheduleItem.name + ", boolean, readable, visible, writable\n";
result += (std::string) "# TYPE emsesp_" + scheduleItem.name + " gauge\n";
result += (std::string) "emsesp_" + scheduleItem.name + " " + (scheduleItem.active ? "1\n" : "0\n");
}
return result;
}
@@ -213,6 +222,7 @@ void WebSchedulerService::get_value_json(JsonObject output, const ScheduleItem &
} else {
output["time"] = scheduleItem.time;
}
output["cmd_name"] = scheduleItem.cmd_name;
output["readable"] = true;
output["writeable"] = true;
@@ -241,11 +251,13 @@ void WebSchedulerService::publish(const bool force) {
if (!Mqtt::enabled() || scheduleItems_->empty()) {
return;
}
if (force) {
if (Mqtt::publish_single()) {
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
publish_single(scheduleItem.name, scheduleItem.active);
}
return;
} else if (!EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
return; // wait for first time period
@@ -279,6 +291,7 @@ void WebSchedulerService::publish(const bool force) {
} else {
snprintf(val_tpl, sizeof(val_tpl), "{{%s}}", val_obj); // omit value conditional Jinja2 template code
}
config["val_tpl"] = val_tpl;
char uniq_s[70];
@@ -328,6 +341,7 @@ bool WebSchedulerService::runScheduleCommand(const ScheduleItem & si) {
EMSESP::logger().warning("Schedule '%s': no command assigned", si.name);
return false;
}
return EMSESP::webCommandService.dispatchCommand(si.cmd_name.c_str());
}
@@ -339,6 +353,7 @@ bool WebSchedulerService::onChange(const char * cmd) {
return true;
}
}
return false;
}
@@ -369,6 +384,7 @@ void WebSchedulerService::loop() {
if (scheduleItems_->empty()) {
return;
}
// do not execute any command in the first 60 secondes
if (uuid::get_uptime_sec() < 60) {
return;
@@ -395,6 +411,7 @@ void WebSchedulerService::loop() {
scheduleItem.retry_cnt = runScheduleCommand(scheduleItem) ? 0xFF : 0;
}
}
last_tm_min = -1;
}
@@ -406,11 +423,13 @@ void WebSchedulerService::loop() {
&& scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) {
scheduleItem.retry_cnt = runScheduleCommand(scheduleItem) ? 0xFF : scheduleItem.retry_cnt + 1;
}
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
&& (uptime_min % scheduleItem.elapsed_min == 0)) {
runScheduleCommand(scheduleItem);
}
}
last_uptime_min = uptime_min;
}
@@ -427,6 +446,7 @@ void WebSchedulerService::loop() {
runScheduleCommand(scheduleItem);
}
}
last_tm_min = tm->tm_min;
}
}
+2 -1
View File
@@ -70,6 +70,7 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
void ha_reset() {
ha_configdone_ = false;
}
uint8_t count_entities();
bool onChange(const char * cmd);
@@ -96,4 +97,4 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
} // namespace emsesp
#endif
#endif
+4
View File
@@ -149,6 +149,7 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
if (settings.eth_power != -1) { // Ethernet Power -1 means disabled
EMSESP::system_.remove_gpio(settings.eth_power, true);
}
// remove the ethernet pins from valid list, regardless of whether the GPIOs are valid or not
EMSESP::system_.remove_gpio(23, true); // MDC
EMSESP::system_.remove_gpio(18, true); // MDIO
@@ -410,6 +411,7 @@ void WebSettingsService::onUpdate() {
if (WebSettings::has_flags(WebSettings::ChangeFlags::MODBUS)) {
EMSESP::system_.modbus_init();
}
WebSettings::reset_flags();
EMSESP::system_.reset_unused_gpios();
}
@@ -514,6 +516,7 @@ void WebSettings::set_board_profile(WebSettings & settings) {
settings.board_profile = "S32"; // ESP32 standard WiFi with PSRAM
}
}
// override if we know the target from the build config like C3, S2, S3 etc..
#elif CONFIG_IDF_TARGET_ESP32C3
settings.board_profile = "C3MINI";
@@ -561,6 +564,7 @@ bool WebSettings::check_flag(int prev_v, int new_v, uint16_t flag) {
#endif
return true;
}
return false;
}
+12
View File
@@ -69,6 +69,7 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
if (esp_sntp_enabled()) {
ntp_status = (EMSESP::system_.ntp_connected()) ? 2 : 1;
}
root["ntp_status"] = ntp_status;
if (ntp_status == 2) {
// send back actual time if NTP enabled and active
@@ -129,6 +130,7 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
root["psram_size"] = EMSESP::system_.PSram();
root["free_psram"] = ESP.getFreePsram() / 1024;
}
root["model"] = EMSESP::system_.getBBQKeesGatewayDetails();
root["board"] = EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::BOARD);
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
@@ -140,6 +142,7 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
if (partition != NULL) {
root["recovery_version"] = EMSESP::system_.recovery_version();
}
partition = esp_ota_get_next_update_partition(nullptr);
if (partition) {
uint64_t buffer;
@@ -159,6 +162,7 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
if (partition.second.version.empty() || partition.second.size == 0) {
continue;
}
JsonObject part = partitions.add<JsonObject>();
part["partition"] = partition.first;
part["version"] = partition.second.version;
@@ -182,6 +186,7 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
// we're ready to do the actual restart ASAP
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
}
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD) {
// error is reported, back to normal state
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
@@ -375,6 +380,7 @@ void WebStatusService::getVersions(JsonObject root) {
if (info.version.empty()) {
return;
}
JsonObject out = root[key].to<JsonObject>();
out["version"] = info.version;
out["date"] = info.date;
@@ -405,6 +411,7 @@ void WebStatusService::schedule_versions_refresh() {
if (next == 0) {
next = 1; // 0 is the "idle" sentinel — never let the wrap land there
}
versions_next_fetch_ms_ = next;
}
@@ -444,6 +451,7 @@ void WebStatusService::loop() {
if (versions_failures_ < VERSIONS_MAX_BACKOFF_STEPS) {
versions_failures_++;
}
interval = VERSIONS_RETRY_INTERVAL_MS << (versions_failures_ - 1);
}
@@ -451,6 +459,7 @@ void WebStatusService::loop() {
if (next == 0) {
next = 1;
}
versions_next_fetch_ms_ = next;
#endif
}
@@ -467,6 +476,7 @@ bool WebStatusService::refresh_versions_cache() {
EMSESP::logger().warning("Unable to retrieve online version information (HTTP error code %d)", http_code);
return false;
}
DeserializationError err = deserializeJson(doc, result);
if (err) {
#if defined(EMSESP_DEBUG)
@@ -483,6 +493,7 @@ bool WebStatusService::refresh_versions_cache() {
out = {};
return;
}
out.version = section["version"] | "";
out.date = section["date"] | "";
out.upgradeable = !out.version.empty() && FirmwareVersion(out.version) > current_version;
@@ -516,6 +527,7 @@ bool WebStatusService::current_upgradeable() const {
if (!versions_cache_valid_) {
return false;
}
return is_dev ? versions_dev_.upgradeable : versions_stable_.upgradeable;
}
+2
View File
@@ -18,6 +18,7 @@ class WebStatusService {
void set_current_version(const std::string & version) {
current_version_s = version;
}
std::string get_current_version() {
return current_version_s;
}
@@ -67,6 +68,7 @@ class WebStatusService {
std::string date;
bool upgradeable = false;
};
VersionInfo versions_stable_;
VersionInfo versions_dev_;
bool versions_cache_valid_ = false; // true once we've had at least one successful fetch