mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 16:29:51 +03:00
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:
@@ -4,37 +4,38 @@
|
||||
|
||||
## Added
|
||||
|
||||
- Add system commands for syslog level and watch [#98](https://github.com/emsesp/EMS-ESP32/issues/98)
|
||||
- Added pool data to telegrams 0x494 & 0x495 #102 [#102](https://github.com/emsesp/EMS-ESP32/issues/102) (@Sunbuzz)
|
||||
- Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108)
|
||||
- Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/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 system commands for syslog level and watch [#98]
|
||||
- Added pool data to telegrams 0x494 & 0x495 [#102]
|
||||
- Add RC300 second summermode telegram [#108]
|
||||
- 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]
|
||||
- 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)
|
||||
- Add current room influence for RC300[#136]
|
||||
- Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129]
|
||||
- Add current room influence for RC300 [#136]
|
||||
- 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 support for mDNS [#161](https://github.com/emsesp/EMS-ESP32/issues/161)
|
||||
- Added another Buderus RC10 thermostat with Product ID 65 [#160]
|
||||
- Added support for mDNS [#161]
|
||||
- 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
|
||||
- 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'
|
||||
|
||||
## Fixed
|
||||
|
||||
- MQTT reconnecting after WiFi reconnect [#99](https://github.com/emsesp/EMS-ESP32/issues/99)
|
||||
- Manually Controlling Solar Circuit [#107](https://github.com/emsesp/EMS-ESP32/issues/107)
|
||||
- Fix thermostat commands not defaulting to the master thermostat [#110](https://github.com/emsesp/EMS-ESP32/issues/110)
|
||||
- MQTT reconnecting after WiFi reconnect [#99]
|
||||
- Manually Controlling Solar Circuit [#107]
|
||||
- Fix thermostat commands not defaulting to the master thermostat [#110]
|
||||
- Enlarge parse-buffer for long names like `cylinderpumpmodulation`
|
||||
- MQTT not subscribing to all device entities [#166](https://github.com/emsesp/EMS-ESP32/issues/166)
|
||||
- Help fix issues with WebUI unable to fully load UI over Ethernet [#177](https://github.com/emsesp/EMS-ESP32/issues/177)
|
||||
- Shower alert never reset after limit reached when enabled [(PR #185)](https://github.com/emsesp/EMS-ESP32/pull/185)
|
||||
- MQTT not subscribing to all device entities [#166]
|
||||
- Help fix issues with WebUI unable to fully load UI over Ethernet [#177]
|
||||
- Shower alert never reset after limit reached when enabled [(PR #185)]
|
||||
- Remove HA entity entries when a device value goes dormant [#196]
|
||||
|
||||
## Changed
|
||||
|
||||
- Syslog BOM only for utf-8 messages [#91](https://github.com/emsesp/EMS-ESP32/issues/91)
|
||||
- Check for KM200 by device-id 0x48, remove tx-delay [#90](https://github.com/emsesp/EMS-ESP32/issues/90)
|
||||
- Syslog BOM only for utf-8 messages [#91]
|
||||
- Check for KM200 by device-id 0x48, remove tx-delay [#90]
|
||||
- rename `fastheatupfactor` to `fastheatup` and add percent [#122]
|
||||
- "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
|
||||
@@ -42,8 +43,8 @@
|
||||
- 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
|
||||
- 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)
|
||||
- Improve moduline 200 functionality [#183](https://github.com/emsesp/EMS-ESP32/issues/183)
|
||||
- Removed settings for MQTT subscribe format [#173]
|
||||
- Improve moduline 200 functionality [#183]
|
||||
|
||||
## **BREAKING CHANGES**
|
||||
|
||||
|
||||
@@ -219,7 +219,9 @@ class EMSESPDataForm extends Component<
|
||||
};
|
||||
|
||||
sendCommand = (dv: DeviceValue) => {
|
||||
this.setState({ edit_devicevalue: dv });
|
||||
if (dv.c && this.props.authenticatedContext.me.admin) {
|
||||
this.setState({ edit_devicevalue: dv });
|
||||
}
|
||||
};
|
||||
|
||||
handleSensorChange = (name: keyof Sensor) => (
|
||||
@@ -285,7 +287,9 @@ class EMSESPDataForm extends Component<
|
||||
};
|
||||
|
||||
sendSensor = (sn: Sensor) => {
|
||||
this.setState({ edit_Sensor: sn });
|
||||
if (this.props.authenticatedContext.me.admin) {
|
||||
this.setState({ edit_Sensor: sn });
|
||||
}
|
||||
};
|
||||
|
||||
noDevices = () => {
|
||||
@@ -391,7 +395,11 @@ class EMSESPDataForm extends Component<
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.sensors.map((sensorData) => (
|
||||
<TableRow key={sensorData.n} hover>
|
||||
<TableRow
|
||||
key={sensorData.n}
|
||||
hover
|
||||
onClick={() => this.sendSensor(sensorData)}
|
||||
>
|
||||
<TableCell padding="checkbox" style={{ width: 18 }}>
|
||||
{me.admin && (
|
||||
<CustomTooltip title="edit" placement="left-end">
|
||||
@@ -553,7 +561,6 @@ class EMSESPDataForm extends Component<
|
||||
renderDeviceData() {
|
||||
const { deviceData } = this.state;
|
||||
const { width } = this.props;
|
||||
const me = this.props.authenticatedContext.me;
|
||||
|
||||
if (this.noDevices()) {
|
||||
return;
|
||||
@@ -580,9 +587,13 @@ class EMSESPDataForm extends Component<
|
||||
<TableHead></TableHead>
|
||||
<TableBody>
|
||||
{deviceData.data.map((item, i) => (
|
||||
<TableRow hover key={i}>
|
||||
<TableRow
|
||||
hover
|
||||
key={i}
|
||||
onClick={() => this.sendCommand(item)}
|
||||
>
|
||||
<TableCell padding="checkbox" style={{ width: 18 }}>
|
||||
{item.c && me.admin && (
|
||||
{item.c && this.props.authenticatedContext.me.admin && (
|
||||
<CustomTooltip
|
||||
title="change value"
|
||||
placement="left-end"
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { FormButton } from '../components';
|
||||
import { DeviceValue, DeviceValueUOM_s } from './EMSESPtypes';
|
||||
import { DeviceValue, DeviceValueUOM, DeviceValueUOM_s } from './EMSESPtypes';
|
||||
|
||||
interface ValueFormProps {
|
||||
devicevalue: DeviceValue;
|
||||
@@ -26,6 +26,16 @@ interface ValueFormProps {
|
||||
) => (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> {
|
||||
formRef: RefObject<any> = React.createRef();
|
||||
|
||||
@@ -69,7 +79,7 @@ class ValueForm extends React.Component<ValueFormProps> {
|
||||
{!devicevalue.l && (
|
||||
<OutlinedInput
|
||||
id="value"
|
||||
value={devicevalue.v}
|
||||
value={formatValue(devicevalue.v, devicevalue.u)}
|
||||
autoFocus
|
||||
fullWidth
|
||||
onChange={handleValueChange('v')}
|
||||
|
||||
@@ -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
|
||||
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 (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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
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 (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;
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ch
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -353,14 +353,14 @@ bool Command::list(const uint8_t device_type, JsonObject & output) {
|
||||
std::list<std::string> sorted_cmds;
|
||||
for (const auto & cf : cmdfunctions_) {
|
||||
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();
|
||||
|
||||
for (auto & cl : sorted_cmds) {
|
||||
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_;
|
||||
}
|
||||
}
|
||||
@@ -380,7 +380,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
|
||||
std::list<std::string> sorted_cmds;
|
||||
for (const auto & cf : cmdfunctions_) {
|
||||
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();
|
||||
@@ -400,7 +400,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
|
||||
for (auto & cl : sorted_cmds) {
|
||||
// find and print the description
|
||||
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();
|
||||
shell.print(" ");
|
||||
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(' ');
|
||||
}
|
||||
shell.print(uuid::read_flash_string(cf.description_));
|
||||
shell.print(read_flash_string(cf.description_));
|
||||
if (!cf.has_flags(CommandFlag::ADMIN_ONLY)) {
|
||||
shell.print(' ');
|
||||
shell.print(COLOR_BRIGHT_RED);
|
||||
|
||||
@@ -235,7 +235,7 @@ void EMSESPShell::add_console_commands() {
|
||||
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
|
||||
char buffer[4];
|
||||
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));
|
||||
shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str());
|
||||
});
|
||||
@@ -278,8 +278,7 @@ void EMSESPShell::add_console_commands() {
|
||||
settings.master_thermostat = value;
|
||||
EMSESP::actual_master_thermostat(value); // set the internal value too
|
||||
char buffer[5];
|
||||
shell.printfln(F_(master_thermostat_fmt),
|
||||
!value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
|
||||
shell.printfln(F_(master_thermostat_fmt), !value ? read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
@@ -446,7 +445,7 @@ void EMSESPShell::add_console_commands() {
|
||||
if (Command::device_has_commands(device_type)) {
|
||||
for (const auto & cf : Command::commands()) {
|
||||
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;
|
||||
@@ -823,14 +822,14 @@ std::string EMSESPShell::prompt_suffix() {
|
||||
}
|
||||
|
||||
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)
|
||||
: uuid::console::Shell(commands, ShellContext::MAIN, local ? (CommandFlags::USER | CommandFlags::LOCAL) : CommandFlags::USER)
|
||||
, uuid::console::StreamConsole(stream)
|
||||
, EMSESPShell()
|
||||
, name_(uuid::read_flash_string(F("Serial")))
|
||||
, name_(read_flash_string(F("Serial")))
|
||||
, pty_(SIZE_MAX)
|
||||
, addr_()
|
||||
, port_(0) {
|
||||
|
||||
@@ -267,7 +267,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
}
|
||||
|
||||
// publish HA config
|
||||
bool Boiler::publish_ha_config() {
|
||||
bool Boiler::publish_ha_device_config() {
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
|
||||
doc["uniq_id"] = F_(boiler);
|
||||
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) {
|
||||
std::string s(12, '\0');
|
||||
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"));
|
||||
write_command(0x05, 0x08, 0xFF, 0x1C);
|
||||
return true;
|
||||
|
||||
@@ -27,7 +27,7 @@ class Boiler : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -29,7 +29,7 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
||||
}
|
||||
|
||||
// publish HA config
|
||||
bool Connect::publish_ha_config() {
|
||||
bool Connect::publish_ha_device_config() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class Connect : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -29,7 +29,7 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
}
|
||||
|
||||
// publish HA config
|
||||
bool Controller::publish_ha_config() {
|
||||
bool Controller::publish_ha_device_config() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class Controller : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -29,7 +29,7 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
||||
}
|
||||
|
||||
// publish HA config
|
||||
bool Gateway::publish_ha_config() {
|
||||
bool Gateway::publish_ha_device_config() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class Gateway : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -34,7 +34,7 @@ Generic::Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
||||
}
|
||||
|
||||
// publish HA config
|
||||
bool Generic::publish_ha_config() {
|
||||
bool Generic::publish_ha_device_config() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class Generic : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -39,7 +39,7 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
|
||||
}
|
||||
|
||||
// publish HA config
|
||||
bool Heatpump::publish_ha_config() {
|
||||
bool Heatpump::publish_ha_device_config() {
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
|
||||
doc["uniq_id"] = F_(heatpump);
|
||||
doc["ic"] = F_(icondevice);
|
||||
|
||||
@@ -27,7 +27,7 @@ class Heatpump : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -100,7 +100,7 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
|
||||
}
|
||||
|
||||
// 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 (!Helpers::hasValue(pumpStatus_)) {
|
||||
return false;
|
||||
|
||||
@@ -27,7 +27,7 @@ class Mixer : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -180,7 +180,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
|
||||
}
|
||||
|
||||
// publish HA config
|
||||
bool Solar::publish_ha_config() {
|
||||
bool Solar::publish_ha_device_config() {
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
|
||||
doc["uniq_id"] = F_(solar);
|
||||
doc["ic"] = F_(icondevice);
|
||||
|
||||
@@ -27,7 +27,7 @@ class Solar : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -40,7 +40,7 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
|
||||
}
|
||||
|
||||
// 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 (!Helpers::hasValue(flowTempHc_)) {
|
||||
return false;
|
||||
|
||||
@@ -27,7 +27,7 @@ class Switch : public EMSdevice {
|
||||
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);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -192,7 +192,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
}
|
||||
|
||||
// publish HA config
|
||||
bool Thermostat::publish_ha_config() {
|
||||
bool Thermostat::publish_ha_device_config() {
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
|
||||
doc["uniq_id"] = F_(thermostat);
|
||||
doc["ic"] = F_(icondevice);
|
||||
@@ -384,7 +384,7 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
|
||||
// publish config topic for HA MQTT Discovery for each of the heating circuit
|
||||
// e.g. homeassistant/climate/ems-esp/thermostat_hc1/config
|
||||
void Thermostat::publish_ha_config_hc(std::shared_ptr<Thermostat::HeatingCircuit> hc) {
|
||||
uint8_t hc_num = hc->hc_num();
|
||||
uint8_t hc_num = hc->hc_num();
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
|
||||
|
||||
char str1[20];
|
||||
@@ -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
|
||||
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("heat");
|
||||
@@ -1798,26 +1798,26 @@ bool Thermostat::set_mode(const char * value, const int8_t id) {
|
||||
uint8_t num = value[0] - '0';
|
||||
switch (model()) {
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC10:
|
||||
mode = uuid::read_flash_string(FL_(enum_mode6)[num]);
|
||||
mode = read_flash_string(FL_(enum_mode6)[num]);
|
||||
break;
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC20:
|
||||
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;
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC30:
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC35:
|
||||
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;
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC300:
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC100:
|
||||
mode = uuid::read_flash_string(FL_(enum_mode)[num]);
|
||||
mode = read_flash_string(FL_(enum_mode)[num]);
|
||||
break;
|
||||
case EMSdevice::EMS_DEVICE_FLAG_JUNKERS:
|
||||
mode = uuid::read_flash_string(FL_(enum_mode4)[num]);
|
||||
mode = read_flash_string(FL_(enum_mode4)[num]);
|
||||
break;
|
||||
case EMSdevice::EMS_DEVICE_FLAG_CRF:
|
||||
mode = uuid::read_flash_string(FL_(enum_mode5)[num]);
|
||||
mode = read_flash_string(FL_(enum_mode5)[num]);
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(F("Set mode: Invalid mode"));
|
||||
@@ -2673,13 +2673,8 @@ void Thermostat::register_device_values() {
|
||||
FL_(ibaCalIntTemperature),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_calinttemp));
|
||||
register_device_value(TAG_THERMOSTAT_DATA,
|
||||
&heatingpid_,
|
||||
DeviceValueType::ENUM,
|
||||
FL_(enum_PID),
|
||||
FL_(heatingPID),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_heatingpid));
|
||||
register_device_value(
|
||||
TAG_THERMOSTAT_DATA, &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_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode3), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
|
||||
break;
|
||||
|
||||
@@ -127,7 +127,7 @@ class Thermostat : public EMSdevice {
|
||||
|
||||
static std::string mode_tostring(uint8_t mode);
|
||||
|
||||
virtual bool publish_ha_config();
|
||||
virtual bool publish_ha_device_config();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -117,18 +117,18 @@ static const __FlashStringHelper * const DeviceValueTAG_mqtt[] PROGMEM = {
|
||||
};
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (uom == DeviceValueUOM::NONE) {
|
||||
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 {
|
||||
@@ -374,13 +374,13 @@ bool EMSdevice::is_fetch(uint16_t telegram_id) {
|
||||
// list of registered device entries, adding the HA entity if it exists
|
||||
void EMSdevice::list_device_entries(JsonObject & output) {
|
||||
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
|
||||
char key[50];
|
||||
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 {
|
||||
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);
|
||||
@@ -405,14 +405,14 @@ void EMSdevice::list_device_entries(JsonObject & output) {
|
||||
const std::string EMSdevice::device_entity_ha(DeviceValue const & dv) {
|
||||
std::string entity_name(50, '\0');
|
||||
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 {
|
||||
snprintf(&entity_name[0],
|
||||
entity_name.capacity() + 1,
|
||||
"sensor.%s %s %s",
|
||||
this->device_type_name().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(), ' ', '_');
|
||||
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
|
||||
@@ -581,7 +585,7 @@ const std::string EMSdevice::get_value_uom(const char * key) {
|
||||
|
||||
size_t sz = sizeof(DeviceValueTAG_s) / sizeof(__FlashStringHelper *);
|
||||
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()) {
|
||||
std::string key2 = key; // copy char to a std::string
|
||||
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
|
||||
for (const auto & dv : devicevalues_) {
|
||||
if (dv_is_visible(dv)) {
|
||||
if (uuid::read_flash_string(dv.full_name) == key_p) {
|
||||
if (dv.has_state(DeviceValueState::DV_VISIBLE)) {
|
||||
if (read_flash_string(dv.full_name) == key_p) {
|
||||
// ignore TIME since "minutes" is already added to the string value
|
||||
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
|
||||
break;
|
||||
@@ -608,51 +612,50 @@ const std::string EMSdevice::get_value_uom(const char * key) {
|
||||
}
|
||||
|
||||
// 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
|
||||
void EMSdevice::generate_values_json_web(JsonObject & output) {
|
||||
output["name"] = to_string_short();
|
||||
JsonArray data = output.createNestedArray("data");
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
// 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["v"] = *(bool *)(dv.value_p) ? "on" : "off";
|
||||
}
|
||||
|
||||
// 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["v"] = (char *)(dv.value_p);
|
||||
}
|
||||
|
||||
// handle ENUMs
|
||||
else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
|
||||
if (*(uint8_t *)(dv.value_p) < dv.options_size) {
|
||||
obj = data.createNestedObject();
|
||||
obj["v"] = dv.options[*(uint8_t *)(dv.value_p)];
|
||||
}
|
||||
else if ((dv.type == DeviceValueType::ENUM) && (*(uint8_t *)(dv.value_p) < dv.options_size)) {
|
||||
obj = data.createNestedObject();
|
||||
obj["v"] = dv.options[*(uint8_t *)(dv.value_p)];
|
||||
}
|
||||
|
||||
// // handle commands without value
|
||||
// else if (dv.type == DeviceValueType::CMD) {
|
||||
// obj = data.createNestedObject();
|
||||
// obj["v"] = "";
|
||||
// }
|
||||
|
||||
// handle Integers and Floats
|
||||
else {
|
||||
// handle Integers and Floats
|
||||
// If a divider is specified, do the division to 2 decimals places and send back as double/float
|
||||
// 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
|
||||
uint8_t divider = 0;
|
||||
uint8_t factor = 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] == '*') {
|
||||
factor = Helpers::atoint(&s[1]);
|
||||
} 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["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["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["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["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["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);
|
||||
obj = data.createNestedObject();
|
||||
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
|
||||
if (obj.containsKey("v")) {
|
||||
// add the unit of measure (uom)
|
||||
obj["u"] = dv.uom;
|
||||
obj["u"] = dv.uom; // add the unit of measure (uom)
|
||||
|
||||
// add name, prefixing the tag if it exists
|
||||
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
|
||||
obj["n"] = dv.full_name;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -700,7 +702,7 @@ void EMSdevice::generate_values_json_web(JsonObject & output) {
|
||||
if (dv.has_cmd) {
|
||||
// add the name of the Command function
|
||||
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 {
|
||||
obj["c"] = dv.short_name;
|
||||
}
|
||||
@@ -708,8 +710,8 @@ void EMSdevice::generate_values_json_web(JsonObject & output) {
|
||||
if (dv.type == DeviceValueType::ENUM) {
|
||||
JsonArray l = obj.createNestedArray("l");
|
||||
for (uint8_t i = 0; i < dv.options_size; i++) {
|
||||
if (!uuid::read_flash_string(dv.options[i]).empty()) {
|
||||
l.add(uuid::read_flash_string(dv.options[i]));
|
||||
if (!read_flash_string(dv.options[i]).empty()) {
|
||||
l.add(read_flash_string(dv.options[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -724,7 +726,7 @@ void EMSdevice::generate_values_json_web(JsonObject & output) {
|
||||
}
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// serializeJson(data, Serial); // debug only
|
||||
// serializeJson(data, Serial); // debug only
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -746,11 +748,12 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
|
||||
|
||||
// search device value with this tag
|
||||
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 factor = 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] == '*') {
|
||||
factor = Helpers::atoint(&s[1]);
|
||||
} 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()) {
|
||||
json["fullname"] = dv.full_name;
|
||||
} 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()) {
|
||||
@@ -784,7 +787,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
|
||||
}
|
||||
}
|
||||
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[max] = dv.options_size - 1;
|
||||
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[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;
|
||||
|
||||
case DeviceValueType::UINT:
|
||||
@@ -812,7 +815,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
|
||||
if (dv.uom == DeviceValueUOM::PERCENT) {
|
||||
json[max] = 100;
|
||||
} 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;
|
||||
|
||||
@@ -822,7 +825,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
|
||||
}
|
||||
json[type] = F_(number);
|
||||
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;
|
||||
|
||||
case DeviceValueType::INT:
|
||||
@@ -835,7 +838,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
|
||||
json[max] = 100;
|
||||
} else {
|
||||
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;
|
||||
|
||||
@@ -932,42 +935,53 @@ bool EMSdevice::generate_values_json(JsonObject & output, const uint8_t tag_filt
|
||||
JsonObject json = output;
|
||||
|
||||
for (auto & dv : devicevalues_) {
|
||||
// conditions
|
||||
bool condition;
|
||||
condition = (tag_filter == DeviceValueTAG::TAG_NONE) || (tag_filter == dv.tag); // tag must be either empty or match a tag passed to this function
|
||||
// check conditions:
|
||||
// 1. it must have a valid value
|
||||
// 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) {
|
||||
condition &= dv_is_visible(dv); // value must be visible if outputting to API (web or console). This is for ID, hamode, hatemp etc
|
||||
// check if it exists. We set the value activated once here
|
||||
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/""
|
||||
bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty());
|
||||
|
||||
// create the name for the JSON key
|
||||
char name[80];
|
||||
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) {
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
uint8_t bool_format = EMSESP::bool_format();
|
||||
if (bool_format == BOOL_FORMAT_ONOFF) {
|
||||
@@ -979,17 +993,15 @@ bool EMSdevice::generate_values_json(JsonObject & output, const uint8_t tag_filt
|
||||
} else {
|
||||
json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0;
|
||||
}
|
||||
has_value = true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
has_value = true;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// check for numeric enum-format, but "hamode" always as text
|
||||
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 {
|
||||
json[name] = dv.options[*(uint8_t *)(dv.value_p)];
|
||||
}
|
||||
has_value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// handle Integers and Floats
|
||||
// If a divider is specified, do the division to 2 decimals places and send back as double/float
|
||||
// otherwise force as a whole integer
|
||||
// note: the strange nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
|
||||
else {
|
||||
// If a divider is specified, do the division to 2 decimals places and send back as double/float
|
||||
// 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
|
||||
uint8_t divider = 0;
|
||||
uint8_t factor = 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] == '*') {
|
||||
factor = Helpers::atoint(&s[1]);
|
||||
} 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));
|
||||
|
||||
// INT
|
||||
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) {
|
||||
if (dv.type == DeviceValueType::INT) {
|
||||
if (make_float) {
|
||||
json[name] = Helpers::round2(*(int8_t *)(dv.value_p), divider);
|
||||
} else {
|
||||
json[name] = *(int8_t *)(dv.value_p) * factor;
|
||||
}
|
||||
has_value = true;
|
||||
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
|
||||
} else if (dv.type == DeviceValueType::UINT) {
|
||||
if (make_float) {
|
||||
json[name] = Helpers::round2(*(uint8_t *)(dv.value_p), divider);
|
||||
} else {
|
||||
json[name] = *(uint8_t *)(dv.value_p) * factor;
|
||||
}
|
||||
has_value = true;
|
||||
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) {
|
||||
} else if (dv.type == DeviceValueType::SHORT) {
|
||||
if (make_float) {
|
||||
json[name] = Helpers::round2(*(int16_t *)(dv.value_p), divider);
|
||||
} else {
|
||||
json[name] = *(int16_t *)(dv.value_p) * factor;
|
||||
}
|
||||
has_value = true;
|
||||
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
|
||||
} else if (dv.type == DeviceValueType::USHORT) {
|
||||
if (make_float) {
|
||||
json[name] = Helpers::round2(*(uint16_t *)(dv.value_p), divider);
|
||||
} else {
|
||||
json[name] = *(uint16_t *)(dv.value_p) * factor;
|
||||
}
|
||||
has_value = true;
|
||||
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
|
||||
} else if (dv.type == DeviceValueType::ULONG) {
|
||||
if (make_float) {
|
||||
json[name] = Helpers::round2(*(uint32_t *)(dv.value_p), divider);
|
||||
} else {
|
||||
json[name] = *(uint32_t *)(dv.value_p) * factor;
|
||||
}
|
||||
has_value = true;
|
||||
} 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);
|
||||
time_value = (divider) ? time_value / divider : time_value * factor; // sometimes we need to divide by 60
|
||||
if (output_target == EMSdevice::OUTPUT_TARGET::API_VERBOSE) {
|
||||
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;
|
||||
} else {
|
||||
json[name] = time_value;
|
||||
}
|
||||
has_value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
dv.ha |= has_value ? DeviceValueHA::HA_VALUE : DeviceValueHA::HA_NONE;
|
||||
has_values |= has_value;
|
||||
}
|
||||
|
||||
return has_values;
|
||||
}
|
||||
|
||||
// create the Home Assistant configs for each value
|
||||
// this is called when an MQTT publish is done via an EMS Device
|
||||
void EMSdevice::publish_mqtt_ha_sensor() {
|
||||
for (auto & dv : devicevalues_) {
|
||||
if (dv.ha == DeviceValueHA::HA_VALUE) {
|
||||
Mqtt::publish_ha_sensor(dv.type, dv.tag, dv.full_name, dv.device_type, dv.short_name, dv.uom, dv.has_cmd);
|
||||
dv.ha |= DeviceValueHA::HA_DONE;
|
||||
}
|
||||
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp
|
||||
// if the main Device Entity config for the device hasn't been setup its also done here
|
||||
void EMSdevice::publish_mqtt_ha_entity_config() {
|
||||
// create the main device config if not doing already
|
||||
if (!ha_config_done()) {
|
||||
bool ok = publish_ha_device_config();
|
||||
ha_config_done(ok); // see if it worked
|
||||
}
|
||||
|
||||
if (!ha_config_done()) {
|
||||
bool ok = publish_ha_config();
|
||||
ha_config_done(ok); // see if it worked
|
||||
for (auto & dv : devicevalues_) {
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
// 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() {
|
||||
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);
|
||||
}
|
||||
@@ -1111,7 +1146,7 @@ const std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram>
|
||||
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
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
|
||||
// so remove it from the automatic fetch list
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -119,12 +119,14 @@ enum DeviceValueTAG : uint8_t {
|
||||
|
||||
};
|
||||
|
||||
// MQTT HA flags
|
||||
enum DeviceValueHA : uint8_t {
|
||||
// states of a device value
|
||||
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 {
|
||||
@@ -134,7 +136,7 @@ class EMSdevice {
|
||||
static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20;
|
||||
|
||||
// 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..
|
||||
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 publish_mqtt_ha_sensor();
|
||||
void publish_mqtt_ha_entity_config();
|
||||
|
||||
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)
|
||||
struct DeviceValue {
|
||||
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
|
||||
int32_t min;
|
||||
uint32_t max;
|
||||
uint8_t state; // DeviceValueState::*
|
||||
|
||||
DeviceValue(uint8_t device_type,
|
||||
uint8_t tag,
|
||||
@@ -444,7 +446,8 @@ class EMSdevice {
|
||||
uint8_t ha,
|
||||
bool has_cmd,
|
||||
int32_t min,
|
||||
uint32_t max)
|
||||
uint32_t max,
|
||||
uint8_t state)
|
||||
: device_type(device_type)
|
||||
, tag(tag)
|
||||
, value_p(value_p)
|
||||
@@ -457,7 +460,22 @@ class EMSdevice {
|
||||
, ha(ha)
|
||||
, has_cmd(has_cmd)
|
||||
, 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;
|
||||
@@ -467,13 +485,11 @@ class EMSdevice {
|
||||
|
||||
const std::string device_entity_ha(DeviceValue const & dv);
|
||||
|
||||
bool check_dv_hasvalue(const DeviceValue & dv);
|
||||
|
||||
void init_devicevalues(uint8_t size) {
|
||||
devicevalues_.reserve(size);
|
||||
}
|
||||
|
||||
inline bool dv_is_visible(DeviceValue dv) {
|
||||
return (dv.full_name);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -458,7 +458,7 @@ void EMSESP::reset_mqtt_ha() {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size
|
||||
JsonObject json = doc.to<JsonObject>();
|
||||
@@ -469,11 +469,6 @@ void EMSESP::publish_device_values(uint8_t device_type) {
|
||||
// group by device type
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
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 (device_type == DeviceType::BOILER) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -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
|
||||
for (const auto & device : device_library_) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -988,7 +988,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
|
||||
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 flags = device_p->flags;
|
||||
|
||||
|
||||
@@ -126,9 +126,9 @@ char * Helpers::smallitoa(char * result, const uint16_t value) {
|
||||
char * Helpers::render_boolean(char * result, bool value) {
|
||||
uint8_t bool_format_ = EMSESP::bool_format();
|
||||
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) {
|
||||
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) {
|
||||
strlcpy(result, value ? "true" : "false", 7);
|
||||
} 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
|
||||
std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
|
||||
if (length == 0) {
|
||||
return uuid::read_flash_string(F("<empty>"));
|
||||
return read_flash_string(F("<empty>"));
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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;
|
||||
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;
|
||||
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);
|
||||
for (value = 0; strs[value]; value++) {
|
||||
std::string str1 = toLower(uuid::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)
|
||||
std::string str1 = toLower(read_flash_string(strs[value]));
|
||||
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')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -187,6 +187,8 @@ MAKE_PSTR(mv, "mV")
|
||||
MAKE_PSTR(times, "times")
|
||||
MAKE_PSTR(oclock, "o'clock")
|
||||
|
||||
MAKE_PSTR(days, "days")
|
||||
|
||||
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
|
||||
// use empty string if want to suppress showing tags
|
||||
// tags must not have spaces
|
||||
|
||||
182
src/mqtt.cpp
182
src/mqtt.cpp
@@ -158,7 +158,7 @@ void Mqtt::loop() {
|
||||
|
||||
// print MQTT log and other stuff to console
|
||||
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.println();
|
||||
@@ -583,24 +583,39 @@ void Mqtt::ha_status() {
|
||||
|
||||
// create the sensors - must match the MQTT payload keys
|
||||
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(DeviceValueType::INT,
|
||||
DeviceValueTAG::TAG_HEARTBEAT,
|
||||
F("WiFi strength"),
|
||||
EMSdevice::DeviceType::SYSTEM,
|
||||
F("wifistrength"),
|
||||
DeviceValueUOM::PERCENT);
|
||||
publish_ha_sensor_config(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 strength"),
|
||||
EMSdevice::DeviceType::SYSTEM,
|
||||
F("wifistrength"),
|
||||
DeviceValueUOM::PERCENT);
|
||||
}
|
||||
|
||||
publish_ha_sensor(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(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"), DeviceValueUOM::KB);
|
||||
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("MQTT fails"), EMSdevice::DeviceType::SYSTEM, F("mqttfails"), DeviceValueUOM::TIMES);
|
||||
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Rx received"), EMSdevice::DeviceType::SYSTEM, F("rxreceived"), DeviceValueUOM::TIMES);
|
||||
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Rx fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails"), DeviceValueUOM::TIMES);
|
||||
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx reads"), EMSdevice::DeviceType::SYSTEM, F("txreads"), DeviceValueUOM::TIMES);
|
||||
publish_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx writes"), EMSdevice::DeviceType::SYSTEM, F("txwrites"), DeviceValueUOM::TIMES);
|
||||
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("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime"), DeviceValueUOM::NONE);
|
||||
publish_ha_sensor_config(DeviceValueType::INT,
|
||||
DeviceValueTAG::TAG_HEARTBEAT,
|
||||
F("Uptime (sec)"),
|
||||
EMSdevice::DeviceType::SYSTEM,
|
||||
F("uptime_sec"),
|
||||
DeviceValueUOM::SECONDS);
|
||||
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"), DeviceValueUOM::KB);
|
||||
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.
|
||||
@@ -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 ((operation == Operation::PUBLISH) && (payload.empty())) {
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_WARNING("[DEBUG] Publish empty payload - quitting");
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
*/
|
||||
|
||||
// take the topic and prefix the base, unless its for HA
|
||||
std::shared_ptr<MqttMessage> message;
|
||||
@@ -622,7 +642,11 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
if (operation == Operation::PUBLISH) {
|
||||
LOG_INFO("[DEBUG] Adding to queue: (Publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str());
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
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
|
||||
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
|
||||
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) {
|
||||
publish(uuid::read_flash_string(topic), payload);
|
||||
publish(read_flash_string(topic), payload);
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
@@ -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) {
|
||||
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) {
|
||||
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.
|
||||
@@ -714,7 +752,7 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) {
|
||||
payload_text.reserve(measureJson(payload) + 1);
|
||||
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)
|
||||
LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), fulltopic.c_str(), payload_text.c_str());
|
||||
#elif defined(EMSESP_DEBUG)
|
||||
@@ -737,7 +775,7 @@ void Mqtt::process_queue() {
|
||||
auto message = mqtt_message.content_;
|
||||
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
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
void Mqtt::publish_ha_sensor(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)
|
||||
const bool has_cmd) {
|
||||
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)
|
||||
const bool remove, // true if we want to remove this topic
|
||||
const bool has_cmd) {
|
||||
// ignore if name (fullname) is empty
|
||||
if (name == nullptr) {
|
||||
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();
|
||||
|
||||
// nested_format is 1 if nested, otherwise 2 for single topics
|
||||
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);
|
||||
|
||||
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["~"] = mqtt_base_;
|
||||
doc["uniq_id"] = uniq;
|
||||
|
||||
// state topic
|
||||
@@ -853,9 +917,9 @@ void Mqtt::publish_ha_sensor(uint8_t type, // EMSdevice::Dev
|
||||
// name = <device> <tag> <name>
|
||||
char new_name[80];
|
||||
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 {
|
||||
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
|
||||
doc["name"] = new_name;
|
||||
@@ -866,25 +930,17 @@ void Mqtt::publish_ha_sensor(uint8_t type, // EMSdevice::Dev
|
||||
if (is_nested) {
|
||||
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity);
|
||||
} 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;
|
||||
|
||||
char topic[MQTT_TOPIC_MAX_SIZE];
|
||||
|
||||
// look at the device value type
|
||||
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
|
||||
char result[10];
|
||||
doc[F("payload_on")] = Helpers::render_boolean(result, true);
|
||||
doc[F("payload_off")] = Helpers::render_boolean(result, false);
|
||||
} 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
|
||||
auto set_state_class = State_class::NONE;
|
||||
auto set_device_class = Device_class::NONE;
|
||||
|
||||
23
src/mqtt.h
23
src/mqtt.h
@@ -110,14 +110,23 @@ class Mqtt {
|
||||
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 __FlashStringHelper * topic, const JsonObject & payload);
|
||||
static void publish_ha(const std::string & topic);
|
||||
|
||||
static void publish_ha_sensor(uint8_t type,
|
||||
uint8_t tag,
|
||||
const __FlashStringHelper * name,
|
||||
const uint8_t device_type,
|
||||
const __FlashStringHelper * entity,
|
||||
const uint8_t uom,
|
||||
const bool has_cmd = false);
|
||||
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,
|
||||
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_mqtt(uuid::console::Shell & shell);
|
||||
|
||||
@@ -830,7 +830,7 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
} else {
|
||||
shell.printfln(F("Syslog: %s"), syslog_.started() ? "started" : "stopped");
|
||||
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.print(F(" "));
|
||||
shell.printfln(F_(port_fmt), syslog_port_);
|
||||
|
||||
@@ -471,10 +471,38 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
|
||||
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") {
|
||||
shell.printfln(F("Testing API with MQTT and REST, standalone"));
|
||||
|
||||
|
||||
Mqtt::ha_enabled(true);
|
||||
// Mqtt::ha_enabled(false);
|
||||
|
||||
|
||||
@@ -39,8 +39,10 @@ namespace emsesp {
|
||||
// #define EMSESP_DEBUG_DEFAULT "shower_alert"
|
||||
// #define EMSESP_DEBUG_DEFAULT "310"
|
||||
// #define EMSESP_DEBUG_DEFAULT "render"
|
||||
#define EMSESP_DEBUG_DEFAULT "api"
|
||||
// #define EMSESP_DEBUG_DEFAULT "api"
|
||||
// #define EMSESP_DEBUG_DEFAULT "crash"
|
||||
#define EMSESP_DEBUG_DEFAULT "dv"
|
||||
|
||||
|
||||
class Test {
|
||||
public:
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.3.0b6"
|
||||
#define EMSESP_APP_VERSION "3.3.0b7"
|
||||
|
||||
Reference in New Issue
Block a user