Merge pull request #197 from proddy/dev

fixes #196 (HA missing entities), add row click to devices, change max limit on integer types to avoid NaN, refactored generation of device value JSON objects
This commit is contained in:
Proddy
2021-11-15 17:45:52 +01:00
committed by GitHub
36 changed files with 495 additions and 284 deletions

View File

@@ -4,37 +4,38 @@
## Added ## Added
- Add system commands for syslog level and watch [#98](https://github.com/emsesp/EMS-ESP32/issues/98) - Add system commands for syslog level and watch [#98]
- Added pool data to telegrams 0x494 & 0x495 #102 [#102](https://github.com/emsesp/EMS-ESP32/issues/102) (@Sunbuzz) - Added pool data to telegrams 0x494 & 0x495 [#102]
- Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108) - Add RC300 second summermode telegram [#108]
- Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/106) - Add support for the RC25 thermostat [#106]
- Add new command 'entities' for a device, e.g. http://ems-esp/api/boiler/entities to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116) - Add new command 'entities' for a device, e.g. http://ems-esp/api/boiler/entities to show the shortname, description and HA Entity name (if HA enabled) [#116]
- Support for Junkers program and remote (fb10/fb110) temperature - Support for Junkers program and remote (fb10/fb110) temperature
- Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129](https://github.com/emsesp/EMS-ESP32/issues/129) - Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129]
- Add current room influence for RC300[#136] - Add current room influence for RC300 [#136]
- Added Home Assistant device_class to sensor entities - Added Home Assistant device_class to sensor entities
- Added another Buderus RC10 thermostat with Product ID 65 [#160](https://github.com/emsesp/EMS-ESP32/issues/160) - Added another Buderus RC10 thermostat with Product ID 65 [#160]
- Added support for mDNS [#161](https://github.com/emsesp/EMS-ESP32/issues/161) - Added support for mDNS [#161]
- Added last system ESP32 reset code to log (and `system info` output) - Added last system ESP32 reset code to log (and `system info` output)
- Firmware Checker in WebUI [#168](https://github.com/emsesp/EMS-ESP32/issues/168) - Firmware Checker in WebUI [#168]
- Added new MQTT setting for enabling 'response' topic - Added new MQTT setting for enabling 'response' topic
- Support for non-standard Thermostats like Tado [#174](https://github.com/emsesp/EMS-ESP32/issues/174) - Support for non-standard Thermostats like Tado [#174]
- Include MQTT connection status in 'api/system/info' - Include MQTT connection status in 'api/system/info'
## Fixed ## Fixed
- MQTT reconnecting after WiFi reconnect [#99](https://github.com/emsesp/EMS-ESP32/issues/99) - MQTT reconnecting after WiFi reconnect [#99]
- Manually Controlling Solar Circuit [#107](https://github.com/emsesp/EMS-ESP32/issues/107) - Manually Controlling Solar Circuit [#107]
- Fix thermostat commands not defaulting to the master thermostat [#110](https://github.com/emsesp/EMS-ESP32/issues/110) - Fix thermostat commands not defaulting to the master thermostat [#110]
- Enlarge parse-buffer for long names like `cylinderpumpmodulation` - Enlarge parse-buffer for long names like `cylinderpumpmodulation`
- MQTT not subscribing to all device entities [#166](https://github.com/emsesp/EMS-ESP32/issues/166) - MQTT not subscribing to all device entities [#166]
- Help fix issues with WebUI unable to fully load UI over Ethernet [#177](https://github.com/emsesp/EMS-ESP32/issues/177) - Help fix issues with WebUI unable to fully load UI over Ethernet [#177]
- Shower alert never reset after limit reached when enabled [(PR #185)](https://github.com/emsesp/EMS-ESP32/pull/185) - Shower alert never reset after limit reached when enabled [(PR #185)]
- Remove HA entity entries when a device value goes dormant [#196]
## Changed ## Changed
- Syslog BOM only for utf-8 messages [#91](https://github.com/emsesp/EMS-ESP32/issues/91) - Syslog BOM only for utf-8 messages [#91]
- Check for KM200 by device-id 0x48, remove tx-delay [#90](https://github.com/emsesp/EMS-ESP32/issues/90) - Check for KM200 by device-id 0x48, remove tx-delay [#90]
- rename `fastheatupfactor` to `fastheatup` and add percent [#122] - rename `fastheatupfactor` to `fastheatup` and add percent [#122]
- "unit" renamed to "uom" in API call to recall a Device Value - "unit" renamed to "uom" in API call to recall a Device Value
- initial backend React changes to replace the class components (HOCs) with React Hooks - initial backend React changes to replace the class components (HOCs) with React Hooks
@@ -42,8 +43,8 @@
- Boiler's maintenancemessage always published in MQTT (to prevent HA missing entity) - Boiler's maintenancemessage always published in MQTT (to prevent HA missing entity)
- Unit of Measure 'times' added to MQTT Fails, Rx fails, Rx received, Tx fails, Tx reads & Tx writes - Unit of Measure 'times' added to MQTT Fails, Rx fails, Rx received, Tx fails, Tx reads & Tx writes
- Improved API. Restful HTTP API works in the same way as MQTT calls - Improved API. Restful HTTP API works in the same way as MQTT calls
- Removed settings for MQTT subscribe format [#173](https://github.com/emsesp/EMS-ESP32/issues/173) - Removed settings for MQTT subscribe format [#173]
- Improve moduline 200 functionality [#183](https://github.com/emsesp/EMS-ESP32/issues/183) - Improve moduline 200 functionality [#183]
## **BREAKING CHANGES** ## **BREAKING CHANGES**

View File

@@ -219,7 +219,9 @@ class EMSESPDataForm extends Component<
}; };
sendCommand = (dv: DeviceValue) => { sendCommand = (dv: DeviceValue) => {
if (dv.c && this.props.authenticatedContext.me.admin) {
this.setState({ edit_devicevalue: dv }); this.setState({ edit_devicevalue: dv });
}
}; };
handleSensorChange = (name: keyof Sensor) => ( handleSensorChange = (name: keyof Sensor) => (
@@ -285,7 +287,9 @@ class EMSESPDataForm extends Component<
}; };
sendSensor = (sn: Sensor) => { sendSensor = (sn: Sensor) => {
if (this.props.authenticatedContext.me.admin) {
this.setState({ edit_Sensor: sn }); this.setState({ edit_Sensor: sn });
}
}; };
noDevices = () => { noDevices = () => {
@@ -391,7 +395,11 @@ class EMSESPDataForm extends Component<
</TableHead> </TableHead>
<TableBody> <TableBody>
{data.sensors.map((sensorData) => ( {data.sensors.map((sensorData) => (
<TableRow key={sensorData.n} hover> <TableRow
key={sensorData.n}
hover
onClick={() => this.sendSensor(sensorData)}
>
<TableCell padding="checkbox" style={{ width: 18 }}> <TableCell padding="checkbox" style={{ width: 18 }}>
{me.admin && ( {me.admin && (
<CustomTooltip title="edit" placement="left-end"> <CustomTooltip title="edit" placement="left-end">
@@ -553,7 +561,6 @@ class EMSESPDataForm extends Component<
renderDeviceData() { renderDeviceData() {
const { deviceData } = this.state; const { deviceData } = this.state;
const { width } = this.props; const { width } = this.props;
const me = this.props.authenticatedContext.me;
if (this.noDevices()) { if (this.noDevices()) {
return; return;
@@ -580,9 +587,13 @@ class EMSESPDataForm extends Component<
<TableHead></TableHead> <TableHead></TableHead>
<TableBody> <TableBody>
{deviceData.data.map((item, i) => ( {deviceData.data.map((item, i) => (
<TableRow hover key={i}> <TableRow
hover
key={i}
onClick={() => this.sendCommand(item)}
>
<TableCell padding="checkbox" style={{ width: 18 }}> <TableCell padding="checkbox" style={{ width: 18 }}>
{item.c && me.admin && ( {item.c && this.props.authenticatedContext.me.admin && (
<CustomTooltip <CustomTooltip
title="change value" title="change value"
placement="left-end" placement="left-end"

View File

@@ -15,7 +15,7 @@ import {
} from '@material-ui/core'; } from '@material-ui/core';
import { FormButton } from '../components'; import { FormButton } from '../components';
import { DeviceValue, DeviceValueUOM_s } from './EMSESPtypes'; import { DeviceValue, DeviceValueUOM, DeviceValueUOM_s } from './EMSESPtypes';
interface ValueFormProps { interface ValueFormProps {
devicevalue: DeviceValue; devicevalue: DeviceValue;
@@ -26,6 +26,16 @@ interface ValueFormProps {
) => (event: React.ChangeEvent<HTMLInputElement>) => void; ) => (event: React.ChangeEvent<HTMLInputElement>) => void;
} }
function formatValue(value: any, uom: number) {
if (uom === DeviceValueUOM.DEGREES) {
return new Intl.NumberFormat(undefined, {
minimumFractionDigits: 1
}).format(value);
}
return value;
}
class ValueForm extends React.Component<ValueFormProps> { class ValueForm extends React.Component<ValueFormProps> {
formRef: RefObject<any> = React.createRef(); formRef: RefObject<any> = React.createRef();
@@ -69,7 +79,7 @@ class ValueForm extends React.Component<ValueFormProps> {
{!devicevalue.l && ( {!devicevalue.l && (
<OutlinedInput <OutlinedInput
id="value" id="value"
value={devicevalue.v} value={formatValue(devicevalue.v, devicevalue.u)}
autoFocus autoFocus
fullWidth fullWidth
onChange={handleValueChange('v')} onChange={handleValueChange('v')}

View File

@@ -296,7 +296,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
// add a command to the list, which does not return json // add a command to the list, which does not return json
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_function_p cb, const __FlashStringHelper * description, uint8_t flags) { void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_function_p cb, const __FlashStringHelper * description, uint8_t flags) {
// if the command already exists for that device type don't add it // if the command already exists for that device type don't add it
if (find_command(device_type, uuid::read_flash_string(cmd).c_str()) != nullptr) { if (find_command(device_type, read_flash_string(cmd).c_str()) != nullptr) {
return; return;
} }
@@ -312,7 +312,7 @@ void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, co
// flag is fixed to MqttSubFlag::MQTT_SUB_FLAG_NOSUB so there will be no topic subscribed to this // flag is fixed to MqttSubFlag::MQTT_SUB_FLAG_NOSUB so there will be no topic subscribed to this
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_json_function_p cb, const __FlashStringHelper * description, uint8_t flags) { void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_json_function_p cb, const __FlashStringHelper * description, uint8_t flags) {
// if the command already exists for that device type don't add it // if the command already exists for that device type don't add it
if (find_command(device_type, uuid::read_flash_string(cmd).c_str()) != nullptr) { if (find_command(device_type, read_flash_string(cmd).c_str()) != nullptr) {
return; return;
} }
@@ -334,7 +334,7 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ch
} }
for (auto & cf : cmdfunctions_) { for (auto & cf : cmdfunctions_) {
if (!strcmp(lowerCmd, Helpers::toLower(uuid::read_flash_string(cf.cmd_)).c_str()) && (cf.device_type_ == device_type)) { if (!strcmp(lowerCmd, Helpers::toLower(read_flash_string(cf.cmd_)).c_str()) && (cf.device_type_ == device_type)) {
return &cf; return &cf;
} }
} }
@@ -353,14 +353,14 @@ bool Command::list(const uint8_t device_type, JsonObject & output) {
std::list<std::string> sorted_cmds; std::list<std::string> sorted_cmds;
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) {
sorted_cmds.push_back(uuid::read_flash_string(cf.cmd_)); sorted_cmds.push_back(read_flash_string(cf.cmd_));
} }
} }
sorted_cmds.sort(); sorted_cmds.sort();
for (auto & cl : sorted_cmds) { for (auto & cl : sorted_cmds) {
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == read_flash_string(cf.cmd_))) {
output[cl] = cf.description_; output[cl] = cf.description_;
} }
} }
@@ -380,7 +380,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
std::list<std::string> sorted_cmds; std::list<std::string> sorted_cmds;
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) {
sorted_cmds.push_back(uuid::read_flash_string(cf.cmd_)); sorted_cmds.push_back(read_flash_string(cf.cmd_));
} }
} }
sorted_cmds.sort(); sorted_cmds.sort();
@@ -400,7 +400,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
for (auto & cl : sorted_cmds) { for (auto & cl : sorted_cmds) {
// find and print the description // find and print the description
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == read_flash_string(cf.cmd_))) {
uint8_t i = cl.length(); uint8_t i = cl.length();
shell.print(" "); shell.print(" ");
if (cf.has_flags(MQTT_SUB_FLAG_HC)) { if (cf.has_flags(MQTT_SUB_FLAG_HC)) {
@@ -420,7 +420,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
shell.print(EMSdevice::tag_to_string(TAG_DEVICE_DATA_WW)); shell.print(EMSdevice::tag_to_string(TAG_DEVICE_DATA_WW));
shell.print(' '); shell.print(' ');
} }
shell.print(uuid::read_flash_string(cf.description_)); shell.print(read_flash_string(cf.description_));
if (!cf.has_flags(CommandFlag::ADMIN_ONLY)) { if (!cf.has_flags(CommandFlag::ADMIN_ONLY)) {
shell.print(' '); shell.print(' ');
shell.print(COLOR_BRIGHT_RED); shell.print(COLOR_BRIGHT_RED);

View File

@@ -235,7 +235,7 @@ void EMSESPShell::add_console_commands() {
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id); shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
char buffer[4]; char buffer[4];
shell.printfln(F_(master_thermostat_fmt), shell.printfln(F_(master_thermostat_fmt),
settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str() settings.master_thermostat == 0 ? read_flash_string(F_(auto)).c_str()
: Helpers::hextoa(buffer, settings.master_thermostat)); : Helpers::hextoa(buffer, settings.master_thermostat));
shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str()); shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str());
}); });
@@ -278,8 +278,7 @@ void EMSESPShell::add_console_commands() {
settings.master_thermostat = value; settings.master_thermostat = value;
EMSESP::actual_master_thermostat(value); // set the internal value too EMSESP::actual_master_thermostat(value); // set the internal value too
char buffer[5]; char buffer[5];
shell.printfln(F_(master_thermostat_fmt), shell.printfln(F_(master_thermostat_fmt), !value ? read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
!value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, },
"local"); "local");
@@ -446,7 +445,7 @@ void EMSESPShell::add_console_commands() {
if (Command::device_has_commands(device_type)) { if (Command::device_has_commands(device_type)) {
for (const auto & cf : Command::commands()) { for (const auto & cf : Command::commands()) {
if (cf.device_type_ == device_type) { if (cf.device_type_ == device_type) {
command_list.emplace_back(uuid::read_flash_string(cf.cmd_)); command_list.emplace_back(read_flash_string(cf.cmd_));
} }
} }
return command_list; return command_list;
@@ -823,14 +822,14 @@ std::string EMSESPShell::prompt_suffix() {
} }
void EMSESPShell::end_of_transmission() { void EMSESPShell::end_of_transmission() {
invoke_command(uuid::read_flash_string(F_(exit))); invoke_command(read_flash_string(F_(exit)));
} }
EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, bool local) EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, bool local)
: uuid::console::Shell(commands, ShellContext::MAIN, local ? (CommandFlags::USER | CommandFlags::LOCAL) : CommandFlags::USER) : uuid::console::Shell(commands, ShellContext::MAIN, local ? (CommandFlags::USER | CommandFlags::LOCAL) : CommandFlags::USER)
, uuid::console::StreamConsole(stream) , uuid::console::StreamConsole(stream)
, EMSESPShell() , EMSESPShell()
, name_(uuid::read_flash_string(F("Serial"))) , name_(read_flash_string(F("Serial")))
, pty_(SIZE_MAX) , pty_(SIZE_MAX)
, addr_() , addr_()
, port_(0) { , port_(0) {

View File

@@ -267,7 +267,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
} }
// publish HA config // publish HA config
bool Boiler::publish_ha_config() { bool Boiler::publish_ha_device_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(boiler); doc["uniq_id"] = F_(boiler);
doc["ic"] = F_(icondevice); doc["ic"] = F_(icondevice);
@@ -1407,7 +1407,7 @@ bool Boiler::set_reset(const char * value, const int8_t id) {
bool Boiler::set_maintenance(const char * value, const int8_t id) { bool Boiler::set_maintenance(const char * value, const int8_t id) {
std::string s(12, '\0'); std::string s(12, '\0');
if (Helpers::value2string(value, s)) { if (Helpers::value2string(value, s)) {
if (s == Helpers::toLower(uuid::read_flash_string(F_(reset)))) { if (s == Helpers::toLower(read_flash_string(F_(reset)))) {
LOG_INFO(F("Reset boiler maintenance message")); LOG_INFO(F("Reset boiler maintenance message"));
write_command(0x05, 0x08, 0xFF, 0x1C); write_command(0x05, 0x08, 0xFF, 0x1C);
return true; return true;

View File

@@ -27,7 +27,7 @@ class Boiler : public EMSdevice {
public: public:
Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -29,7 +29,7 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
} }
// publish HA config // publish HA config
bool Connect::publish_ha_config() { bool Connect::publish_ha_device_config() {
return true; return true;
} }

View File

@@ -27,7 +27,7 @@ class Connect : public EMSdevice {
public: public:
Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -29,7 +29,7 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
// publish HA config // publish HA config
bool Controller::publish_ha_config() { bool Controller::publish_ha_device_config() {
return true; return true;
} }

View File

@@ -27,7 +27,7 @@ class Controller : public EMSdevice {
public: public:
Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -29,7 +29,7 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
} }
// publish HA config // publish HA config
bool Gateway::publish_ha_config() { bool Gateway::publish_ha_device_config() {
return true; return true;
} }

View File

@@ -27,7 +27,7 @@ class Gateway : public EMSdevice {
public: public:
Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -34,7 +34,7 @@ Generic::Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
} }
// publish HA config // publish HA config
bool Generic::publish_ha_config() { bool Generic::publish_ha_device_config() {
return true; return true;
} }

View File

@@ -27,7 +27,7 @@ class Generic : public EMSdevice {
public: public:
Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -39,7 +39,7 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
} }
// publish HA config // publish HA config
bool Heatpump::publish_ha_config() { bool Heatpump::publish_ha_device_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(heatpump); doc["uniq_id"] = F_(heatpump);
doc["ic"] = F_(icondevice); doc["ic"] = F_(icondevice);

View File

@@ -27,7 +27,7 @@ class Heatpump : public EMSdevice {
public: public:
Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -100,7 +100,7 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
} }
// publish HA config // publish HA config
bool Mixer::publish_ha_config() { bool Mixer::publish_ha_device_config() {
// if we don't have valid values for this HC don't add it ever again // if we don't have valid values for this HC don't add it ever again
if (!Helpers::hasValue(pumpStatus_)) { if (!Helpers::hasValue(pumpStatus_)) {
return false; return false;

View File

@@ -27,7 +27,7 @@ class Mixer : public EMSdevice {
public: public:
Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -180,7 +180,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
} }
// publish HA config // publish HA config
bool Solar::publish_ha_config() { bool Solar::publish_ha_device_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(solar); doc["uniq_id"] = F_(solar);
doc["ic"] = F_(icondevice); doc["ic"] = F_(icondevice);

View File

@@ -27,7 +27,7 @@ class Solar : public EMSdevice {
public: public:
Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -40,7 +40,7 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
} }
// publish HA config // publish HA config
bool Switch::publish_ha_config() { bool Switch::publish_ha_device_config() {
// if we don't have valid values don't add it ever again // if we don't have valid values don't add it ever again
if (!Helpers::hasValue(flowTempHc_)) { if (!Helpers::hasValue(flowTempHc_)) {
return false; return false;

View File

@@ -27,7 +27,7 @@ class Switch : public EMSdevice {
public: public:
Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -192,7 +192,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
// publish HA config // publish HA config
bool Thermostat::publish_ha_config() { bool Thermostat::publish_ha_device_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(thermostat); doc["uniq_id"] = F_(thermostat);
doc["ic"] = F_(icondevice); doc["ic"] = F_(icondevice);
@@ -437,7 +437,7 @@ void Thermostat::publish_ha_config_hc(std::shared_ptr<Thermostat::HeatingCircuit
// the HA climate component only responds to auto, heat and off // the HA climate component only responds to auto, heat and off
JsonArray modes = doc.createNestedArray("modes"); JsonArray modes = doc.createNestedArray("modes");
if (hc->get_model() != EMSdevice::EMS_DEVICE_FLAG_RC10){ if (hc->get_model() != EMSdevice::EMS_DEVICE_FLAG_RC10) {
modes.add("auto"); modes.add("auto");
} }
modes.add("heat"); modes.add("heat");
@@ -1798,26 +1798,26 @@ bool Thermostat::set_mode(const char * value, const int8_t id) {
uint8_t num = value[0] - '0'; uint8_t num = value[0] - '0';
switch (model()) { switch (model()) {
case EMSdevice::EMS_DEVICE_FLAG_RC10: case EMSdevice::EMS_DEVICE_FLAG_RC10:
mode = uuid::read_flash_string(FL_(enum_mode6)[num]); mode = read_flash_string(FL_(enum_mode6)[num]);
break; break;
case EMSdevice::EMS_DEVICE_FLAG_RC20: case EMSdevice::EMS_DEVICE_FLAG_RC20:
case EMSdevice::EMS_DEVICE_FLAG_RC20_N: case EMSdevice::EMS_DEVICE_FLAG_RC20_N:
mode = uuid::read_flash_string(FL_(enum_mode2)[num]); mode = read_flash_string(FL_(enum_mode2)[num]);
break; break;
case EMSdevice::EMS_DEVICE_FLAG_RC30: case EMSdevice::EMS_DEVICE_FLAG_RC30:
case EMSdevice::EMS_DEVICE_FLAG_RC35: case EMSdevice::EMS_DEVICE_FLAG_RC35:
case EMSdevice::EMS_DEVICE_FLAG_RC30_N: case EMSdevice::EMS_DEVICE_FLAG_RC30_N:
mode = uuid::read_flash_string(FL_(enum_mode3)[num]); mode = read_flash_string(FL_(enum_mode3)[num]);
break; break;
case EMSdevice::EMS_DEVICE_FLAG_RC300: case EMSdevice::EMS_DEVICE_FLAG_RC300:
case EMSdevice::EMS_DEVICE_FLAG_RC100: case EMSdevice::EMS_DEVICE_FLAG_RC100:
mode = uuid::read_flash_string(FL_(enum_mode)[num]); mode = read_flash_string(FL_(enum_mode)[num]);
break; break;
case EMSdevice::EMS_DEVICE_FLAG_JUNKERS: case EMSdevice::EMS_DEVICE_FLAG_JUNKERS:
mode = uuid::read_flash_string(FL_(enum_mode4)[num]); mode = read_flash_string(FL_(enum_mode4)[num]);
break; break;
case EMSdevice::EMS_DEVICE_FLAG_CRF: case EMSdevice::EMS_DEVICE_FLAG_CRF:
mode = uuid::read_flash_string(FL_(enum_mode5)[num]); mode = read_flash_string(FL_(enum_mode5)[num]);
break; break;
default: default:
LOG_WARNING(F("Set mode: Invalid mode")); LOG_WARNING(F("Set mode: Invalid mode"));
@@ -2673,13 +2673,8 @@ void Thermostat::register_device_values() {
FL_(ibaCalIntTemperature), FL_(ibaCalIntTemperature),
DeviceValueUOM::DEGREES, DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_calinttemp)); MAKE_CF_CB(set_calinttemp));
register_device_value(TAG_THERMOSTAT_DATA, register_device_value(
&heatingpid_, TAG_THERMOSTAT_DATA, &heatingpid_, DeviceValueType::ENUM, FL_(enum_PID), FL_(heatingPID), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingpid));
DeviceValueType::ENUM,
FL_(enum_PID),
FL_(heatingPID),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_heatingpid));
register_device_value(TAG_THERMOSTAT_DATA, &backlight_, DeviceValueType::BOOL, nullptr, FL_(backlight), DeviceValueUOM::NONE, MAKE_CF_CB(set_backlight)); register_device_value(TAG_THERMOSTAT_DATA, &backlight_, DeviceValueType::BOOL, nullptr, FL_(backlight), DeviceValueUOM::NONE, MAKE_CF_CB(set_backlight));
register_device_value(TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode3), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); register_device_value(TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode3), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
break; break;

View File

@@ -127,7 +127,7 @@ class Thermostat : public EMSdevice {
static std::string mode_tostring(uint8_t mode); static std::string mode_tostring(uint8_t mode);
virtual bool publish_ha_config(); virtual bool publish_ha_device_config();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -117,18 +117,18 @@ static const __FlashStringHelper * const DeviceValueTAG_mqtt[] PROGMEM = {
}; };
const std::string EMSdevice::tag_to_string(uint8_t tag) { const std::string EMSdevice::tag_to_string(uint8_t tag) {
return uuid::read_flash_string(DeviceValueTAG_s[tag]); return read_flash_string(DeviceValueTAG_s[tag]);
} }
const std::string EMSdevice::tag_to_mqtt(uint8_t tag) { const std::string EMSdevice::tag_to_mqtt(uint8_t tag) {
return uuid::read_flash_string(DeviceValueTAG_mqtt[tag]); return read_flash_string(DeviceValueTAG_mqtt[tag]);
} }
const std::string EMSdevice::uom_to_string(uint8_t uom) { const std::string EMSdevice::uom_to_string(uint8_t uom) {
if (uom == DeviceValueUOM::NONE) { if (uom == DeviceValueUOM::NONE) {
return std::string{}; return std::string{};
} }
return uuid::read_flash_string(DeviceValueUOM_s[uom - 1]); // offset by 1 to account for NONE return read_flash_string(DeviceValueUOM_s[uom - 1]); // offset by 1 to account for NONE
} }
const std::vector<EMSdevice::DeviceValue> EMSdevice::devicevalues() const { const std::vector<EMSdevice::DeviceValue> EMSdevice::devicevalues() const {
@@ -374,13 +374,13 @@ bool EMSdevice::is_fetch(uint16_t telegram_id) {
// list of registered device entries, adding the HA entity if it exists // list of registered device entries, adding the HA entity if it exists
void EMSdevice::list_device_entries(JsonObject & output) { void EMSdevice::list_device_entries(JsonObject & output) {
for (const auto & dv : devicevalues_) { for (const auto & dv : devicevalues_) {
if (dv_is_visible(dv) && dv.type != DeviceValueType::CMD) { if (dv.has_state(DeviceValueState::DV_VISIBLE) && dv.type != DeviceValueType::CMD) {
// if we have a tag prefix it // if we have a tag prefix it
char key[50]; char key[50];
if (!EMSdevice::tag_to_string(dv.tag).empty()) { if (!EMSdevice::tag_to_string(dv.tag).empty()) {
snprintf(key, 50, "%s.%s", EMSdevice::tag_to_string(dv.tag).c_str(), uuid::read_flash_string(dv.short_name).c_str()); snprintf(key, 50, "%s.%s", EMSdevice::tag_to_string(dv.tag).c_str(), read_flash_string(dv.short_name).c_str());
} else { } else {
snprintf(key, 50, "%s", uuid::read_flash_string(dv.short_name).c_str()); snprintf(key, 50, "%s", read_flash_string(dv.short_name).c_str());
} }
JsonArray details = output.createNestedArray(key); JsonArray details = output.createNestedArray(key);
@@ -405,14 +405,14 @@ void EMSdevice::list_device_entries(JsonObject & output) {
const std::string EMSdevice::device_entity_ha(DeviceValue const & dv) { const std::string EMSdevice::device_entity_ha(DeviceValue const & dv) {
std::string entity_name(50, '\0'); std::string entity_name(50, '\0');
if (EMSdevice::tag_to_string(dv.tag).empty()) { if (EMSdevice::tag_to_string(dv.tag).empty()) {
snprintf(&entity_name[0], entity_name.capacity() + 1, "sensor.%s %s", this->device_type_name().c_str(), uuid::read_flash_string(dv.full_name).c_str()); snprintf(&entity_name[0], entity_name.capacity() + 1, "sensor.%s %s", this->device_type_name().c_str(), read_flash_string(dv.full_name).c_str());
} else { } else {
snprintf(&entity_name[0], snprintf(&entity_name[0],
entity_name.capacity() + 1, entity_name.capacity() + 1,
"sensor.%s %s %s", "sensor.%s %s %s",
this->device_type_name().c_str(), this->device_type_name().c_str(),
EMSdevice::tag_to_string(dv.tag).c_str(), EMSdevice::tag_to_string(dv.tag).c_str(),
uuid::read_flash_string(dv.full_name).c_str()); read_flash_string(dv.full_name).c_str());
} }
std::replace(entity_name.begin(), entity_name.end(), ' ', '_'); std::replace(entity_name.begin(), entity_name.end(), ' ', '_');
return Helpers::toLower(entity_name); return Helpers::toLower(entity_name);
@@ -512,7 +512,11 @@ void EMSdevice::register_device_value(uint8_t tag,
}; };
} }
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max); // set state
// if fullname is empty don't set the flag to visible (used for hamode and hatemp)
uint8_t state = (full_name) ? DeviceValueState::DV_VISIBLE : DeviceValueState::DV_DEFAULT;
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max, state);
} }
// function with min and max values // function with min and max values
@@ -581,7 +585,7 @@ const std::string EMSdevice::get_value_uom(const char * key) {
size_t sz = sizeof(DeviceValueTAG_s) / sizeof(__FlashStringHelper *); size_t sz = sizeof(DeviceValueTAG_s) / sizeof(__FlashStringHelper *);
for (uint8_t i = 0; i < sz; i++) { for (uint8_t i = 0; i < sz; i++) {
auto tag = uuid::read_flash_string(DeviceValueTAG_s[i]); auto tag = read_flash_string(DeviceValueTAG_s[i]);
if (!tag.empty()) { if (!tag.empty()) {
std::string key2 = key; // copy char to a std::string std::string key2 = key; // copy char to a std::string
if ((key2.find(tag) != std::string::npos) && (key[tag.length()] == ' ')) { if ((key2.find(tag) != std::string::npos) && (key[tag.length()] == ' ')) {
@@ -593,8 +597,8 @@ const std::string EMSdevice::get_value_uom(const char * key) {
// look up key in our device value list // look up key in our device value list
for (const auto & dv : devicevalues_) { for (const auto & dv : devicevalues_) {
if (dv_is_visible(dv)) { if (dv.has_state(DeviceValueState::DV_VISIBLE)) {
if (uuid::read_flash_string(dv.full_name) == key_p) { if (read_flash_string(dv.full_name) == key_p) {
// ignore TIME since "minutes" is already added to the string value // ignore TIME since "minutes" is already added to the string value
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) { if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break; break;
@@ -608,51 +612,50 @@ const std::string EMSdevice::get_value_uom(const char * key) {
} }
// prepare array of device values used for the WebUI // prepare array of device values used for the WebUI
// this is loosely based of the function generate_values_json used for the MQTT and Console
// except additional data is stored in the JSON document needed for the Web UI like the UOM and command
// v = value, u=uom, n=name, c=cmd // v = value, u=uom, n=name, c=cmd
void EMSdevice::generate_values_json_web(JsonObject & output) { void EMSdevice::generate_values_json_web(JsonObject & output) {
output["name"] = to_string_short(); output["name"] = to_string_short();
JsonArray data = output.createNestedArray("data"); JsonArray data = output.createNestedArray("data");
for (const auto & dv : devicevalues_) { for (const auto & dv : devicevalues_) {
// check conditions:
// 1. full_name cannot be empty
// 2. it can't be a command (like publish)
// 3. it must have a valid value
// ignore if full_name empty and also commands // ignore if full_name empty and also commands
if (dv_is_visible(dv) && dv.type != DeviceValueType::CMD) { if (dv.has_state(DeviceValueState::DV_VISIBLE) && (dv.type != DeviceValueType::CMD) && check_dv_hasvalue(dv)) {
JsonObject obj; // create the object, if needed JsonObject obj; // create the object, if needed
// handle Booleans (true, false) // handle Booleans (true, false)
if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { if (dv.type == DeviceValueType::BOOL) {
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = *(bool *)(dv.value_p) ? "on" : "off"; obj["v"] = *(bool *)(dv.value_p) ? "on" : "off";
} }
// handle TEXT strings // handle TEXT strings
else if ((dv.type == DeviceValueType::STRING) && (Helpers::hasValue((char *)(dv.value_p)))) { else if (dv.type == DeviceValueType::STRING) {
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = (char *)(dv.value_p); obj["v"] = (char *)(dv.value_p);
} }
// handle ENUMs // handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) { else if ((dv.type == DeviceValueType::ENUM) && (*(uint8_t *)(dv.value_p) < dv.options_size)) {
if (*(uint8_t *)(dv.value_p) < dv.options_size) {
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = dv.options[*(uint8_t *)(dv.value_p)]; obj["v"] = dv.options[*(uint8_t *)(dv.value_p)];
} }
}
// // handle commands without value
// else if (dv.type == DeviceValueType::CMD) {
// obj = data.createNestedObject();
// obj["v"] = "";
// }
else {
// handle Integers and Floats // handle Integers and Floats
else {
// If a divider is specified, do the division to 2 decimals places and send back as double/float // If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole // otherwise force as an integer whole
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler // the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
uint8_t divider = 0; uint8_t divider = 0;
uint8_t factor = 1; uint8_t factor = 1;
if (dv.options_size == 1) { if (dv.options_size == 1) {
const char * s = uuid::read_flash_string(dv.options[0]).c_str(); const char * s = read_flash_string(dv.options[0]).c_str();
if (s[0] == '*') { if (s[0] == '*') {
factor = Helpers::atoint(&s[1]); factor = Helpers::atoint(&s[1]);
} else { } else {
@@ -660,22 +663,22 @@ void EMSdevice::generate_values_json_web(JsonObject & output) {
} }
} }
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) { if (dv.type == DeviceValueType::INT) {
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(int8_t *)(dv.value_p), divider) : *(int8_t *)(dv.value_p) * factor; obj["v"] = (divider) ? Helpers::round2(*(int8_t *)(dv.value_p), divider) : *(int8_t *)(dv.value_p) * factor;
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) { } else if (dv.type == DeviceValueType::UINT) {
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(uint8_t *)(dv.value_p), divider) : *(uint8_t *)(dv.value_p) * factor; obj["v"] = (divider) ? Helpers::round2(*(uint8_t *)(dv.value_p), divider) : *(uint8_t *)(dv.value_p) * factor;
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) { } else if (dv.type == DeviceValueType::SHORT) {
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(int16_t *)(dv.value_p), divider) : *(int16_t *)(dv.value_p) * factor; obj["v"] = (divider) ? Helpers::round2(*(int16_t *)(dv.value_p), divider) : *(int16_t *)(dv.value_p) * factor;
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) { } else if (dv.type == DeviceValueType::USHORT) {
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(uint16_t *)(dv.value_p), divider) : *(uint16_t *)(dv.value_p) * factor; obj["v"] = (divider) ? Helpers::round2(*(uint16_t *)(dv.value_p), divider) : *(uint16_t *)(dv.value_p) * factor;
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { } else if (dv.type == DeviceValueType::ULONG) {
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = divider ? Helpers::round2(*(uint32_t *)(dv.value_p), divider) : *(uint32_t *)(dv.value_p) * factor; obj["v"] = divider ? Helpers::round2(*(uint32_t *)(dv.value_p), divider) : *(uint32_t *)(dv.value_p) * factor;
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { } else if (dv.type == DeviceValueType::TIME) {
uint32_t time_value = *(uint32_t *)(dv.value_p); uint32_t time_value = *(uint32_t *)(dv.value_p);
obj = data.createNestedObject(); obj = data.createNestedObject();
obj["v"] = (divider > 0) ? time_value / divider : time_value * factor; // sometimes we need to divide by 60 obj["v"] = (divider > 0) ? time_value / divider : time_value * factor; // sometimes we need to divide by 60
@@ -684,15 +687,14 @@ void EMSdevice::generate_values_json_web(JsonObject & output) {
// check if we've added a data element then add the remaining elements // check if we've added a data element then add the remaining elements
if (obj.containsKey("v")) { if (obj.containsKey("v")) {
// add the unit of measure (uom) obj["u"] = dv.uom; // add the unit of measure (uom)
obj["u"] = dv.uom;
// add name, prefixing the tag if it exists // add name, prefixing the tag if it exists
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) { if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["n"] = dv.full_name; obj["n"] = dv.full_name;
} else { } else {
char name[50]; char name[50];
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), uuid::read_flash_string(dv.full_name).c_str()); snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(dv.full_name).c_str());
obj["n"] = name; obj["n"] = name;
} }
@@ -700,7 +702,7 @@ void EMSdevice::generate_values_json_web(JsonObject & output) {
if (dv.has_cmd) { if (dv.has_cmd) {
// add the name of the Command function // add the name of the Command function
if (dv.tag >= DeviceValueTAG::TAG_HC1) { if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["c"] = tag_to_string(dv.tag) + "/" + uuid::read_flash_string(dv.short_name); obj["c"] = tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
} else { } else {
obj["c"] = dv.short_name; obj["c"] = dv.short_name;
} }
@@ -708,8 +710,8 @@ void EMSdevice::generate_values_json_web(JsonObject & output) {
if (dv.type == DeviceValueType::ENUM) { if (dv.type == DeviceValueType::ENUM) {
JsonArray l = obj.createNestedArray("l"); JsonArray l = obj.createNestedArray("l");
for (uint8_t i = 0; i < dv.options_size; i++) { for (uint8_t i = 0; i < dv.options_size; i++) {
if (!uuid::read_flash_string(dv.options[i]).empty()) { if (!read_flash_string(dv.options[i]).empty()) {
l.add(uuid::read_flash_string(dv.options[i])); l.add(read_flash_string(dv.options[i]));
} }
} }
} }
@@ -724,7 +726,7 @@ void EMSdevice::generate_values_json_web(JsonObject & output) {
} }
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
// serializeJson(data, Serial); // debug only // serializeJson(data, Serial); // debug only
#endif #endif
} }
@@ -746,11 +748,12 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
// search device value with this tag // search device value with this tag
for (auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
if (dv_is_visible(dv) && (strcmp(cmd, Helpers::toLower(uuid::read_flash_string(dv.short_name)).c_str()) == 0 && (tag <= 0 || tag == dv.tag))) { if (dv.has_state(DeviceValueState::DV_VISIBLE)
&& (strcmp(cmd, Helpers::toLower(read_flash_string(dv.short_name)).c_str()) == 0 && (tag <= 0 || tag == dv.tag))) {
uint8_t divider = 0; uint8_t divider = 0;
uint8_t factor = 1; uint8_t factor = 1;
if (dv.options_size == 1) { if (dv.options_size == 1) {
const char * s = uuid::read_flash_string(dv.options[0]).c_str(); const char * s = read_flash_string(dv.options[0]).c_str();
if (s[0] == '*') { if (s[0] == '*') {
factor = Helpers::atoint(&s[1]); factor = Helpers::atoint(&s[1]);
} else { } else {
@@ -767,7 +770,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) { if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
json["fullname"] = dv.full_name; json["fullname"] = dv.full_name;
} else { } else {
json["fullname"] = tag_to_string(dv.tag) + " " + uuid::read_flash_string(dv.full_name); json["fullname"] = tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
} }
if (!tag_to_mqtt(dv.tag).empty()) { if (!tag_to_mqtt(dv.tag).empty()) {
@@ -784,7 +787,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
} }
} }
json[type] = F_(enum); json[type] = F_(enum);
// uint8_t min_ = (uuid::read_flash_string(dv.options[0]) == "") ? 1 : 0; // uint8_t min_ = (read_flash_string(dv.options[0]) == "") ? 1 : 0;
// json[min] = min_; // json[min] = min_;
// json[max] = dv.options_size - 1; // json[max] = dv.options_size - 1;
JsonArray enum_ = json.createNestedArray(F_(enum)); JsonArray enum_ = json.createNestedArray(F_(enum));
@@ -800,7 +803,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
} }
json[type] = F_(number); json[type] = F_(number);
json[min] = 0; json[min] = 0;
json[max] = divider ? EMS_VALUE_USHORT_NOTSET / divider : EMS_VALUE_USHORT_NOTSET; json[max] = divider ? EMS_VALUE_USHORT_NOTSET / divider : EMS_VALUE_USHORT_NOTSET - 1;
break; break;
case DeviceValueType::UINT: case DeviceValueType::UINT:
@@ -812,7 +815,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
if (dv.uom == DeviceValueUOM::PERCENT) { if (dv.uom == DeviceValueUOM::PERCENT) {
json[max] = 100; json[max] = 100;
} else { } else {
json[max] = divider ? EMS_VALUE_UINT_NOTSET / divider : EMS_VALUE_UINT_NOTSET; json[max] = divider ? EMS_VALUE_UINT_NOTSET / divider : EMS_VALUE_UINT_NOTSET - 1;
} }
break; break;
@@ -822,7 +825,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
} }
json[type] = F_(number); json[type] = F_(number);
json[min] = divider ? -EMS_VALUE_SHORT_NOTSET / divider : -EMS_VALUE_SHORT_NOTSET; json[min] = divider ? -EMS_VALUE_SHORT_NOTSET / divider : -EMS_VALUE_SHORT_NOTSET;
json[max] = divider ? EMS_VALUE_SHORT_NOTSET / divider : EMS_VALUE_SHORT_NOTSET; json[max] = divider ? EMS_VALUE_SHORT_NOTSET / divider : EMS_VALUE_SHORT_NOTSET - 1;
break; break;
case DeviceValueType::INT: case DeviceValueType::INT:
@@ -835,7 +838,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
json[max] = 100; json[max] = 100;
} else { } else {
json[min] = divider ? -EMS_VALUE_INT_NOTSET / divider : -EMS_VALUE_INT_NOTSET; json[min] = divider ? -EMS_VALUE_INT_NOTSET / divider : -EMS_VALUE_INT_NOTSET;
json[max] = divider ? EMS_VALUE_INT_NOTSET / divider : EMS_VALUE_INT_NOTSET; json[max] = divider ? EMS_VALUE_INT_NOTSET / divider : EMS_VALUE_INT_NOTSET - 1;
} }
break; break;
@@ -932,42 +935,53 @@ bool EMSdevice::generate_values_json(JsonObject & output, const uint8_t tag_filt
JsonObject json = output; JsonObject json = output;
for (auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
// conditions // check conditions:
bool condition; // 1. it must have a valid value
condition = (tag_filter == DeviceValueTAG::TAG_NONE) || (tag_filter == dv.tag); // tag must be either empty or match a tag passed to this function // 2. it must be visible, unless our output destination is MQTT
// 3. it must match the given tag filter or have an empty tag
if (output_target != OUTPUT_TARGET::MQTT) { // check if it exists. We set the value activated once here
condition &= dv_is_visible(dv); // value must be visible if outputting to API (web or console). This is for ID, hamode, hatemp etc bool has_value = check_dv_hasvalue(dv);
if (has_value) {
dv.add_state(DeviceValueState::DV_ACTIVE);
} else {
dv.remove_state(DeviceValueState::DV_ACTIVE);
} }
bool has_value = false; bool conditions = ((tag_filter == DeviceValueTAG::TAG_NONE) || (tag_filter == dv.tag)) && has_value;
if (output_target != OUTPUT_TARGET::MQTT) {
conditions &=
dv.has_state(DeviceValueState::DV_VISIBLE); // value must be visible if outputting to API (web or console). This is for ID, hamode, hatemp etc
}
if (conditions) {
has_values = true; // we actually have data
if (condition) {
// we have a tag if it matches the filter given, and that the tag name is not empty/"" // we have a tag if it matches the filter given, and that the tag name is not empty/""
bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty()); bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty());
// create the name for the JSON key
char name[80]; char name[80];
if (output_target == OUTPUT_TARGET::API_VERBOSE) { if (output_target == OUTPUT_TARGET::API_VERBOSE) {
// prefix the tag in brackets, unless it's Boiler because we're naughty and use tag for the MQTT topic
if (have_tag) { if (have_tag) {
snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), uuid::read_flash_string(dv.full_name).c_str()); snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(dv.full_name).c_str()); // prefix the tag
} else { } else {
strcpy(name, uuid::read_flash_string(dv.full_name).c_str()); // use full name strcpy(name, read_flash_string(dv.full_name).c_str()); // use full name
} }
} else { } else {
strcpy(name, uuid::read_flash_string(dv.short_name).c_str()); // use short name strcpy(name, read_flash_string(dv.short_name).c_str()); // use short name
// if we have a tag, and its different to the last one create a nested object // if we have a tag, and its different to the last one create a nested object. only for hc, wwc and hs
if (dv.tag != old_tag) { if (dv.tag != old_tag) {
old_tag = dv.tag; old_tag = dv.tag;
if (nested && have_tag && dv.tag >= DeviceValueTAG::TAG_HC1) { // no nests for boiler tags if (nested && have_tag && dv.tag >= DeviceValueTAG::TAG_HC1) {
json = output.createNestedObject(tag_to_string(dv.tag)); json = output.createNestedObject(tag_to_string(dv.tag));
} }
} }
} }
// handle Booleans (true, false) // handle Booleans (true, false)
if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { if (dv.type == DeviceValueType::BOOL) {
// see how to render the value depending on the setting // see how to render the value depending on the setting
uint8_t bool_format = EMSESP::bool_format(); uint8_t bool_format = EMSESP::bool_format();
if (bool_format == BOOL_FORMAT_ONOFF) { if (bool_format == BOOL_FORMAT_ONOFF) {
@@ -979,17 +993,15 @@ bool EMSdevice::generate_values_json(JsonObject & output, const uint8_t tag_filt
} else { } else {
json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0; json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0;
} }
has_value = true;
} }
// handle TEXT strings // handle TEXT strings
else if ((dv.type == DeviceValueType::STRING) && (Helpers::hasValue((char *)(dv.value_p)))) { else if (dv.type == DeviceValueType::STRING) {
json[name] = (char *)(dv.value_p); json[name] = (char *)(dv.value_p);
has_value = true;
} }
// handle ENUMs // handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) { else if (dv.type == DeviceValueType::ENUM) {
if (*(uint8_t *)(dv.value_p) < dv.options_size) { if (*(uint8_t *)(dv.value_p) < dv.options_size) {
// check for numeric enum-format, but "hamode" always as text // check for numeric enum-format, but "hamode" always as text
if ((EMSESP::enum_format() == ENUM_FORMAT_NUMBER) && (dv.short_name != FL_(hamode)[0])) { if ((EMSESP::enum_format() == ENUM_FORMAT_NUMBER) && (dv.short_name != FL_(hamode)[0])) {
@@ -997,19 +1009,18 @@ bool EMSdevice::generate_values_json(JsonObject & output, const uint8_t tag_filt
} else { } else {
json[name] = dv.options[*(uint8_t *)(dv.value_p)]; json[name] = dv.options[*(uint8_t *)(dv.value_p)];
} }
has_value = true;
} }
} }
// handle Integers and Floats // handle Integers and Floats
else {
// If a divider is specified, do the division to 2 decimals places and send back as double/float // If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole // otherwise force as a whole integer
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler // note: the strange nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
else {
uint8_t divider = 0; uint8_t divider = 0;
uint8_t factor = 1; uint8_t factor = 1;
if (dv.options_size == 1) { if (dv.options_size == 1) {
const char * s = uuid::read_flash_string(dv.options[0]).c_str(); const char * s = read_flash_string(dv.options[0]).c_str();
if (s[0] == '*') { if (s[0] == '*') {
factor = Helpers::atoint(&s[1]); factor = Helpers::atoint(&s[1]);
} else { } else {
@@ -1017,85 +1028,109 @@ bool EMSdevice::generate_values_json(JsonObject & output, const uint8_t tag_filt
} }
} }
// always convert temperatures to floats // always convert temperatures to floats with 1 decimal place
bool make_float = (divider || (dv.uom == DeviceValueUOM::DEGREES)); bool make_float = (divider || (dv.uom == DeviceValueUOM::DEGREES));
// INT if (dv.type == DeviceValueType::INT) {
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) {
if (make_float) { if (make_float) {
json[name] = Helpers::round2(*(int8_t *)(dv.value_p), divider); json[name] = Helpers::round2(*(int8_t *)(dv.value_p), divider);
} else { } else {
json[name] = *(int8_t *)(dv.value_p) * factor; json[name] = *(int8_t *)(dv.value_p) * factor;
} }
has_value = true; } else if (dv.type == DeviceValueType::UINT) {
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
if (make_float) { if (make_float) {
json[name] = Helpers::round2(*(uint8_t *)(dv.value_p), divider); json[name] = Helpers::round2(*(uint8_t *)(dv.value_p), divider);
} else { } else {
json[name] = *(uint8_t *)(dv.value_p) * factor; json[name] = *(uint8_t *)(dv.value_p) * factor;
} }
has_value = true; } else if (dv.type == DeviceValueType::SHORT) {
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) {
if (make_float) { if (make_float) {
json[name] = Helpers::round2(*(int16_t *)(dv.value_p), divider); json[name] = Helpers::round2(*(int16_t *)(dv.value_p), divider);
} else { } else {
json[name] = *(int16_t *)(dv.value_p) * factor; json[name] = *(int16_t *)(dv.value_p) * factor;
} }
has_value = true; } else if (dv.type == DeviceValueType::USHORT) {
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
if (make_float) { if (make_float) {
json[name] = Helpers::round2(*(uint16_t *)(dv.value_p), divider); json[name] = Helpers::round2(*(uint16_t *)(dv.value_p), divider);
} else { } else {
json[name] = *(uint16_t *)(dv.value_p) * factor; json[name] = *(uint16_t *)(dv.value_p) * factor;
} }
has_value = true; } else if (dv.type == DeviceValueType::ULONG) {
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
if (make_float) { if (make_float) {
json[name] = Helpers::round2(*(uint32_t *)(dv.value_p), divider); json[name] = Helpers::round2(*(uint32_t *)(dv.value_p), divider);
} else { } else {
json[name] = *(uint32_t *)(dv.value_p) * factor; json[name] = *(uint32_t *)(dv.value_p) * factor;
} }
has_value = true; } else if (dv.type == DeviceValueType::TIME) {
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p); uint32_t time_value = *(uint32_t *)(dv.value_p);
time_value = (divider) ? time_value / divider : time_value * factor; // sometimes we need to divide by 60 time_value = (divider) ? time_value / divider : time_value * factor; // sometimes we need to divide by 60
if (output_target == EMSdevice::OUTPUT_TARGET::API_VERBOSE) { if (output_target == EMSdevice::OUTPUT_TARGET::API_VERBOSE) {
char time_s[40]; char time_s[40];
snprintf(time_s, sizeof(time_s), "%d days %d hours %d minutes", (time_value / 1440), ((time_value % 1440) / 60), (time_value % 60)); snprintf(time_s,
sizeof(time_s),
"%d %s %d %s %d %s",
(time_value / 1440),
read_flash_string(F_(days)).c_str(),
((time_value % 1440) / 60),
read_flash_string(F_(hours)).c_str(),
(time_value % 60),
read_flash_string(F_(minutes)).c_str());
json[name] = time_s; json[name] = time_s;
} else { } else {
json[name] = time_value; json[name] = time_value;
} }
has_value = true;
} }
} }
} }
dv.ha |= has_value ? DeviceValueHA::HA_VALUE : DeviceValueHA::HA_NONE;
has_values |= has_value;
} }
return has_values; return has_values;
} }
// create the Home Assistant configs for each value // create the Home Assistant configs for each value
// this is called when an MQTT publish is done via an EMS Device // this is called when an MQTT publish is done via an EMS Device in emsesp.cpp
void EMSdevice::publish_mqtt_ha_sensor() { // if the main Device Entity config for the device hasn't been setup its also done here
for (auto & dv : devicevalues_) { void EMSdevice::publish_mqtt_ha_entity_config() {
if (dv.ha == DeviceValueHA::HA_VALUE) { // create the main device config if not doing already
Mqtt::publish_ha_sensor(dv.type, dv.tag, dv.full_name, dv.device_type, dv.short_name, dv.uom, dv.has_cmd); if (!ha_config_done()) {
dv.ha |= DeviceValueHA::HA_DONE; bool ok = publish_ha_device_config();
} ha_config_done(ok); // see if it worked
} }
if (!ha_config_done()) { for (auto & dv : devicevalues_) {
bool ok = publish_ha_config(); #if defined(EMSESP_STANDALONE)
ha_config_done(ok); // see if it worked // debug messages to go with the test called 'dv'
if (strcmp(read_flash_string(dv.short_name).c_str(), "wwseltemp") == 0) {
EMSESP::logger().warning(F("! init: wwseltemp state=%d, active=%d config_created=%d"),
dv.get_state(),
dv.has_state(DV_ACTIVE),
dv.has_state(DV_HA_CONFIG_CREATED));
}
#endif
// if the HA config has already been created and now the value has gone dormant, delete the config
// https://github.com/emsesp/EMS-ESP32/issues/196
if (dv.has_state(DV_ACTIVE)) {
if (!dv.has_state(DV_HA_CONFIG_CREATED)) {
// add it
Mqtt::publish_ha_sensor_config(dv.type, dv.tag, dv.full_name, dv.device_type, dv.short_name, dv.uom, false, dv.has_cmd);
dv.add_state(DV_HA_CONFIG_CREATED);
}
} else {
if (dv.has_state(DV_HA_CONFIG_CREATED)) {
// remove it
Mqtt::publish_ha_sensor_config(dv.type, dv.tag, dv.full_name, dv.device_type, dv.short_name, dv.uom, true, dv.has_cmd);
dv.remove_state(DV_HA_CONFIG_CREATED);
}
}
} }
} }
// remove all config topics in HA
void EMSdevice::ha_config_clear() { void EMSdevice::ha_config_clear() {
for (auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
dv.ha = DeviceValueHA::HA_NONE; // also wait for new value Mqtt::publish_ha_sensor_config(dv.type, dv.tag, dv.full_name, dv.device_type, dv.short_name, dv.uom, true, dv.has_cmd); // delete topic
dv.remove_state(DV_HA_CONFIG_CREATED);
} }
ha_config_done(false); ha_config_done(false);
} }
@@ -1111,7 +1146,7 @@ const std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram>
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if ((tf.telegram_type_id_ == telegram->type_id) && (telegram->type_id != 0xFF)) { if ((tf.telegram_type_id_ == telegram->type_id) && (telegram->type_id != 0xFF)) {
return uuid::read_flash_string(tf.telegram_type_name_); return read_flash_string(tf.telegram_type_name_);
} }
} }
@@ -1126,7 +1161,7 @@ bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) {
// if the data block is empty, assume that this telegram is not recognized by the bus master // if the data block is empty, assume that this telegram is not recognized by the bus master
// so remove it from the automatic fetch list // so remove it from the automatic fetch list
if (telegram->message_length == 0 && telegram->offset == 0) { if (telegram->message_length == 0 && telegram->offset == 0) {
EMSESP::logger().debug(F("This telegram (%s) is not recognized by the EMS bus"), uuid::read_flash_string(tf.telegram_type_name_).c_str()); EMSESP::logger().debug(F("This telegram (%s) is not recognized by the EMS bus"), read_flash_string(tf.telegram_type_name_).c_str());
toggle_fetch(tf.telegram_type_id_, false); toggle_fetch(tf.telegram_type_id_, false);
return false; return false;
} }
@@ -1161,4 +1196,51 @@ void EMSdevice::read_command(const uint16_t type_id, const uint8_t offset, const
EMSESP::send_read_request(type_id, device_id(), offset, length); EMSESP::send_read_request(type_id, device_id(), offset, length);
} }
// checks whether the device value has an actual value
// returns true if its valid
// state is stored in the dv object
bool EMSdevice::check_dv_hasvalue(const DeviceValue & dv) {
bool has_value = false;
switch (dv.type) {
case DeviceValueType::BOOL:
has_value = Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL);
break;
case DeviceValueType::STRING:
has_value = Helpers::hasValue((char *)(dv.value_p));
break;
case DeviceValueType::ENUM:
has_value = Helpers::hasValue(*(uint8_t *)(dv.value_p));
break;
case DeviceValueType::INT:
has_value = Helpers::hasValue(*(int8_t *)(dv.value_p));
break;
case DeviceValueType::UINT:
has_value = Helpers::hasValue(*(uint8_t *)(dv.value_p));
break;
case DeviceValueType::SHORT:
has_value = Helpers::hasValue(*(int16_t *)(dv.value_p));
break;
case DeviceValueType::USHORT:
has_value = Helpers::hasValue(*(uint16_t *)(dv.value_p));
break;
case DeviceValueType::ULONG:
has_value = Helpers::hasValue(*(uint32_t *)(dv.value_p));
break;
case DeviceValueType::TIME:
has_value = Helpers::hasValue(*(uint32_t *)(dv.value_p));
break;
default:
break;
}
#if defined(EMSESP_DEBUG)
// https://github.com/emsesp/EMS-ESP32/issues/196
if (dv.has_state(DeviceValueState::DV_ACTIVE) && !has_value) {
EMSESP::logger().warning(F("[DEBUG] Lost device value %s"), dv.short_name);
}
#endif
return has_value;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -119,12 +119,14 @@ enum DeviceValueTAG : uint8_t {
}; };
// MQTT HA flags // states of a device value
enum DeviceValueHA : uint8_t { enum DeviceValueState : uint8_t {
DV_DEFAULT = 0, // 0 - does not yet have a value
DV_ACTIVE = (1 << 0), // 1 - has a value
DV_VISIBLE = (1 << 1), // 2 - shown on web and console
DV_HA_CONFIG_CREATED = (1 << 2) // 4 - set if the HA config has been created
HA_NONE = 0,
HA_VALUE,
HA_DONE
}; };
class EMSdevice { class EMSdevice {
@@ -134,7 +136,7 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20; static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20;
// virtual functions overrules by derived classes // virtual functions overrules by derived classes
virtual bool publish_ha_config() = 0; virtual bool publish_ha_device_config() = 0;
// device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc.. // device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc..
EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
@@ -291,7 +293,7 @@ class EMSdevice {
void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0); void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0);
void publish_mqtt_ha_sensor(); void publish_mqtt_ha_entity_config();
const std::string telegram_type_name(std::shared_ptr<const Telegram> telegram); const std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);
@@ -415,7 +417,6 @@ class EMSdevice {
} }
}; };
// DeviceValue holds all the attributes for a device value (also a device parameter) // DeviceValue holds all the attributes for a device value (also a device parameter)
struct DeviceValue { struct DeviceValue {
uint8_t device_type; // EMSdevice::DeviceType uint8_t device_type; // EMSdevice::DeviceType
@@ -431,6 +432,7 @@ class EMSdevice {
bool has_cmd; // true if there is a Console/MQTT command which matches the short_name bool has_cmd; // true if there is a Console/MQTT command which matches the short_name
int32_t min; int32_t min;
uint32_t max; uint32_t max;
uint8_t state; // DeviceValueState::*
DeviceValue(uint8_t device_type, DeviceValue(uint8_t device_type,
uint8_t tag, uint8_t tag,
@@ -444,7 +446,8 @@ class EMSdevice {
uint8_t ha, uint8_t ha,
bool has_cmd, bool has_cmd,
int32_t min, int32_t min,
uint32_t max) uint32_t max,
uint8_t state)
: device_type(device_type) : device_type(device_type)
, tag(tag) , tag(tag)
, value_p(value_p) , value_p(value_p)
@@ -457,7 +460,22 @@ class EMSdevice {
, ha(ha) , ha(ha)
, has_cmd(has_cmd) , has_cmd(has_cmd)
, min(min) , min(min)
, max(max) { , max(max)
, state(state) {
}
// state flags
inline void add_state(uint8_t s) {
state |= s;
}
inline bool has_state(uint8_t s) const {
return (state & s) == s;
}
inline void remove_state(uint8_t s) {
state &= ~s;
}
inline uint8_t get_state() const {
return state;
} }
}; };
const std::vector<DeviceValue> devicevalues() const; const std::vector<DeviceValue> devicevalues() const;
@@ -467,13 +485,11 @@ class EMSdevice {
const std::string device_entity_ha(DeviceValue const & dv); const std::string device_entity_ha(DeviceValue const & dv);
bool check_dv_hasvalue(const DeviceValue & dv);
void init_devicevalues(uint8_t size) { void init_devicevalues(uint8_t size) {
devicevalues_.reserve(size); devicevalues_.reserve(size);
} }
inline bool dv_is_visible(DeviceValue dv) {
return (dv.full_name);
}
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -458,7 +458,7 @@ void EMSESP::reset_mqtt_ha() {
} }
// create json doc for the devices values and add to MQTT publish queue // create json doc for the devices values and add to MQTT publish queue
// generate_values_json is called without verbose mode (defaults to false) // generate_values_json is called to build the device value (dv) object array
void EMSESP::publish_device_values(uint8_t device_type) { void EMSESP::publish_device_values(uint8_t device_type) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
@@ -469,11 +469,6 @@ void EMSESP::publish_device_values(uint8_t device_type) {
// group by device type // group by device type
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) { if (emsdevice && (emsdevice->device_type() == device_type)) {
// if we're using HA, done is checked for each sensor in devices
if (Mqtt::ha_enabled()) {
emsdevice->publish_mqtt_ha_sensor(); // create the configs for each value as a sensor
}
// if its a boiler, generate json for each group and publish it directly. not nested // if its a boiler, generate json for each group and publish it directly. not nested
if (device_type == DeviceType::BOILER) { if (device_type == DeviceType::BOILER) {
if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) { if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) {
@@ -528,6 +523,11 @@ void EMSESP::publish_device_values(uint8_t device_type) {
need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
} }
} }
// if we're using HA, done is checked for each sensor in devices
if (Mqtt::ha_enabled()) {
emsdevice->publish_mqtt_ha_entity_config(); // create the configs for each value as a sensor
}
} }
// publish it under a single topic, only if we have data to publish // publish it under a single topic, only if we have data to publish
@@ -949,7 +949,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
// find the name and flags in our database // find the name and flags in our database
for (const auto & device : device_library_) { for (const auto & device : device_library_) {
if (device.product_id == product_id && device.device_type == emsdevice->device_type()) { if (device.product_id == product_id && device.device_type == emsdevice->device_type()) {
emsdevice->name(std::move(uuid::read_flash_string(device.name))); emsdevice->name(std::move(read_flash_string(device.name)));
emsdevice->add_flags(device.flags); emsdevice->add_flags(device.flags);
} }
} }
@@ -988,7 +988,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
return false; // not found return false; // not found
} }
auto name = uuid::read_flash_string(device_p->name); auto name = read_flash_string(device_p->name);
auto device_type = device_p->device_type; auto device_type = device_p->device_type;
auto flags = device_p->flags; auto flags = device_p->flags;

View File

@@ -126,9 +126,9 @@ char * Helpers::smallitoa(char * result, const uint16_t value) {
char * Helpers::render_boolean(char * result, bool value) { char * Helpers::render_boolean(char * result, bool value) {
uint8_t bool_format_ = EMSESP::bool_format(); uint8_t bool_format_ = EMSESP::bool_format();
if (bool_format_ == BOOL_FORMAT_ONOFF) { if (bool_format_ == BOOL_FORMAT_ONOFF) {
strlcpy(result, value ? uuid::read_flash_string(F_(on)).c_str() : uuid::read_flash_string(F_(off)).c_str(), 5); strlcpy(result, value ? read_flash_string(F_(on)).c_str() : read_flash_string(F_(off)).c_str(), 5);
} else if (bool_format_ == BOOL_FORMAT_ONOFF_CAP) { } else if (bool_format_ == BOOL_FORMAT_ONOFF_CAP) {
strlcpy(result, value ? uuid::read_flash_string(F_(ON)).c_str() : uuid::read_flash_string(F_(OFF)).c_str(), 5); strlcpy(result, value ? read_flash_string(F_(ON)).c_str() : read_flash_string(F_(OFF)).c_str(), 5);
} else if (bool_format_ == BOOL_FORMAT_TRUEFALSE) { } else if (bool_format_ == BOOL_FORMAT_TRUEFALSE) {
strlcpy(result, value ? "true" : "false", 7); strlcpy(result, value ? "true" : "false", 7);
} else { } else {
@@ -306,7 +306,7 @@ char * Helpers::render_value(char * result, const uint32_t value, const uint8_t
// creates string of hex values from an arrray of bytes // creates string of hex values from an arrray of bytes
std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) { std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
if (length == 0) { if (length == 0) {
return uuid::read_flash_string(F("<empty>")); return read_flash_string(F("<empty>"));
} }
std::string str(160, '\0'); std::string str(160, '\0');
@@ -464,12 +464,12 @@ bool Helpers::value2bool(const char * v, bool & value) {
std::string bool_str = toLower(v); // convert to lower case std::string bool_str = toLower(v); // convert to lower case
if ((bool_str == uuid::read_flash_string(F_(on))) || (bool_str == "1") or (bool_str == "true")) { if ((bool_str == read_flash_string(F_(on))) || (bool_str == "1") or (bool_str == "true")) {
value = true; value = true;
return true; // is a bool return true; // is a bool
} }
if ((bool_str == uuid::read_flash_string(F_(off))) || (bool_str == "0") or (bool_str == "false")) { if ((bool_str == read_flash_string(F_(off))) || (bool_str == "0") or (bool_str == "false")) {
value = false; value = false;
return true; // is a bool return true; // is a bool
} }
@@ -484,8 +484,8 @@ bool Helpers::value2enum(const char * v, uint8_t & value, const __FlashStringHel
} }
std::string str = toLower(v); std::string str = toLower(v);
for (value = 0; strs[value]; value++) { for (value = 0; strs[value]; value++) {
std::string str1 = toLower(uuid::read_flash_string(strs[value])); std::string str1 = toLower(read_flash_string(strs[value]));
if ((str1 == uuid::read_flash_string(F_(off)) && str == "false") || (str1 == uuid::read_flash_string(F_(on)) && str == "true") || (str == str1) if ((str1 == read_flash_string(F_(off)) && str == "false") || (str1 == read_flash_string(F_(on)) && str == "true") || (str == str1)
|| (v[0] == ('0' + value) && v[1] == '\0')) { || (v[0] == ('0' + value) && v[1] == '\0')) {
return true; return true;
} }

View File

@@ -187,6 +187,8 @@ MAKE_PSTR(mv, "mV")
MAKE_PSTR(times, "times") MAKE_PSTR(times, "times")
MAKE_PSTR(oclock, "o'clock") MAKE_PSTR(oclock, "o'clock")
MAKE_PSTR(days, "days")
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp // TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
// use empty string if want to suppress showing tags // use empty string if want to suppress showing tags
// tags must not have spaces // tags must not have spaces

View File

@@ -158,7 +158,7 @@ void Mqtt::loop() {
// print MQTT log and other stuff to console // print MQTT log and other stuff to console
void Mqtt::show_mqtt(uuid::console::Shell & shell) { void Mqtt::show_mqtt(uuid::console::Shell & shell) {
shell.printfln(F("MQTT is %s"), connected() ? uuid::read_flash_string(F_(connected)).c_str() : uuid::read_flash_string(F_(disconnected)).c_str()); shell.printfln(F("MQTT is %s"), connected() ? read_flash_string(F_(connected)).c_str() : read_flash_string(F_(disconnected)).c_str());
shell.printfln(F("MQTT publish errors: %lu"), mqtt_publish_fails_); shell.printfln(F("MQTT publish errors: %lu"), mqtt_publish_fails_);
shell.println(); shell.println();
@@ -583,8 +583,8 @@ void Mqtt::ha_status() {
// create the sensors - must match the MQTT payload keys // create the sensors - must match the MQTT payload keys
if (!EMSESP::system_.ethernet_connected()) { if (!EMSESP::system_.ethernet_connected()) {
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("WiFi RSSI"), EMSdevice::DeviceType::SYSTEM, F("rssi"), DeviceValueUOM::DBM); publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("WiFi RSSI"), EMSdevice::DeviceType::SYSTEM, F("rssi"), DeviceValueUOM::DBM);
publish_ha_sensor(DeviceValueType::INT, publish_ha_sensor_config(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT, DeviceValueTAG::TAG_HEARTBEAT,
F("WiFi strength"), F("WiFi strength"),
EMSdevice::DeviceType::SYSTEM, EMSdevice::DeviceType::SYSTEM,
@@ -592,15 +592,30 @@ void Mqtt::ha_status() {
DeviceValueUOM::PERCENT); DeviceValueUOM::PERCENT);
} }
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime"), DeviceValueUOM::NONE); publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime"), DeviceValueUOM::NONE);
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime (sec)"), EMSdevice::DeviceType::SYSTEM, F("uptime_sec"), DeviceValueUOM::SECONDS); publish_ha_sensor_config(DeviceValueType::INT,
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"), DeviceValueUOM::KB); DeviceValueTAG::TAG_HEARTBEAT,
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("MQTT fails"), EMSdevice::DeviceType::SYSTEM, F("mqttfails"), DeviceValueUOM::TIMES); F("Uptime (sec)"),
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Rx received"), EMSdevice::DeviceType::SYSTEM, F("rxreceived"), DeviceValueUOM::TIMES); EMSdevice::DeviceType::SYSTEM,
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Rx fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails"), DeviceValueUOM::TIMES); F("uptime_sec"),
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx reads"), EMSdevice::DeviceType::SYSTEM, F("txreads"), DeviceValueUOM::TIMES); DeviceValueUOM::SECONDS);
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx writes"), EMSdevice::DeviceType::SYSTEM, F("txwrites"), DeviceValueUOM::TIMES); publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"), DeviceValueUOM::KB);
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx fails"), EMSdevice::DeviceType::SYSTEM, F("txfails"), DeviceValueUOM::TIMES); publish_ha_sensor_config(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("MQTT fails"),
EMSdevice::DeviceType::SYSTEM,
F("mqttfails"),
DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("Rx received"),
EMSdevice::DeviceType::SYSTEM,
F("rxreceived"),
DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Rx fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails"), DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx reads"), EMSdevice::DeviceType::SYSTEM, F("txreads"), DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx writes"), EMSdevice::DeviceType::SYSTEM, F("txwrites"), DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx fails"), EMSdevice::DeviceType::SYSTEM, F("txfails"), DeviceValueUOM::TIMES);
} }
// add sub or pub task to the queue. // add sub or pub task to the queue.
@@ -612,9 +627,14 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
} }
// if it's a publish and the payload is empty, stop // if it's a publish and the payload is empty, stop
/*
if ((operation == Operation::PUBLISH) && (payload.empty())) { if ((operation == Operation::PUBLISH) && (payload.empty())) {
#ifdef EMSESP_DEBUG
LOG_WARNING("[DEBUG] Publish empty payload - quitting");
#endif
return nullptr; return nullptr;
} }
*/
// take the topic and prefix the base, unless its for HA // take the topic and prefix the base, unless its for HA
std::shared_ptr<MqttMessage> message; std::shared_ptr<MqttMessage> message;
@@ -622,7 +642,11 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
if (operation == Operation::PUBLISH) { if (operation == Operation::PUBLISH) {
if (message->payload.empty()) {
LOG_INFO("[DEBUG] Adding to queue: (Publish) topic='%s' empty payload", message->topic.c_str());
} else {
LOG_INFO("[DEBUG] Adding to queue: (Publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str()); LOG_INFO("[DEBUG] Adding to queue: (Publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str());
}
} else { } else {
LOG_INFO("[DEBUG] Adding to queue: (Subscribe) topic='%s'", message->topic.c_str()); LOG_INFO("[DEBUG] Adding to queue: (Subscribe) topic='%s'", message->topic.c_str());
} }
@@ -657,16 +681,16 @@ void Mqtt::publish(const std::string & topic, const std::string & payload) {
// MQTT Publish, using a user's retain flag - except for char * strings // MQTT Publish, using a user's retain flag - except for char * strings
void Mqtt::publish(const __FlashStringHelper * topic, const char * payload) { void Mqtt::publish(const __FlashStringHelper * topic, const char * payload) {
queue_publish_message(uuid::read_flash_string(topic), payload, mqtt_retain_); queue_publish_message(read_flash_string(topic), payload, mqtt_retain_);
} }
// MQTT Publish, using a specific retain flag, topic is a flash string // MQTT Publish, using a specific retain flag, topic is a flash string
void Mqtt::publish(const __FlashStringHelper * topic, const std::string & payload) { void Mqtt::publish(const __FlashStringHelper * topic, const std::string & payload) {
queue_publish_message(uuid::read_flash_string(topic), payload, mqtt_retain_); queue_publish_message(read_flash_string(topic), payload, mqtt_retain_);
} }
void Mqtt::publish(const __FlashStringHelper * topic, const JsonObject & payload) { void Mqtt::publish(const __FlashStringHelper * topic, const JsonObject & payload) {
publish(uuid::read_flash_string(topic), payload); publish(read_flash_string(topic), payload);
} }
// publish json doc, only if its not empty // publish json doc, only if its not empty
@@ -681,7 +705,7 @@ void Mqtt::publish(const std::string & topic) {
// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag // MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag
void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) { void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) {
queue_publish_message(uuid::read_flash_string(topic), payload, retain); queue_publish_message(read_flash_string(topic), payload, retain);
} }
// publish json doc, only if its not empty, using the retain flag // publish json doc, only if its not empty, using the retain flag
@@ -694,11 +718,25 @@ void Mqtt::publish_retain(const std::string & topic, const JsonObject & payload,
} }
void Mqtt::publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain) { void Mqtt::publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain) {
publish_retain(uuid::read_flash_string(topic), payload, retain); publish_retain(read_flash_string(topic), payload, retain);
} }
void Mqtt::publish_ha(const __FlashStringHelper * topic, const JsonObject & payload) { void Mqtt::publish_ha(const __FlashStringHelper * topic, const JsonObject & payload) {
publish_ha(uuid::read_flash_string(topic), payload); publish_ha(read_flash_string(topic), payload);
}
// publish empty payload to remove the topic
void Mqtt::publish_ha(const std::string & topic) {
if (!enabled()) {
return;
}
std::string fulltopic = read_flash_string(F_(homeassistant)) + topic;
#if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Publishing empty HA topic=%s"), fulltopic.c_str());
#endif
publish(topic); // call it immediately, don't queue it
} }
// publish a Home Assistant config topic and payload, with retain flag off. // publish a Home Assistant config topic and payload, with retain flag off.
@@ -714,7 +752,7 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) {
payload_text.reserve(measureJson(payload) + 1); payload_text.reserve(measureJson(payload) + 1);
serializeJson(payload, payload_text); // convert json to string serializeJson(payload, payload_text); // convert json to string
std::string fulltopic = uuid::read_flash_string(F_(homeassistant)) + topic; std::string fulltopic = read_flash_string(F_(homeassistant)) + topic;
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), fulltopic.c_str(), payload_text.c_str()); LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), fulltopic.c_str(), payload_text.c_str());
#elif defined(EMSESP_DEBUG) #elif defined(EMSESP_DEBUG)
@@ -737,7 +775,7 @@ void Mqtt::process_queue() {
auto message = mqtt_message.content_; auto message = mqtt_message.content_;
char topic[MQTT_TOPIC_MAX_SIZE]; char topic[MQTT_TOPIC_MAX_SIZE];
if (message->topic.find(uuid::read_flash_string(F_(homeassistant))) == 0) { if (message->topic.find(read_flash_string(F_(homeassistant))) == 0) {
strcpy(topic, message->topic.c_str()); // leave topic as it is strcpy(topic, message->topic.c_str()); // leave topic as it is
} else { } else {
snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str()); snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str());
@@ -803,46 +841,72 @@ void Mqtt::process_queue() {
mqtt_messages_.pop_front(); // remove the message from the queue mqtt_messages_.pop_front(); // remove the message from the queue
} }
void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG
const __FlashStringHelper * name,
const uint8_t device_type, // EMSdevice::DeviceType
const __FlashStringHelper * entity,
const uint8_t uom) { // EMSdevice::DeviceValueUOM (0=NONE)
publish_ha_sensor_config(type, tag, name, device_type, entity, uom, false, false);
}
// HA config for a sensor and binary_sensor entity // HA config for a sensor and binary_sensor entity
// entity must match the key/value pair in the *_data topic // entity must match the key/value pair in the *_data topic
// note: some string copying here into chars, it looks messy but does help with heap fragmentation issues // note: some string copying here into chars, it looks messy but does help with heap fragmentation issues
void Mqtt::publish_ha_sensor(uint8_t type, // EMSdevice::DeviceValueType void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG uint8_t tag, // EMSdevice::DeviceValueTAG
const __FlashStringHelper * name, const __FlashStringHelper * name,
const uint8_t device_type, // EMSdevice::DeviceType const uint8_t device_type, // EMSdevice::DeviceType
const __FlashStringHelper * entity, const __FlashStringHelper * entity,
const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE) const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE)
const bool remove, // true if we want to remove this topic
const bool has_cmd) { const bool has_cmd) {
// ignore if name (fullname) is empty // ignore if name (fullname) is empty
if (name == nullptr) { if (name == nullptr) {
return; return;
} }
// create the device name
char device_name[50];
strlcpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
// create entity by add the hc/wwc tag if present, seperating with a .
char new_entity[50];
if (tag >= DeviceValueTAG::TAG_HC1) {
snprintf(new_entity, sizeof(new_entity), "%s.%s", EMSdevice::tag_to_string(tag).c_str(), read_flash_string(entity).c_str());
} else {
snprintf(new_entity, sizeof(new_entity), "%s", read_flash_string(entity).c_str());
}
// build unique identifier which will be used in the topic, replacing all . with _ as not to break HA
std::string uniq(50, '\0');
snprintf(&uniq[0], uniq.capacity() + 1, "%s_%s", device_name, new_entity);
std::replace(uniq.begin(), uniq.end(), '.', '_');
// create the topic
char topic[MQTT_TOPIC_MAX_SIZE];
if (type == DeviceValueType::BOOL) {
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s/config", mqtt_base_.c_str(), uniq.c_str()); // binary sensor
} else {
snprintf(topic, sizeof(topic), "sensor/%s/%s/config", mqtt_base_.c_str(), uniq.c_str()); // normal HA sensor, not a boolean one
}
// if we're asking to remove this topic, send an empty payload
// https://github.com/emsesp/EMS-ESP32/issues/196
if (remove) {
LOG_WARNING(F("Device value %s gone silent. Removing HA config topic %s"), uniq.c_str(), topic);
publish_ha(topic);
return;
}
bool have_tag = !EMSdevice::tag_to_string(tag).empty(); bool have_tag = !EMSdevice::tag_to_string(tag).empty();
// nested_format is 1 if nested, otherwise 2 for single topics // nested_format is 1 if nested, otherwise 2 for single topics
bool is_nested = (nested_format_ == 1); bool is_nested = (nested_format_ == 1);
char device_name[50];
strlcpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG); DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
doc["~"] = mqtt_base_; doc["~"] = mqtt_base_;
// create entity by add the hc/wwc tag if present, seperating with a .
char new_entity[50];
if (tag >= DeviceValueTAG::TAG_HC1) {
snprintf(new_entity, sizeof(new_entity), "%s.%s", EMSdevice::tag_to_string(tag).c_str(), uuid::read_flash_string(entity).c_str());
} else {
snprintf(new_entity, sizeof(new_entity), "%s", uuid::read_flash_string(entity).c_str());
}
// build unique identifier which will be used in the topic
// and replacing all . with _ as not to break HA
std::string uniq(50, '\0');
snprintf(&uniq[0], uniq.capacity() + 1, "%s_%s", device_name, new_entity);
std::replace(uniq.begin(), uniq.end(), '.', '_');
doc["uniq_id"] = uniq; doc["uniq_id"] = uniq;
// state topic // state topic
@@ -853,9 +917,9 @@ void Mqtt::publish_ha_sensor(uint8_t type, // EMSdevice::Dev
// name = <device> <tag> <name> // name = <device> <tag> <name>
char new_name[80]; char new_name[80];
if (have_tag) { if (have_tag) {
snprintf(new_name, sizeof(new_name), "%s %s %s", device_name, EMSdevice::tag_to_string(tag).c_str(), uuid::read_flash_string(name).c_str()); snprintf(new_name, sizeof(new_name), "%s %s %s", device_name, EMSdevice::tag_to_string(tag).c_str(), read_flash_string(name).c_str());
} else { } else {
snprintf(new_name, sizeof(new_name), "%s %s", device_name, uuid::read_flash_string(name).c_str()); snprintf(new_name, sizeof(new_name), "%s %s", device_name, read_flash_string(name).c_str());
} }
new_name[0] = toupper(new_name[0]); // capitalize first letter new_name[0] = toupper(new_name[0]); // capitalize first letter
doc["name"] = new_name; doc["name"] = new_name;
@@ -866,25 +930,17 @@ void Mqtt::publish_ha_sensor(uint8_t type, // EMSdevice::Dev
if (is_nested) { if (is_nested) {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity); snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity);
} else { } else {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", uuid::read_flash_string(entity).c_str()); snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", read_flash_string(entity).c_str());
} }
doc["val_tpl"] = val_tpl; doc["val_tpl"] = val_tpl;
char topic[MQTT_TOPIC_MAX_SIZE];
// look at the device value type // look at the device value type
if (type == DeviceValueType::BOOL) { if (type == DeviceValueType::BOOL) {
// binary sensor
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s/config", mqtt_base_.c_str(), uniq.c_str()); // topic
// how to render boolean. HA only accepts String values // how to render boolean. HA only accepts String values
char result[10]; char result[10];
doc[F("payload_on")] = Helpers::render_boolean(result, true); doc[F("payload_on")] = Helpers::render_boolean(result, true);
doc[F("payload_off")] = Helpers::render_boolean(result, false); doc[F("payload_off")] = Helpers::render_boolean(result, false);
} else { } else {
// normal HA sensor, not a boolean one
snprintf(topic, sizeof(topic), "sensor/%s/%s/config", mqtt_base_.c_str(), uniq.c_str()); // topic
// set default state and device class for HA // set default state and device class for HA
auto set_state_class = State_class::NONE; auto set_state_class = State_class::NONE;
auto set_device_class = Device_class::NONE; auto set_device_class = Device_class::NONE;

View File

@@ -110,14 +110,23 @@ class Mqtt {
static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain); static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain);
static void publish_ha(const std::string & topic, const JsonObject & payload); static void publish_ha(const std::string & topic, const JsonObject & payload);
static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload); static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload);
static void publish_ha(const std::string & topic);
static void publish_ha_sensor(uint8_t type, static void publish_ha_sensor_config(uint8_t type,
uint8_t tag, uint8_t tag,
const __FlashStringHelper * name, const __FlashStringHelper * name,
const uint8_t device_type, const uint8_t device_type,
const __FlashStringHelper * entity, const __FlashStringHelper * entity,
const uint8_t uom, const uint8_t uom,
const bool has_cmd = false); const bool remove,
const bool has_cmd);
static void publish_ha_sensor_config(uint8_t type,
uint8_t tag,
const __FlashStringHelper * name,
const uint8_t device_type,
const __FlashStringHelper * entity,
const uint8_t uom);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
static void show_mqtt(uuid::console::Shell & shell); static void show_mqtt(uuid::console::Shell & shell);

View File

@@ -830,7 +830,7 @@ void System::show_system(uuid::console::Shell & shell) {
} else { } else {
shell.printfln(F("Syslog: %s"), syslog_.started() ? "started" : "stopped"); shell.printfln(F("Syslog: %s"), syslog_.started() ? "started" : "stopped");
shell.print(F(" ")); shell.print(F(" "));
shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : uuid::read_flash_string(F_(unset)).c_str()); shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : read_flash_string(F_(unset)).c_str());
shell.printfln(F(" IP: %s"), uuid::printable_to_string(syslog_.ip()).c_str()); shell.printfln(F(" IP: %s"), uuid::printable_to_string(syslog_.ip()).c_str());
shell.print(F(" ")); shell.print(F(" "));
shell.printfln(F_(port_fmt), syslog_port_); shell.printfln(F_(port_fmt), syslog_port_);

View File

@@ -471,10 +471,38 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.invoke_command("call boiler entities"); shell.invoke_command("call boiler entities");
} }
if (command == "dv") {
shell.printfln(F("Testing device value rendering"));
Mqtt::ha_enabled(true);
// Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
Mqtt::send_response(false);
run_test("boiler");
run_test("thermostat");
// shell.invoke_command("show");
// change a value to null/bogus/dormant
// homeassistant/sensor/ems-esp/boiler_wwseltemp/config
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
// Boiler -> Me, UBAParameterWW(0x33)
// wwseltemp = goes from 52 degrees (0x34) to void (0xFF)
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0xFF, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
// shell.invoke_command("show mqtt");
}
if (command == "api") { if (command == "api") {
shell.printfln(F("Testing API with MQTT and REST, standalone")); shell.printfln(F("Testing API with MQTT and REST, standalone"));
Mqtt::ha_enabled(true); Mqtt::ha_enabled(true);
// Mqtt::ha_enabled(false); // Mqtt::ha_enabled(false);

View File

@@ -39,8 +39,10 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "shower_alert" // #define EMSESP_DEBUG_DEFAULT "shower_alert"
// #define EMSESP_DEBUG_DEFAULT "310" // #define EMSESP_DEBUG_DEFAULT "310"
// #define EMSESP_DEBUG_DEFAULT "render" // #define EMSESP_DEBUG_DEFAULT "render"
#define EMSESP_DEBUG_DEFAULT "api" // #define EMSESP_DEBUG_DEFAULT "api"
// #define EMSESP_DEBUG_DEFAULT "crash" // #define EMSESP_DEBUG_DEFAULT "crash"
#define EMSESP_DEBUG_DEFAULT "dv"
class Test { class Test {
public: public:

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.3.0b6" #define EMSESP_APP_VERSION "3.3.0b7"