10 Commits

Author SHA1 Message Date
Proddy
de8309de4a Merge pull request #2836 from MichaelDvP/dev
force publish single on connect
2025-12-21 14:45:49 +01:00
MichaelDvP
bc3269037f chore: update generated files for v3.7.3-dev.39 2025-12-21 11:05:49 +00:00
MichaelDvP
31131427b8 fix RC120RF check 2025-12-21 11:53:39 +01:00
MichaelDvP
9957bff62b check Mqtt::enabled 2025-12-21 11:17:31 +01:00
MichaelDvP
f1841347a7 set lastresponse also if not connected 2025-12-19 18:50:51 +01:00
MichaelDvP
1b8b72c443 publish mqtt emsesp on-change messages on connect 2025-12-19 17:14:50 +01:00
MichaelDvP
b4affbff6d Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2025-12-19 16:53:36 +01:00
MichaelDvP
4ebe8cc0cc dallas dev_cla 2025-12-18 17:17:00 +01:00
MichaelDvP
6dabfb7fe2 analogsensor: add_ha_classes 2025-12-18 13:31:10 +01:00
MichaelDvP
611b1d9aca add RC120RF as remote 2025-12-18 13:30:41 +01:00
12 changed files with 102 additions and 43 deletions

View File

@@ -5577,7 +5577,7 @@
| hc1.currtemp | current room temperature | int16 (>=-3199<=3199) | C | false | HC | 1 | 1 | 1/10 |
| hc1.haclimate | mqtt discovery current room temperature | enum [selTemp\|roomTemp] (>=5<=30) | | false | HC | 2 | 1 | 1 |
### TR120RF, CR20RF
### RC120RF, TR120RF, CR20RF
| shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor |
|-|-|-|-|-|-|-|-|-|
| errorcode | error code | string | | false | DEVICE_DATA | 0 | 8 | 1 |

View File

@@ -5287,12 +5287,12 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"RC100H, CR10H",thermostat,200,hc1.seltemp,selected room temperature,int16 (>=0<=30),C,true,number.thermostat_hc1_selected_room_temperature,number.thermostat_hc1_seltemp,6,1,1/2,0,1
"RC100H, CR10H",thermostat,200,hc1.currtemp,current room temperature,int16 (>=-3199<=3199),C,false,sensor.thermostat_hc1_current_room_temperature,sensor.thermostat_hc1_currtemp,6,1,1/10,1,1
"RC100H, CR10H",thermostat,200,hc1.haclimate,mqtt discovery current room temperature,enum [selTemp\|roomTemp] (>=5<=30), ,false,sensor.thermostat_hc1_mqtt_discovery_current_room_temperature,sensor.thermostat_hc1_haclimate,6,1,1,2,1
"TR120RF, CR20RF",thermostat,249,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8
"TR120RF, CR20RF",thermostat,249,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25
"TR120RF, CR20RF",thermostat,249,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13
"TR120RF, CR20RF",thermostat,249,hc1.seltemp,selected room temperature,int16 (>=0<=30),C,true,number.thermostat_hc1_selected_room_temperature,number.thermostat_hc1_seltemp,6,1,1/2,0,1
"TR120RF, CR20RF",thermostat,249,hc1.currtemp,current room temperature,int16 (>=-3199<=3199),C,false,sensor.thermostat_hc1_current_room_temperature,sensor.thermostat_hc1_currtemp,6,1,1/10,1,1
"TR120RF, CR20RF",thermostat,249,hc1.haclimate,mqtt discovery current room temperature,enum [selTemp\|roomTemp] (>=5<=30), ,false,sensor.thermostat_hc1_mqtt_discovery_current_room_temperature,sensor.thermostat_hc1_haclimate,6,1,1,2,1
"RC120RF, TR120RF, CR20RF",thermostat,249,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8
"RC120RF, TR120RF, CR20RF",thermostat,249,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25
"RC120RF, TR120RF, CR20RF",thermostat,249,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13
"RC120RF, TR120RF, CR20RF",thermostat,249,hc1.seltemp,selected room temperature,int16 (>=0<=30),C,true,number.thermostat_hc1_selected_room_temperature,number.thermostat_hc1_seltemp,6,1,1/2,0,1
"RC120RF, TR120RF, CR20RF",thermostat,249,hc1.currtemp,current room temperature,int16 (>=-3199<=3199),C,false,sensor.thermostat_hc1_current_room_temperature,sensor.thermostat_hc1_currtemp,6,1,1/10,1,1
"RC120RF, TR120RF, CR20RF",thermostat,249,hc1.haclimate,mqtt discovery current room temperature,enum [selTemp\|roomTemp] (>=5<=30), ,false,sensor.thermostat_hc1_mqtt_discovery_current_room_temperature,sensor.thermostat_hc1_haclimate,6,1,1,2,1
"XCUMixer",mixer,8,hc1.flowtemphc,flow temperature (TC1),uint16 (>=0<=3199),C,false,sensor.mixer_hc1_flow_temperature_(TC1),sensor.mixer_hc1_flowtemphc,7,1,1/10,0,1
"XCUMixer",mixer,8,hc1.valvestatus,mixing valve actuator (VC1),uint8 (>=0<=100),%,false,sensor.mixer_hc1_mixing_valve_actuator_(VC1),sensor.mixer_hc1_valvestatus,7,1,1,1,1
"XCUMixer",mixer,8,hc1.flowsettemp,setpoint flow temperature,uint8 (>=0<=254),C,true,number.mixer_hc1_setpoint_flow_temperature,number.mixer_hc1_flowsettemp,7,1,1,2,1
Can't render this file because it is too large.

View File

@@ -664,13 +664,18 @@ void AnalogSensor::remove_ha_topic(const int8_t type, const uint8_t gpio) const
void AnalogSensor::publish_values(const bool force) {
uint8_t num_sensors = sensors_.size();
if (num_sensors == 0) {
if (!Mqtt::enabled() || num_sensors == 0) {
return;
}
if (force && Mqtt::publish_single()) {
for (const auto & sensor : sensors_) {
publish_sensor(sensor);
if (force) {
if (Mqtt::publish_single()) {
for (const auto & sensor : sensors_) {
publish_sensor(sensor);
}
return;
} else if (!EMSESP::mqtt_.get_publish_onchange(0)) {
return; // wait for first time periode
}
}
@@ -748,7 +753,8 @@ void AnalogSensor::publish_values(const bool force) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
// Set commands for some analog types
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
uint8_t valueType = DeviceValueType::INT16;
#if CONFIG_IDF_TARGET_ESP32
if (sensor.type() == AnalogType::PULSE || (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26)) {
#elif CONFIG_IDF_TARGET_ESP32S2
@@ -760,6 +766,7 @@ void AnalogSensor::publish_values(const bool force) {
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name());
config["cmd_t"] = command_topic;
Mqtt::add_ha_bool(config.as<JsonObject>());
valueType = DeviceValueType::BOOL;
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { // DAC
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name());
@@ -787,22 +794,22 @@ void AnalogSensor::publish_values(const bool force) {
} else if (sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name());
config["cmd_t"] = command_topic;
config["stat_cla"] = "total_increasing";
config["cmd_t"] = command_topic;
// config["mode"] = "box"; // auto, slider or box
// config["step"] = sensor.factor();
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
Mqtt::add_ha_bool(config.as<JsonObject>());
valueType = DeviceValueType::BOOL;
} else {
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
config["stat_cla"] = "measurement";
}
// add default_entity_id
std::string topic_str(topic);
config["def_ent_id"] = topic_str.substr(0, topic_str.find("/")) + "." + uniq_s;
Mqtt::add_ha_classes(config.as<JsonObject>(), EMSdevice::DeviceType::ANALOGSENSOR, valueType, sensor.uom());
// dev section with model is only created on the 1st sensor
Mqtt::add_ha_dev_section(config.as<JsonObject>(), "Analog Sensors", !ha_dev_created);
Mqtt::add_ha_avty_section(config.as<JsonObject>(), stat_t, val_cond);

View File

@@ -129,7 +129,7 @@
// Thermostat remote - 0x38
{ 3, DeviceType::THERMOSTAT, "RT800, RC220", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
{200, DeviceType::THERMOSTAT, "RC100H, CR10H", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
{249, DeviceType::THERMOSTAT, "TR120RF, CR20RF", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
{249, DeviceType::THERMOSTAT, "RC120RF, TR120RF, CR20RF", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
// Solar Modules - 0x30 (for solar), 0x2A, 0x41 (for ww)
{ 73, DeviceType::SOLAR, "SM10", DeviceFlags::EMS_DEVICE_FLAG_SM10},

View File

@@ -511,6 +511,11 @@ void Mqtt::on_connect() {
// send initial MQTT messages for some of our services
EMSESP::system_.send_heartbeat(); // send heartbeat
// for publish on change publish the initial complete list
EMSESP::webCustomEntityService.publish(true);
EMSESP::webSchedulerService.publish(true);
EMSESP::analogsensor_.publish_values(true);
EMSESP::temperaturesensor_.publish_values(true);
}
// Home Assistant Discovery - the main master Device called EMS-ESP
@@ -590,10 +595,6 @@ void Mqtt::ha_status() {
// 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) {
if (!mqtt_enabled_ || topic.empty() || !connected()) {
return false; // quit, not using MQTT
}
if (topic == "response" && operation == Operation::PUBLISH) {
lastresponse_ = payload;
if (!send_response_) {
@@ -601,6 +602,10 @@ bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, con
}
}
if (!mqtt_enabled_ || topic.empty() || !connected()) {
return false; // quit, not using MQTT
}
// check free mem
#ifndef EMSESP_STANDALONE
// if (ESP.getFreeHeap() < 60 * 1024 || ESP.getMaxAllocHeap() < 40 * 1024) {

View File

@@ -76,9 +76,11 @@ uint8_t Roomctrl::get_hc(uint8_t addr) {
if (addr >= 0x40 && addr <= 0x44 && type_[addr - 0x40] == SENSOR) {
return addr - 0x40; // SENSOR
} else if (addr >= 0x38 && addr <= 0x3B
&& (type_[addr - 0x38] == RC100H || type_[addr - 0x38] == RC200 || type_[addr - 0x38] == RC100 || type_[addr - 0x38] == RT800)) {
return addr - 0x38; // RC100H, RC200
} else if (addr >= 0x18 && addr <= 0x1B && (type_[addr - 0x18] == RC20 || type_[addr - 0x18] == FB10)) {
&& (type_[addr - 0x38] == RC100H || type_[addr - 0x38] == RC200 || type_[addr - 0x38] == RC100 || type_[addr - 0x38] == RT800
|| type_[addr - 0x38] == RC120RF)) {
return addr - 0x38; // RC100H, RC200
}
else if (addr >= 0x18 && addr <= 0x1B && (type_[addr - 0x18] == RC20 || type_[addr - 0x18] == FB10)) {
return addr - 0x18; // RC20, FB10
}
return 0xFF; // invalid
@@ -88,7 +90,7 @@ uint8_t Roomctrl::get_hc(uint8_t addr) {
* if remote control is active send the temperature every 15 seconds
*/
void Roomctrl::send(uint8_t addr) {
if (addr & 0x80) {
if ((addr & 0x80) || EMSESP::system_.readonly_mode()) {
return;
}
uint8_t hc = get_hc(addr);
@@ -108,7 +110,7 @@ void Roomctrl::send(uint8_t addr) {
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) {
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;
@@ -145,6 +147,9 @@ void Roomctrl::send(uint8_t addr) {
* check if there is a message for the remote room controller
*/
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;
@@ -249,6 +254,14 @@ void Roomctrl::version(uint8_t addr, uint8_t dst, uint8_t hc) {
data[7] = EMSbus::calculate_crc(data, 7); // append CRC
EMSuart::transmit(data, 8);
return;
} else if (type_[hc] == RC120RF) {
data[5] = 16; // version 16.02
data[6] = 2;
data[7] = 0;
data[8] = 0xFF;
data[9] = EMSbus::calculate_crc(data, 9); // append CRC
EMSuart::transmit(data, 10);
return;
}
}
@@ -308,7 +321,7 @@ void Roomctrl::temperature(uint8_t addr, uint8_t dst, uint8_t hc) {
data[5] = 0x2B + hc;
data[6] = (uint8_t)(remotetemp_[hc] >> 8);
data[7] = (uint8_t)(remotetemp_[hc] & 0xFF);
uint16_t t1 = remotetemp_[hc] * 10 + 3;
uint16_t t1 = remotetemp_[hc] * 10;
data[8] = (uint8_t)(t1 >> 8);
data[9] = (uint8_t)(t1 & 0xFF);
data[10] = 1; // not sure what this is and if we need it, maybe mode?
@@ -339,13 +352,27 @@ void Roomctrl::temperature(uint8_t addr, uint8_t dst, uint8_t hc) {
data[5] = 0x2B + hc;
data[6] = (uint8_t)(remotetemp_[hc] >> 8);
data[7] = (uint8_t)(remotetemp_[hc] & 0xFF);
uint16_t t1 = remotetemp_[hc] * 10 + 3;
uint16_t t1 = remotetemp_[hc] * 10;
data[8] = (uint8_t)(t1 >> 8);
data[9] = (uint8_t)(t1 & 0xFF);
data[10] = 1; // not sure what this is and if we need it, maybe mode?
data[11] = 9; // not sure what this is and if we need it, maybe mode?
data[12] = EMSbus::calculate_crc(data, 12); // append CRC
EMSuart::transmit(data, 13);
} else if (type_[hc] == RC120RF) {
data[2] = 0xFF;
data[3] = 0;
data[4] = 3;
data[5] = 0x2B + hc;
data[6] = (uint8_t)(remotetemp_[hc] >> 8);
data[7] = (uint8_t)(remotetemp_[hc] & 0xFF);
uint16_t t1 = remotetemp_[hc] * 10;
data[8] = (uint8_t)(t1 >> 8);
data[9] = (uint8_t)(t1 & 0xFF);
data[10] = 0; // not sure what this is and if we need it
data[11] = 0; // not sure what this is and if we need it
data[12] = EMSbus::calculate_crc(data, 12); // append CRC
EMSuart::transmit(data, 13);
}
}

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Roomctrl {
public:
// Product-Id of the remote
enum RemoteType : uint8_t { NONE = 0, RC20 = 113, FB10 = 109, RC100H = 200, SENSOR = 0x40, RC200 = 157, RC100 = 165, RT800 = 3 };
enum RemoteType : uint8_t { NONE = 0, RC20 = 113, FB10 = 109, RC100H = 200, SENSOR = 0x40, RC200 = 157, RC100 = 165, RT800 = 3, RC120RF = 249 };
static void send(uint8_t addr);
static void check(uint8_t addr, const uint8_t * data, const uint8_t length);

View File

@@ -473,10 +473,15 @@ void TemperatureSensor::publish_values(const bool force) {
return;
}
if (force && Mqtt::publish_single()) {
if (force) {
if (Mqtt::publish_single()) {
for (const auto & sensor : sensors_) {
publish_sensor(sensor);
}
return;
} else if (!EMSESP::mqtt_.get_publish_onchange(0)) {
return; // wait for first time periode
}
}
JsonDocument doc;
@@ -543,6 +548,8 @@ void TemperatureSensor::publish_values(const bool force) {
config["uniq_id"] = uniq_s;
config["def_ent_id"] = (std::string) "sensor." + uniq_s;
config["name"] = (const char *)sensor.name();
config["stat_cla"] = "measurement";
config["dev_cla"] = "temperature";
// dev section with model is only created on the 1st sensor
Mqtt::add_ha_dev_section(config.as<JsonObject>(), "Temperature Sensors", !ha_dev_created);

View File

@@ -2109,6 +2109,8 @@ bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
Roomctrl::set_remotetemp(Roomctrl::RC100H, hc->hc(), hc->remotetemp); // RC100H
} else if (hc->control == 2) { // RC100(2)
Roomctrl::set_remotetemp(Roomctrl::RC100, hc->hc(), hc->remotetemp); // RC100
} else if (hc->control == 5) {
Roomctrl::set_remotetemp(Roomctrl::RC120RF, hc->hc(), hc->remotetemp);
} else if (hc->control == 6) {
Roomctrl::set_remotetemp(Roomctrl::RT800, hc->hc(), hc->remotetemp);
} else {
@@ -2141,6 +2143,9 @@ bool Thermostat::set_remotehum(const char * value, const int8_t id) {
if (hc->control == 3) {
Roomctrl::set_remotehum(Roomctrl::RC100H, hc->hc(), hc->remotehum); // RC100H
return true;
} else if (hc->control == 5) {
Roomctrl::set_remotehum(Roomctrl::RC120RF, hc->hc(), hc->remotehum);
return true;
} else if (hc->control == 6) {
Roomctrl::set_remotehum(Roomctrl::RT800, hc->hc(), hc->remotehum);
return true;
@@ -2310,6 +2315,8 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
Roomctrl::set_remotetemp(Roomctrl::RC100, hc->hc(), hc->remotetemp);
} else if (ctrl == 3) {
Roomctrl::set_remotetemp(Roomctrl::RC100H, hc->hc(), hc->remotetemp);
} else if (ctrl == 5) {
Roomctrl::set_remotetemp(Roomctrl::RC120RF, hc->hc(), hc->remotetemp); // RC120
} else if (ctrl == 6) {
Roomctrl::set_remotetemp(Roomctrl::RT800, hc->hc(), hc->remotetemp);
} else {

View File

@@ -378,10 +378,20 @@ void WebCustomEntityService::publish_single(CustomEntityItem & entity) {
}
// publish to Mqtt
void WebCustomEntityService::publish() {
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(0)) {
return; // wait for first time periode
}
}
JsonDocument doc;
JsonObject output = doc.to<JsonObject>();

View File

@@ -58,7 +58,7 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
void begin();
void publish_single(CustomEntityItem & entity);
void publish();
void publish(const bool force = false);
bool command_setvalue(const char * value, const int8_t id, const char * name);
bool get_value_info(JsonObject output, const char * cmd);
void get_value_json(JsonObject output, CustomEntityItem const & entity);

View File

@@ -105,10 +105,6 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
CommandFlag::ADMIN_ONLY);
}
}
EMSESP::webSchedulerService.ha_reset();
EMSESP::webSchedulerService.publish();
return StateUpdateResult::CHANGED;
}
@@ -217,17 +213,17 @@ void WebSchedulerService::publish_single(const char * name, const bool state) {
// publish to Mqtt
void WebSchedulerService::publish(const bool force) {
if (force) {
ha_configdone_ = false;
}
if (!Mqtt::enabled() || scheduleItems_->empty()) {
return;
}
if (Mqtt::publish_single() && force) {
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
publish_single(scheduleItem.name, scheduleItem.active);
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(0)) {
return; // wait for first time periode
}
}