Merge pull request #2647 from MichaelDvP/dev

fixes and additions, see description
This commit is contained in:
Proddy
2025-10-07 17:38:33 +02:00
committed by GitHub
26 changed files with 495 additions and 94 deletions

View File

@@ -18,11 +18,15 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
- disinfection command [#2601](https://github.com/emsesp/EMS-ESP32/issues/2601)
- added new board profile for upcoming BBQKees E32V2.2
- set differential pressure entity in Mixer device
- set set climate action cooling/heating in HA
- set set climate action cooling/heating in HA [#2583](https://github.com/emsesp/EMS-ESP32/issues/2583)
- Internal sensors of E32V2_2
- FW200 display options
- CR11 mode settings OFF/MANUAL depends on selTemp
- FW200 display options [#2610](https://github.com/emsesp/EMS-ESP32/discussions/2610)
- CR11 mode settings OFF/MANUAL depends on selTemp [#2437](https://github.com/emsesp/EMS-ESP32/issues/2437)
- Fuse settings for BBQKees boards
- Analogsensors for pulse output [#2624](https://github.com/emsesp/EMS-ESP32/discussions/2624)
- Analogsensors frequency input [#2631](https://github.com/emsesp/EMS-ESP32/discussions/2631)
- SRC plus thermostats [#2636](https://github.com/emsesp/EMS-ESP32/issues/2636)
- Greenstar 2000 [#2645](https://github.com/emsesp/EMS-ESP32/issues/2645)
## Fixed
@@ -39,9 +43,13 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
- Add pulsed water meter input to V1.3 gateway with Lilygo S3 [#2550](https://github.com/emsesp/EMS-ESP32/issues/2550)
- fix missing long 10-second press of Button to perform a factory reset
- fix wwMaxPower on Junkers ZBS14 [#2609](https://github.com/emsesp/EMS-ESP32/issues/2609)
- ventilation bypass state from telegram 0x55C [#1197](https://github.com/emsesp/EMS-ESP32/issues/1197)
- set selflowtemp for ems+ boilers [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641)
## Changed
- show console log with ISO date/time [#2533](https://github.com/emsesp/EMS-ESP32/discussions/2533)
- remove ESP32 CPU temperature
- updated core libraries like AsyncTCP, AsyncWebServer and Modbus
- remove command `scan deep`
- ignore repeated `forceheatingoff` commands [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641)

View File

@@ -439,7 +439,8 @@ const Sensors = () => {
<Cell>{a.n}</Cell>
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
{(a.t === AnalogType.DIGITAL_OUT && a.g !== 25 && a.g !== 26) ||
a.t === AnalogType.DIGITAL_IN ? (
a.t === AnalogType.DIGITAL_IN ||
a.t === AnalogType.PULSE ? (
<Cell stiff>{a.v ? LL.ON() : LL.OFF()}</Cell>
) : (
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>

View File

@@ -132,7 +132,9 @@ const SensorsAnalogDialog = ({
))}
</TextField>
</Grid>
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
{((editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE) ||
(editItem.t >= AnalogType.FREQ_0 &&
editItem.t <= AnalogType.FREQ_2)) && (
<Grid>
<TextField
name="u"
@@ -348,6 +350,42 @@ const SensorsAnalogDialog = ({
</Grid>
</>
)}
{editItem.t === AnalogType.PULSE && (
<>
<Grid>
<TextField
name="o"
label={LL.POLARITY()}
value={editItem.o}
sx={{ width: '11ch' }}
select
onChange={updateFormValue}
>
<MenuItem value={0}>{LL.ACTIVEHIGH()}</MenuItem>
<MenuItem value={1}>{LL.ACTIVELOW()}</MenuItem>
</TextField>
</Grid>
<Grid>
<TextField
name="f"
label="Pulse"
value={numberValue(editItem.f)}
type="number"
sx={{ width: '15ch' }}
variant="outlined"
onChange={updateFormValue}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">s</InputAdornment>
)
},
htmlInput: { min: '0', max: '10000', step: '0.1' }
}}
/>
</Grid>
</>
)}
</Grid>
</DialogContent>
<DialogActions>

View File

@@ -188,7 +188,8 @@ export enum DeviceValueUOM {
VOLTS,
MBAR,
LH,
CTKWH
CTKWH,
HZ
}
export const DeviceValueUOM_s = [
@@ -218,7 +219,8 @@ export const DeviceValueUOM_s = [
'V',
'mbar',
'l/h',
'ct/kWh'
'ct/kWh',
'Hz'
];
export enum AnalogType {
@@ -234,7 +236,11 @@ export enum AnalogType {
PWM_1 = 8,
PWM_2 = 9,
NTC = 10,
RGB = 11
RGB = 11,
PULSE = 12,
FREQ_0 = 13,
FREQ_1 = 14,
FREQ_2 = 15
}
export const AnalogTypeNames = [
@@ -249,7 +255,11 @@ export const AnalogTypeNames = [
'PWM 1',
'PWM 2',
'NTC Temp.',
'RGB Led'
'RGB Led',
'Pulse',
'Freq 0',
'Freq 1',
'Freq 2'
];
type BoardProfiles = Record<string, string>;

View File

@@ -33,9 +33,9 @@ void MqttSettingsService::begin() {
void MqttSettingsService::startClient() {
static bool isSecure = false;
if (_mqttClient != nullptr) {
if (_mqttClient) {
// do we need to change the client?
if (_state.enabled && ((isSecure && _state.enableTLS) || (!isSecure && !_state.enableTLS))) {
if ((isSecure && _state.enableTLS) || (!isSecure && !_state.enableTLS)) {
return;
}
delete _mqttClient;
@@ -79,9 +79,6 @@ void MqttSettingsService::startClient() {
}
void MqttSettingsService::loop() {
if (!_state.enabled || _mqttClient == nullptr || emsesp::EMSESP::system_.systemStatus() != 0) {
return;
}
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
// reconfigure MQTT client
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();

View File

@@ -23,6 +23,31 @@ namespace emsesp {
uuid::log::Logger AnalogSensor::logger_{F_(analogsensor), uuid::log::Facility::DAEMON};
#ifndef EMSESP_STANDALONE
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
unsigned long AnalogSensor::edge[] = {0, 0, 0};
unsigned long AnalogSensor::edgecnt[] = {0, 0, 0};
void IRAM_ATTR AnalogSensor::freqIrq0() {
portENTER_CRITICAL_ISR(&mux);
edgecnt[0]++;
edge[0] = micros();
portEXIT_CRITICAL_ISR(&mux);
}
void IRAM_ATTR AnalogSensor::freqIrq1() {
portENTER_CRITICAL_ISR(&mux);
edgecnt[1]++;
edge[1] = micros();
portEXIT_CRITICAL_ISR(&mux);
}
void IRAM_ATTR AnalogSensor::freqIrq2() {
portENTER_CRITICAL_ISR(&mux);
edgecnt[2]++;
edge[2] = micros();
portEXIT_CRITICAL_ISR(&mux);
}
#endif
void AnalogSensor::start(const bool factory_settings) {
// if (factory_settings && EMSESP::nvs_.getString("boot").equals("E32V2_2") && EMSESP::nvs_.getString("hwrevision").equals("3.0")) {
if (factory_settings && analogReadMilliVolts(39) > 700) { // core voltage > 2.6V
@@ -92,7 +117,7 @@ void AnalogSensor::reload(bool get_nvs) {
for (const auto & sensor : settings.analogCustomizations) { // search customlist
if (sensor_.gpio() == sensor.gpio) {
// for output sensors set value to new start-value
if (sensor.type >= AnalogType::DIGITAL_OUT
if (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2
&& (sensor_.type() != sensor.type || sensor_.offset() != sensor.offset || sensor_.factor() != sensor.factor)) {
sensor_.set_value(sensor.offset);
}
@@ -134,7 +159,7 @@ void AnalogSensor::reload(bool get_nvs) {
}
}
if (sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2)
|| sensor.type == AnalogType::RGB) {
|| sensor.type == AnalogType::RGB || sensor.type == AnalogType::PULSE) {
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
sensor.name.c_str(),
@@ -142,6 +167,7 @@ void AnalogSensor::reload(bool get_nvs) {
sensor.type == AnalogType::COUNTER ? FL_(counter)
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
: sensor.type == AnalogType::RGB ? FL_(RGB)
: sensor.type == AnalogType::PULSE ? FL_(pulse)
: FL_(pwm),
CommandFlag::ADMIN_ONLY);
}
@@ -204,6 +230,18 @@ void AnalogSensor::reload(bool get_nvs) {
sensor.set_offset(0);
sensor.set_value(0);
publish_sensor(sensor);
#ifndef EMSESP_STANDALONE
} else if (sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::FREQ_2) {
LOG_DEBUG("Frequency on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
sensor.set_offset(0);
sensor.set_value(0);
publish_sensor(sensor);
auto index = sensor.type() - AnalogType::FREQ_0;
attachInterrupt(sensor.gpio(), index == 0 ? freqIrq0 : index == 1 ? freqIrq1 : freqIrq2, FALLING);
lastedge[index] = edge[index] = micros();
edgecnt[index] = 0;
#endif
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
LOG_DEBUG("Digital Read on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
@@ -260,6 +298,11 @@ void AnalogSensor::reload(bool get_nvs) {
sensor.set_value(sensor.offset());
}
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::PULSE) {
LOG_DEBUG("Pulse on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), OUTPUT);
digitalWrite(sensor.gpio(), (sensor.offset() == 1) ^ (sensor.value() == 1));
sensor.polltime_ = sensor.value() != 0 ? uuid::get_uptime() + (sensor.factor() * 1000) : 0;
} else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) {
LOG_DEBUG("PWM output on GPIO %02d", sensor.gpio());
#if ESP_IDF_VERSION_MAJOR >= 5
@@ -330,6 +373,25 @@ void AnalogSensor::measure() {
changed_ = true;
publish_sensor(sensor);
}
#ifndef EMSESP_STANDALONE
} else if (sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::FREQ_2) {
auto index = sensor.type() - AnalogType::FREQ_0;
auto oldval = sensor.value();
if (edge[index] != lastedge[index] && edgecnt[index] > 0) {
portENTER_CRITICAL_ISR(&mux);
auto t = (edge[index] - lastedge[index]) / edgecnt[index];
lastedge[index] = edge[index];
edgecnt[index] = 0;
portEXIT_CRITICAL_ISR(&mux);
sensor.set_value(sensor.factor() * 1000000.0 / t);
} else if (micros() - edge[index] > 10000000ul && sensor.value() > 0) {
sensor.set_value(0);
}
if (sensor.value() != oldval) {
changed_ = true;
publish_sensor(sensor);
}
#endif
}
}
}
@@ -337,9 +399,9 @@ void AnalogSensor::measure() {
// poll digital io every time with debounce
// go through the list of digital sensors
for (auto & sensor : sensors_) {
auto old_value = sensor.value(); // remember current value before reading
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::TIMER
|| sensor.type() == AnalogType::RATE) {
auto old_value = sensor.value(); // remember current value before reading
auto current_reading = digitalRead(sensor.gpio());
if (sensor.poll_ != current_reading) { // check for pinchange
sensor.polltime_ = uuid::get_uptime(); // remember time of pinchange
@@ -362,13 +424,17 @@ void AnalogSensor::measure() {
sensor.last_polltime_ = sensor.polltime_;
}
}
// see if there is a change and increment # reads
if (old_value != sensor.value()) {
sensorreads_++;
changed_ = true;
publish_sensor(sensor);
}
}
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_++;
changed_ = true;
publish_sensor(sensor);
}
}
@@ -562,6 +628,9 @@ void AnalogSensor::publish_values(const bool force) {
case AnalogType::PWM_0:
case AnalogType::PWM_1:
case AnalogType::PWM_2:
case AnalogType::FREQ_0:
case AnalogType::FREQ_1:
case AnalogType::FREQ_2:
case AnalogType::RGB:
case AnalogType::NTC:
dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // double
@@ -762,7 +831,7 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
output["analog"] = FL_(list_sensortype)[sensor.type()];
output["value"] = sensor.value();
output["readable"] = true;
output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() >= AnalogType::RGB
output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE
|| (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2);
output["visible"] = true;
if (sensor.type() == AnalogType::COUNTER) {
@@ -842,6 +911,12 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
#endif
LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::PULSE) {
uint8_t v = val;
sensor.set_value(v);
pinMode(sensor.gpio(), OUTPUT);
digitalWrite(sensor.gpio(), (sensor.offset() != 0) ^ (sensor.value() != 0));
sensor.polltime_ = sensor.value() != 0 ? uuid::get_uptime() + (sensor.factor() * 1000) : 0;
} else if (sensor.type() == AnalogType::DIGITAL_OUT) {
uint8_t v = val;
#if CONFIG_IDF_TARGET_ESP32

View File

@@ -124,7 +124,11 @@ class AnalogSensor {
PWM_1 = 8,
PWM_2 = 9,
NTC = 10,
RGB = 11
RGB = 11,
PULSE = 12,
FREQ_0 = 13,
FREQ_1 = 14,
FREQ_2 = 15
};
void start(const bool factory_settings = false);
@@ -190,6 +194,15 @@ class AnalogSensor {
bool changed_ = true; // this will force a publish of all sensors when initialising
uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0;
#ifndef EMSESP_STANDALONE
static void IRAM_ATTR freqIrq0();
static void IRAM_ATTR freqIrq1();
static void IRAM_ATTR freqIrq2();
static unsigned long edge[3];
static unsigned long edgecnt[3];
unsigned long lastedge[3] = {0, 0, 0};
#endif
};
} // namespace emsesp

View File

@@ -364,6 +364,10 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
// EMS device commands
//
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, [](Shell & shell, const std::vector<std::string> & arguments) {
EMSESP::scan_devices();
});
/* removed scan deep
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, {F_(deep_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.empty()) {
EMSESP::scan_devices();
@@ -379,7 +383,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
}
}
});
*/
// read <deviceID> <type ID> [offset] [length]
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,

View File

@@ -25,6 +25,7 @@
// Boilers - 0x08
{ 8, DeviceType::BOILER, "CS5800i, CS6800i, WLW176i, WLW186i", DeviceFlags::EMS_DEVICE_FLAG_CS6800},
{ 11, DeviceType::BOILER, "Greenstar 2000", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 12, DeviceType::BOILER, "C1200W", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 16, DeviceType::BOILER, "CS5800iG", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 64, DeviceType::BOILER, "BK13/BK15, Smartline, GB1*2", DeviceFlags::EMS_DEVICE_FLAG_NONE},

View File

@@ -258,6 +258,9 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (!strcmp(lowtopic, F_(pool))) {
return DeviceType::POOL;
}
if (!strcmp(lowtopic, F_(connect))) {
return DeviceType::CONNECT;
}
// non EMS
if (!strcmp(lowtopic, F_(custom))) {
@@ -1896,6 +1899,11 @@ void EMSdevice::mqtt_ha_entity_config_create() {
create_device_config = false; // only create the main config once
count++;
}
// SRC thermostats mapped to connect/hs1/...
if (dv.tag >= DeviceValueTAG::TAG_HS1 && !strcmp(dv.short_name, FL_(roomtemp)[0])) {
Mqtt::publish_ha_climate_config(dv.tag, true, false, dv.min, dv.max);
}
#ifndef EMSESP_STANDALONE
// always create minimum one config
if (count && (heap_caps_get_free_size(MALLOC_CAP_8BIT) < 65 * 1024)) { // checks free Heap+PSRAM

View File

@@ -108,10 +108,10 @@ DeviceValue::DeviceValue(uint8_t device_type,
const char * DeviceValue::DeviceValueUOM_s[] = {
F_(uom_blank), // 0
F_(uom_degrees), F_(uom_degrees), F_(uom_percent), F_(uom_lmin), F_(uom_kwh), F_(uom_wh), FL_(hours)[0],
FL_(minutes)[0], F_(uom_ua), F_(uom_bar), F_(uom_kw), F_(uom_w), F_(uom_kb), FL_(seconds)[0],
F_(uom_dbm), F_(uom_fahrenheit), F_(uom_mv), F_(uom_sqm), F_(uom_m3), F_(uom_l), F_(uom_kmin),
F_(uom_k), F_(uom_volts), F_(uom_mbar), F_(uom_lh), F_(uom_ctkwh), F_(uom_blank)
F_(uom_degrees), F_(uom_degrees), F_(uom_percent), F_(uom_lmin), F_(uom_kwh), F_(uom_wh), FL_(hours)[0], FL_(minutes)[0],
F_(uom_ua), F_(uom_bar), F_(uom_kw), F_(uom_w), F_(uom_kb), FL_(seconds)[0], F_(uom_dbm), F_(uom_fahrenheit),
F_(uom_mv), F_(uom_sqm), F_(uom_m3), F_(uom_l), F_(uom_kmin), F_(uom_k), F_(uom_volts), F_(uom_mbar),
F_(uom_lh), F_(uom_ctkwh), F_(uom_hz), F_(uom_blank)
};

View File

@@ -75,7 +75,8 @@ class DeviceValue {
MBAR, // 24 - mbar
LH, // 25 - l/h
CTKWH, // 26 - ct/kWh
CONNECTIVITY // 27 - used in HA
HZ, // 27 - Hz
CONNECTIVITY // 28 - used in HA
};
// TAG mapping - maps to DeviceValueTAG_s in emsdevicevalue.cpp

View File

@@ -1307,7 +1307,11 @@ 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;
}

View File

@@ -264,6 +264,7 @@ MAKE_WORD_CUSTOM(uom_volts, "V")
MAKE_WORD_CUSTOM(uom_mbar, "mbar")
MAKE_WORD_CUSTOM(uom_lh, "l/h")
MAKE_WORD_CUSTOM(uom_ctkwh, "ct/kWh")
MAKE_WORD_CUSTOM(uom_hz, "Hz")
// MQTT topics and prefixes
MAKE_WORD_CUSTOM(heating_active, "heating_active")
@@ -277,7 +278,8 @@ MAKE_ENUM_FIXED(list_syslog_level, "off", "emerg", "alert", "crit", "error", "wa
MAKE_ENUM_FIXED(counter, "counter")
MAKE_ENUM_FIXED(digital_out, "digital_out")
MAKE_ENUM_FIXED(RGB, "RGB")
MAKE_ENUM_FIXED(list_sensortype, "disabled", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2", "NTC Temp", "RGB Led")
MAKE_ENUM_FIXED(pulse, "pulse")
MAKE_ENUM_FIXED(list_sensortype, "disabled", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2", "NTC Temp", "RGB Led", "pulse", "freq 0", "freq 1", "freq 2")
// watch
MAKE_ENUM_FIXED(list_watch, "off", "on", "raw", "unknown")
@@ -337,6 +339,7 @@ MAKE_ENUM(enum_mode4, FL_(nofrost), FL_(eco), FL_(heat), FL_(auto)) // JUNKERS
MAKE_ENUM(enum_mode5, FL_(auto), FL_(off)) // CRF
MAKE_ENUM(enum_mode6, FL_(nofrost), FL_(night), FL_(day)) // RC10
MAKE_ENUM(enum_mode7, FL_(off), FL_(manual)) // CR11
MAKE_ENUM(enum_mode8, FL_(auto), FL_(heat), FL_(cool), FL_(off)) // SRC room thermostats
MAKE_ENUM(enum_mode_ha, FL_(off), FL_(heat), FL_(auto)) // HA climate
MAKE_ENUM(enum_modetype, FL_(eco), FL_(comfort))

View File

@@ -915,6 +915,9 @@ MAKE_TRANSLATION(status, "status", "status", "Status", "Status", "Status", "stat
// RF sensor, id 0x40, telegram 0x435
MAKE_TRANSLATION(RFTemp, "rftemp", "RF room temperature sensor", "RF Raumtemperatursensor", "RF ruimtetemperatuur sensor", "RF Rumsgivare Temperatur", "bezprzewodowy czujnik temperatury pomieszczenia", "RF romsgiver temp", "capteur de température de pièce RF", "RF oda sıcaklık sensörü", "Sensore di temperatura ambiente RF", "RF snímač izbovej teploty", "RF senzor teploty místnosti")
// gateway thermostat
MAKE_TRANSLATION(name, "name", "name", "Name")
// ventilation
MAKE_TRANSLATION(outFresh, "outfresh", "outdoor fresh air", "Außenlufttemp.", "temperatuur buitenlucht", "Utelufttemperatur", "świeże powietrze z zewnątrz", "", "", "dış ortam taze hava", "aria fresca esterna", "čerstvý vzduch vonku", "venkovní čerstvý vzduch") // TODO translate
MAKE_TRANSLATION(inFresh, "infresh", "indoor fresh air", "Zulufttemp.", "temperatuur aanvoer", "Tillufttemperatur", "nawiew", "", "", "iç ortam taze hava", "aria fresca interna", "čerstvý vzduch v interiéri", "vnitřní čerstvý vzduch") // TODO translate

View File

@@ -528,8 +528,8 @@ void Mqtt::ha_status() {
strcpy(uniq, "system_status");
}
doc["uniq_id"] = uniq;
doc["obj_id"] = uniq;
doc["uniq_id"] = uniq;
doc["default_entity_id"] = uniq;
doc["stat_t"] = Mqtt::base() + "/status";
doc["name"] = "System status";
@@ -827,7 +827,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
snprintf(entity_with_tag, sizeof(entity_with_tag), "%s", entity);
}
// build unique identifier also used as object_id which also becomes the Entity ID in HA
// build unique identifier also used as default_entity_id which also becomes the Entity ID in HA
char uniq_id[80];
// list of boiler entities that need conversion for 3.6 compatibility, add ww suffix
@@ -980,8 +980,8 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// build the full payload
JsonDocument doc;
doc["uniq_id"] = uniq_id;
doc["obj_id"] = uniq_id; // same as unique_id
doc["uniq_id"] = uniq_id;
doc["default_entity_id"] = uniq_id; // same as unique_id
char sample_val[30] = "0"; // sample, correct(!) entity value, used only to prevent warning/error in HA if real value is not published yet
@@ -1235,7 +1235,9 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
}
bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, const bool remove, const int16_t min, const uint32_t max) {
uint8_t hc_num = tag;
uint8_t hc_num = tag < DeviceValueTAG::TAG_HS1 ? tag : tag - DeviceValueTAG::TAG_HS1 + 1;
const char * devicename = tag < DeviceValueTAG::TAG_HS1 ? "thermostat" : "connect";
const char * tagname = tag < DeviceValueTAG::TAG_HS1 ? "hc" : "hs";
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
char topic_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
@@ -1253,21 +1255,21 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp,
char min_s[10];
char max_s[10];
snprintf(topic, sizeof(topic), "climate/%s/thermostat_hc%d/config", Mqtt::basename().c_str(), hc_num);
snprintf(topic, sizeof(topic), "climate/%s/%s_%s%d/config", Mqtt::basename().c_str(), devicename, tagname, hc_num);
if (remove) {
return queue_remove_topic(topic); // publish empty payload with retain flag
}
if (Mqtt::is_nested()) {
// nested format
snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.hc%d.mode", hc_num);
snprintf(hc_mode_cond, sizeof(hc_mode_cond), "value_json.hc%d is undefined or %s is undefined", hc_num, hc_mode_s);
snprintf(seltemp_s, sizeof(seltemp_s), "value_json.hc%d.seltemp", hc_num);
snprintf(seltemp_cond, sizeof(seltemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, seltemp_s);
snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.%s%d.mode", tagname, hc_num);
snprintf(hc_mode_cond, sizeof(hc_mode_cond), "value_json.%s%d is undefined or %s is undefined", tagname, hc_num, hc_mode_s);
snprintf(seltemp_s, sizeof(seltemp_s), "value_json.%s%d.seltemp", tagname, hc_num);
snprintf(seltemp_cond, sizeof(seltemp_cond), "value_json.%s%d is defined and %s is defined", tagname, hc_num, seltemp_s);
if (has_roomtemp) {
snprintf(currtemp_s, sizeof(currtemp_s), "value_json.hc%d.currtemp", hc_num);
snprintf(currtemp_cond, sizeof(currtemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, currtemp_s);
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);
}
snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_NONE).c_str());
} else {
@@ -1281,7 +1283,10 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp,
snprintf(currtemp_s, sizeof(currtemp_s), "value_json.currtemp");
snprintf(currtemp_cond, sizeof(currtemp_cond), "%s is defined", currtemp_s);
}
snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_HC1 + hc_num - 1).c_str());
snprintf(topic_t,
sizeof(topic_t),
"~/%s",
Mqtt::tag_to_topic(tag < DeviceValueTAG::TAG_HS1 ? EMSdevice::DeviceType::THERMOSTAT : EMSdevice::DeviceType::CONNECT, tag).c_str());
}
snprintf(mode_str_tpl,
@@ -1297,28 +1302,28 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp,
hc_mode_s,
Helpers::translated_word(FL_(off)));
snprintf(name_s, sizeof(name_s), "Hc%d", hc_num);
snprintf(name_s, sizeof(name_s), "%s%d", tag < DeviceValueTAG::TAG_HS1 ? "Hc" : "Hs", hc_num);
if (Mqtt::entity_format() == entityFormat::MULTI_SHORT) {
snprintf(uniq_id_s, sizeof(uniq_id_s), "%s_thermostat_hc%d", Mqtt::basename().c_str(), hc_num); // add basename
snprintf(uniq_id_s, sizeof(uniq_id_s), "%s_%s%s%d", Mqtt::basename().c_str(), devicename, tagname, hc_num); // add basename
} else {
snprintf(uniq_id_s, sizeof(uniq_id_s), "thermostat_hc%d", hc_num); // backward compatible with v3.4
snprintf(uniq_id_s, sizeof(uniq_id_s), "%s%d", devicename, hc_num); // backward compatible with v3.4
}
snprintf(temp_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/seltemp", hc_num);
snprintf(mode_cmd_s, sizeof(mode_cmd_s), "~/thermostat/hc%d/mode", hc_num);
snprintf(temp_cmd_s, sizeof(temp_cmd_s), "~/%s/%s%d/seltemp", devicename, tagname, hc_num);
snprintf(mode_cmd_s, sizeof(mode_cmd_s), "~/%s/%s%d/mode", devicename, tagname, hc_num);
JsonDocument doc;
doc["~"] = Mqtt::base();
doc["uniq_id"] = uniq_id_s;
doc["obj_id"] = uniq_id_s; // same as uniq_id
doc["name"] = name_s;
doc["mode_stat_t"] = topic_t;
doc["mode_stat_tpl"] = mode_str_tpl;
doc["temp_cmd_t"] = temp_cmd_s;
doc["temp_stat_t"] = topic_t;
doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}";
doc["~"] = Mqtt::base();
doc["uniq_id"] = uniq_id_s;
doc["default_entity_id"] = uniq_id_s; // same as uniq_id
doc["name"] = name_s;
doc["mode_stat_t"] = topic_t;
doc["mode_stat_tpl"] = mode_str_tpl;
doc["temp_cmd_t"] = temp_cmd_s;
doc["temp_stat_t"] = topic_t;
doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}";
if (has_roomtemp) {
doc["curr_temp_t"] = topic_t;
@@ -1341,7 +1346,7 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp,
modes.add("heat");
modes.add("off");
add_ha_dev_section(doc.as<JsonObject>(), "thermostat", nullptr, nullptr, nullptr, false); // add dev section
add_ha_dev_section(doc.as<JsonObject>(), devicename, nullptr, nullptr, nullptr, false); // add dev section
add_ha_avail_section(doc.as<JsonObject>(), topic_t, false, seltemp_cond, has_roomtemp ? currtemp_cond : nullptr, hc_mode_cond); // add availability section
return queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag

View File

@@ -518,7 +518,7 @@ std::string calculate(const std::string & expr) {
#ifndef EMSESP_STANDALONE
stack.push_back(to_string(rhd * esp_random() / UINT32_MAX));
#else
stack.push_back(to_string(rhd * random()));
stack.push_back(to_string(rhd * rand() / RAND_MAX));
#endif
break;
}

View File

@@ -66,6 +66,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0xE9, "UBAMonitorWWPlus", false, MAKE_PF_CB(process_UBAMonitorWWPlus));
register_telegram_type(0xEA, "UBAParameterWWPlus", true, MAKE_PF_CB(process_UBAParameterWWPlus));
register_telegram_type(0x28, "WeatherComp", true, MAKE_PF_CB(process_WeatherComp));
register_telegram_type(0x2E0, "UBASetPoints", false, MAKE_PF_CB(process_UBASetPoints2));
}
if (isHeatPump()) {
@@ -1208,8 +1209,13 @@ void Boiler::check_active() {
static uint32_t lastSendHeatingOff = 0;
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && (uuid::get_uptime_sec() - lastSendHeatingOff) >= 60) {
lastSendHeatingOff = uuid::get_uptime_sec();
uint8_t data[] = {0, 0, 0, 0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
if (has_telegram_id(0xE4)) {
uint8_t data[] = {1, 0, 0, 1, 1};
write_command(EMS_TYPE_UBASetPoints2, 0, data, sizeof(data), 0);
} else {
uint8_t data[] = {0, 0, 0, 0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
}
}
// calculate energy for boiler 0x08 from stored modulation an time in units of 0.01 Wh
@@ -1832,20 +1838,34 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
// UBASetPoint 0x1A
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
uint8_t setFlowTemp_ = 0;
uint8_t setBurnPow_ = 0;
uint8_t wwSetBurnPow_ = 0;
uint8_t setFlowTemp_ = 0;
uint8_t setBurnPow_ = 0;
uint8_t setPumpMod_ = 0;
telegram->read_value(setFlowTemp_, 0);
telegram->read_value(setBurnPow_, 1);
telegram->read_value(wwSetBurnPow_, 2);
telegram->read_value(setPumpMod_, 2);
// overwrite other settings on receive?
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0x08 && (setFlowTemp_ + setBurnPow_ + wwSetBurnPow_) != 0) {
// forceHeatingOff overwrite to zero
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0x08 && (setFlowTemp_ + setBurnPow_ + setPumpMod_) != 0) {
uint8_t data[] = {0, 0, 0, 0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
}
}
// UBASetPoints ems+ 0x2E0
void Boiler::process_UBASetPoints2(std::shared_ptr<const Telegram> telegram) {
uint8_t setFlowTemp_ = 0;
uint8_t setBurnPow_ = 0;
telegram->read_value(setFlowTemp_, 0);
telegram->read_value(setBurnPow_, 1);
// forceHeatingOff overwrite to zero
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0x08 && (setFlowTemp_ + setBurnPow_) != 0) {
uint8_t data[] = {1, 0, 0, 1, 1};
write_command(EMS_TYPE_UBASetPoints2, 0, data, sizeof(data), 0);
}
}
// 0x35 - not yet implemented, not readable, only for settings
void Boiler::process_UBAFlags(std::shared_ptr<const Telegram> telegram) {
}
@@ -2419,12 +2439,17 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) {
// no verify if value is unchanged, put it to end of tx-queue, no priority
// see https://github.com/emsesp/EMS-ESP32/issues/654, https://github.com/emsesp/EMS-ESP32/issues/954
if (v == selFlowTemp_) {
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, (uint8_t *)&v, 1, 0, false);
uint8_t v1 = v;
if (has_telegram_id(0xE4)) {
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints2, 1, &v1, 1, 0, false);
} else {
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, &v1, 1, 0, false);
}
return true;
}
if (has_telegram_id(0xE4)) {
write_command(EMS_TYPE_UBASetPoints, 0, v, 0xE4);
write_command(EMS_TYPE_UBASetPoints2, 1, v, 0xE4);
} else {
write_command(EMS_TYPE_UBASetPoints, 0, v, 0x18);
}
@@ -2438,8 +2463,11 @@ bool Boiler::set_burn_power(const char * value, const int8_t id) {
return false;
}
write_command(EMS_TYPE_UBASetPoints, 1, v, EMS_TYPE_UBASetPoints);
if (has_telegram_id(0xE4)) {
write_command(EMS_TYPE_UBASetPoints2, 2, v);
} else {
write_command(EMS_TYPE_UBASetPoints, 1, v);
}
return true;
}
@@ -3415,14 +3443,20 @@ bool Boiler::set_wwAltOpPrio(const char * value, const int8_t id) {
bool Boiler::set_forceHeatingOff(const char * value, const int8_t id) {
bool v;
if (Helpers::value2bool(value, v)) {
has_update(forceHeatingOff_, v);
if (!v && Helpers::hasValue(heatingTemp_)) {
uint8_t data[] = {heatingTemp_,
(Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100),
(Helpers::hasValue(pumpModMax_) ? pumpModMax_ : (uint8_t)0),
0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
// set only on change on->off
if (!v && forceHeatingOff_ && Helpers::hasValue(heatingTemp_)) {
if (has_telegram_id(0xE4)) {
uint8_t data[] = {1, heatingTemp_, (Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100), 1, 1};
write_command(EMS_TYPE_UBASetPoints2, 0, data, sizeof(data), 0);
} else {
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;

View File

@@ -354,6 +354,7 @@ class Boiler : public EMSdevice {
void process_UBAParameterWWPlus(std::shared_ptr<const Telegram> telegram);
void process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_UBASetPoints(std::shared_ptr<const Telegram> telegram);
void process_UBASetPoints2(std::shared_ptr<const Telegram> telegram);
void process_UBAFlags(std::shared_ptr<const Telegram> telegram);
void process_MC110Status(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);

View File

@@ -32,6 +32,18 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(outdoorTemp),
DeviceValueUOM::DEGREES);
// Roomthermostats
for (uint8_t i = 0; i < 16; i++) {
register_telegram_type(0x0BDD + i, "Room", false, MAKE_PF_CB(process_roomThermostat)); // broadcasted
register_telegram_type(0x0B3D + i, "Roomname", false, MAKE_PF_CB(process_roomThermostatName)); // fetch for active circuits
register_telegram_type(0x0BB5 + i, "Roomsettings", false, MAKE_PF_CB(process_roomThermostatMode)); // fetch for active circuits
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(0xDB65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule));
// 0x2040, broadcast 36 bytes:
// data: 0E 60 00 DF 0D AF 0A 46 0A 46 02 9A 1C 53 1C 53 12 AD 12 AD 00 00 13 C2
// data: 1F 37 1F 37 00 00 00 00 18 97 11 27 (offset 24)
}
}
/*
@@ -46,6 +58,151 @@ void Connect::process_OutdoorTemp(std::shared_ptr<const Telegram> telegram) {
(0x0880), data: 01 04
(0x0889), data: 00 80 80 01
*/
void Connect::register_device_values_room(std::shared_ptr<Connect::RoomCircuit> room) {
auto tag = DeviceValueTAG::TAG_HS1 + room->room();
register_device_value(tag, &room->temp_, DeviceValueType::INT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(roomTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &room->humidity_, DeviceValueType::INT8, FL_(airHumidity), DeviceValueUOM::PERCENT);
register_device_value(tag, &room->dewtemp_, DeviceValueType::INT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(dewTemperature), DeviceValueUOM::DEGREES);
register_device_value(
tag, &room->seltemp_, DeviceValueType::UINT8, DeviceValueNumOp::DV_NUMOP_DIV2, FL_(seltemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_seltemp));
register_device_value(tag, &room->mode_, DeviceValueType::ENUM, FL_(enum_mode8), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &room->name_, DeviceValueType::STRING, FL_(name), DeviceValueUOM::NONE, MAKE_CF_CB(set_name));
}
std::shared_ptr<Connect::RoomCircuit> Connect::room_circuit(const uint8_t num, const bool create) {
// check for existing circuit
for (const auto & room_circuit : room_circuits_) {
if (room_circuit->room() == num) {
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);
// register the device values
register_device_values_room(new_room);
toggle_fetch(0x0B3D + num, true); // name
toggle_fetch(0x0BB5 + num, true); // mode
toggle_fetch(0x1230 + num, true); // unknown
return new_room; // return back point to new HC object
}
// gateway(0x50) B all(0x00), ?(0x0BDD), data: 00 E6 36 2A
void Connect::process_roomThermostat(std::shared_ptr<const Telegram> telegram) {
bool create = telegram->offset == 0 && telegram->message_data[0] < 0x80;
auto rc = room_circuit(telegram->type_id - 0xBDD, create);
if (rc == nullptr) {
return;
}
has_update(telegram, rc->temp_, 0);
has_update(telegram, rc->humidity_, 2); // could show -3 if not set
has_update(telegram, rc->seltemp_, 3);
// calculate dew temperature
const float k2 = 17.62;
const float k3 = 243.12;
const float t = (float)rc->temp_ / 10;
const float h = (float)rc->humidity_ / 100;
int16_t dt = (10 * k3 * (((k2 * t) / (k3 + t)) + log(h)) / (((k2 * k3) / (k3 + t)) - log(h)));
has_update(rc->dewtemp_, dt);
}
// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 01
// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 00 4B 00 FC 00 63 00 68 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (offset 1)
void Connect::process_roomThermostatName(std::shared_ptr<const Telegram> telegram) {
auto rc = room_circuit(telegram->type_id - 0xB3D);
if (rc == nullptr) {
return;
}
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];
}
}
rc->name_[50] = '\0'; // make sure name is terminated
}
// settings 0-mode, 1-tempautotemp, 3 - manualtemp, 6,7 - ?
void Connect::process_roomThermostatMode(std::shared_ptr<const Telegram> telegram) {
auto rc = room_circuit(telegram->type_id - 0xBB5);
if (rc == nullptr) {
return;
}
// has_enumupdate(telegram, rc->mode_, 0, {3, 1, 0});
has_update(telegram, rc->mode_, 0);
}
// unknown telegrams, needs fetch
void Connect::process_roomThermostatParam(std::shared_ptr<const Telegram> telegram) {
auto rc = room_circuit(telegram->type_id - 0x1230);
if (rc == nullptr) {
return;
}
}
// unknown broadcasted telegrams
void Connect::process_roomThermostatData(std::shared_ptr<const Telegram> telegram) {
auto rc = room_circuit(telegram->type_id - 0x1244);
if (rc == nullptr) {
return;
}
}
// Settings:
bool Connect::set_mode(const char * value, const int8_t id) {
auto rc = room_circuit(id - DeviceValueTAG::TAG_HS1);
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_mode8))) {
write_command(0xBB5 + rc->room(), 0, v); // no validate, mode change is broadcasted
return true;
}
return false;
}
bool Connect::set_seltemp(const char * value, const int8_t id) {
auto rc = room_circuit(id - DeviceValueTAG::TAG_HS1);
if (rc == nullptr) {
return false;
}
float v;
if (Helpers::value2float(value, v)) {
// write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
write_command(0xBB5 + rc->room(), rc->mode_ == 0 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
return true;
}
return false;
}
bool Connect::set_name(const char * value, const int8_t id) {
auto rc = room_circuit(id - DeviceValueTAG::TAG_HS1);
if (rc == nullptr || value == nullptr || strlen(value) > 50) {
return false;
}
uint8_t len = strlen(value) * 2 + 2;
uint8_t data[len];
for (uint8_t i = 0; i < strlen(value) + 1; i++) { // include terminating '\0'
data[2 * i] = 0;
data[2 * i + 1] = value[i];
}
uint8_t ofs = 0;
while (len > 0) {
uint8_t part = len > 25 ? 25 : len;
write_command(0x0B3D + rc->room(), ofs + 1, &data[ofs], part, 0);
ofs += part;
len -= part;
}
return true;
}
} // namespace emsesp

View File

@@ -27,7 +27,42 @@ class Connect : public EMSdevice {
public:
Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
class RoomCircuit {
public:
RoomCircuit(const uint8_t num)
: room_(num) {
}
~RoomCircuit() = default;
int16_t temp_;
int8_t humidity_;
uint8_t seltemp_;
uint8_t mode_;
char name_[51];
int16_t dewtemp_;
uint8_t room() {
return room_;
}
private:
uint8_t room_; // dhw circuit number 0..10
};
private:
std::shared_ptr<Connect::RoomCircuit> room_circuit(const uint8_t num, const bool create = false);
void register_device_values_room(std::shared_ptr<Connect::RoomCircuit> room);
void process_roomThermostat(std::shared_ptr<const Telegram> telegram);
void process_roomThermostatName(std::shared_ptr<const Telegram> telegram);
void process_roomThermostatMode(std::shared_ptr<const Telegram> telegram);
void process_roomThermostatParam(std::shared_ptr<const Telegram> telegram);
void process_roomThermostatData(std::shared_ptr<const Telegram> telegram);
bool set_mode(const char * value, const int8_t id);
bool set_seltemp(const char * value, const int8_t id);
bool set_name(const char * value, const int8_t id);
std::vector<std::shared_ptr<Connect::RoomCircuit>> room_circuits_;
void process_OutdoorTemp(std::shared_ptr<const Telegram> telegram);
int16_t outdoorTemp_;
};

View File

@@ -2006,12 +2006,12 @@ bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
auto t = (int8_t)(ct * 10);
LOG_DEBUG("Calibrating internal temperature to %d.%d C", t / 10, t < 0 ? -t % 10 : t % 10);
if (model() == EMSdevice::EMS_DEVICE_FLAG_RC10) {
if (device_id() >= 0x38 && device_id() <= 0x3F) { // remote thermostats RC100H, CR10, ...
write_command(0x273 + device_id() - 0x38, 0, t, 0x273 + device_id() - 0x38);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC10) {
write_command(0xB0, 0, t, 0xB0);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC30) {
write_command(EMS_TYPE_RC30Settings, 1, t, EMS_TYPE_RC30Settings);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100H) {
write_command(0x273, 0, t, 0x273);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
write_command(0x241, 7, t, 0x241);
} else if (isRC300()) {

View File

@@ -30,7 +30,7 @@ Ventilation::Ventilation(uint8_t device_type, uint8_t device_id, uint8_t product
register_telegram_type(0x583, "VentilationMonitor", false, MAKE_PF_CB(process_MonitorMessage));
register_telegram_type(0x5D9, "Airquality", false, MAKE_PF_CB(process_VOCMessage));
register_telegram_type(0x587, "Bypass", false, MAKE_PF_CB(process_BypassMessage));
// register_telegram_type(0x5, "VentilationSet", true, MAKE_PF_CB(process_SetMessage));
register_telegram_type(0x55C, "VentilationSet", true, MAKE_PF_CB(process_SetMessage));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&outFresh_,
@@ -50,8 +50,9 @@ Ventilation::Ventilation(uint8_t device_type, uint8_t device_id, uint8_t product
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &bypass_, DeviceValueType::BOOL, FL_(airbypass), DeviceValueUOM::NONE, MAKE_CF_CB(set_bypass));
}
// message
// message 0x055C, data: 08 01 11 17
void Ventilation::process_SetMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, bypass_, 1);
}
// message 583
@@ -85,9 +86,10 @@ void Ventilation::process_ModeMessage(std::shared_ptr<const Telegram> telegram)
has_enumupdate(telegram, mode_, 0, -1);
}
// message 0x0587, data: 01 00
// message 0x0587, data: 00 00 64 00 64 0A 00 01 54 01 00 01 00 00 00 46 00 00 00 02 00 A3 00 A3
void Ventilation::process_BypassMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, bypass_, 1);
// has_update(telegram, bypass_closing, 0);
// has_update(telegram, bypass_opening, 1);
}
bool Ventilation::set_ventMode(const char * value, const int8_t id) {

View File

@@ -41,7 +41,7 @@ class Ventilation : public EMSdevice {
uint8_t ventOutSpeed_;
// handlers: 0x056B 0x0575 0x0583 0x0585 0x0586 0x0587 0x0588 0x058D 0x058E 0x058F 0x0590 0x05CF 0x05D9 0x05E3
void process_SetMessage(std::shared_ptr<const Telegram> telegram);
void process_SetMessage(std::shared_ptr<const Telegram> telegram); // 0x55C
void process_MonitorMessage(std::shared_ptr<const Telegram> telegram);
void process_ModeMessage(std::shared_ptr<const Telegram> telegram); // 0x56B
void process_BlowerMessage(std::shared_ptr<const Telegram> telegram); // 0x56B

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.3-dev.18"
#define EMSESP_APP_VERSION "3.7.3-dev.19"

View File

@@ -436,7 +436,8 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
obj["v"] = Helpers::transformNumFloat(sensor.value());
} else
#endif
if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT || sensor.type() == AnalogSensor::AnalogType::DIGITAL_IN) {
if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT || sensor.type() == AnalogSensor::AnalogType::DIGITAL_IN
|| sensor.type() == AnalogSensor::AnalogType::PULSE) {
char s[12];
dv["v"] = Helpers::render_boolean(s, sensor.value() != 0, true);
JsonArray l = dv["l"].to<JsonArray>();
@@ -448,7 +449,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
}
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::RGB || sensor.type() == AnalogSensor::AnalogType::PULSE) {
dv["c"] = sensor.name();
}
}