added all devices - API: HTTP read/write #506

This commit is contained in:
proddy
2020-09-20 15:52:44 +02:00
parent 22fcff6682
commit 9fc15650a2
35 changed files with 742 additions and 425 deletions

View File

@@ -17,7 +17,7 @@ class EMSESPHelp extends Component {
</Box> </Box>
<br></br> <br></br>
<Typography variant="body1" paragraph> <Typography variant="body1" paragraph>
Check for news and updates on the <Link href="https://emsesp.github.io/docs" color="primary">{'Wiki'}</Link>. Check for news and updates on the <Link href="https://emsesp.github.io/docs" color="primary">{'Documentation site'}</Link>.
</Typography> </Typography>
<Typography variant="body1" paragraph> <Typography variant="body1" paragraph>
For live community chat go to <Link href="https://gitter.im/EMS-ESP/community#" color="primary">{'Gitter'}</Link>. For live community chat go to <Link href="https://gitter.im/EMS-ESP/community#" color="primary">{'Gitter'}</Link>.

View File

@@ -48,7 +48,7 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
<ValidatorForm onSubmit={saveData}> <ValidatorForm onSubmit={saveData}>
<Box bgcolor="info.main" p={2} mt={2} mb={2}> <Box bgcolor="info.main" p={2} mt={2} mb={2}>
<Typography variant="body1"> <Typography variant="body1">
Customize EMS-ESP by modifying the default settings here. Refer to the <Link href="https://emsesp.github.io/docs/#/Configure-firmware" color="primary">{'Wiki'}</Link>&nbsp;for descriptions of each setting. Customize EMS-ESP by modifying the default settings here. Refer to the <Link href="https://emsesp.github.io/docs/#/Configure-firmware?id=settings" color="primary">{'Documentation'}</Link>&nbsp;for descriptions of each setting.
</Typography> </Typography>
</Box> </Box>
<br></br> <br></br>
@@ -181,6 +181,20 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
label="Shower Alert" label="Shower Alert"
/> />
<br></br> <br></br>
<Typography variant="h6" color="primary" >
API
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.api_enabled}
onChange={handleValueChange('api_enabled')}
value="api_enabled"
/>
}
label="Enable WEB API (for write commands)"
/>
<br></br>
<Typography variant="h6" color="primary" > <Typography variant="h6" color="primary" >
Syslog Syslog
</Typography> </Typography>

View File

@@ -13,6 +13,7 @@ export interface EMSESPSettings {
dallas_parasite: boolean; dallas_parasite: boolean;
led_gpio: number; led_gpio: number;
hide_led: boolean; hide_led: boolean;
api_enabled: boolean;
} }
export enum busConnectionStatus { export enum busConnectionStatus {

View File

@@ -4,9 +4,9 @@
namespace emsesp { namespace emsesp {
class EMSESP { class EMSESP {
public: public:
static System system_; static System system_;
static Mqtt mqtt_; static Mqtt mqtt_;
static Sensors sensors_; static Sensor sensor_;
}; };
} // namespace emsesp } // namespace emsesp
@@ -169,7 +169,7 @@ void MqttSettingsService::configureMqtt() {
_mqttClient.connect(); _mqttClient.connect();
} }
emsesp::EMSESP::sensors_.reload(); emsesp::EMSESP::sensor_.reload();
} }
void MqttSettings::read(MqttSettings & settings, JsonObject & root) { void MqttSettings::read(MqttSettings & settings, JsonObject & root) {

View File

@@ -10,7 +10,7 @@
#include "../../src/system.h" #include "../../src/system.h"
#include "../../src/mqtt.h" #include "../../src/mqtt.h"
#include "../../src/sensors.h" #include "../../src/sensor.h"
#define MQTT_RECONNECTION_DELAY 1000 #define MQTT_RECONNECTION_DELAY 1000

View File

@@ -23,6 +23,7 @@ class DummySettings {
bool shower_timer = false; bool shower_timer = false;
bool shower_alert = false; bool shower_alert = false;
bool hide_led = false; bool hide_led = false;
bool api_enabled = true;
uint16_t publish_time = 10; // seconds uint16_t publish_time = 10; // seconds
uint8_t mqtt_format = 3; // 1=single, 2=nested, 3=ha, 4=custom uint8_t mqtt_format = 3; // 1=single, 2=nested, 3=ha, 4=custom
uint8_t mqtt_qos = 0; uint8_t mqtt_qos = 0;

View File

@@ -30,9 +30,13 @@ EMSESPAPIService::EMSESPAPIService(AsyncWebServer * server) {
// http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1 // http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1
void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) { void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) {
// see if the API is enabled
bool api_enabled;
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { api_enabled = settings.api_enabled; });
// must have device and cmd parameters // must have device and cmd parameters
if ((!request->hasParam(F_(device))) || (!request->hasParam(F_(cmd)))) { if ((!request->hasParam(F_(device))) || (!request->hasParam(F_(cmd)))) {
request->send(400, "text/plain", F("invalid syntax")); request->send(400, "text/plain", F("Invalid syntax"));
return; return;
} }
@@ -40,26 +44,16 @@ void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) {
String device = request->getParam(F_(device))->value(); String device = request->getParam(F_(device))->value();
uint8_t device_type = EMSdevice::device_name_2_device_type(device.c_str()); uint8_t device_type = EMSdevice::device_name_2_device_type(device.c_str());
if (device_type == emsesp::EMSdevice::DeviceType::UNKNOWN) { if (device_type == emsesp::EMSdevice::DeviceType::UNKNOWN) {
request->send(400, "text/plain", F("invalid device")); request->send(400, "text/plain", F("Invalid device"));
return; return;
} }
// get cmd, we know we have one // get cmd, we know we have one
String cmd = request->getParam(F_(cmd))->value(); String cmd = request->getParam(F_(cmd))->value();
// first test for special service commands
// e.g. http://ems-esp/api?device=system&cmd=info
if (device.equals("system")) {
if (cmd.equals("info")) {
request->send(200, "text/plain", System::export_settings());
EMSESP::logger().info(F("Sent settings json to web UI"));
return;
}
}
// look up command in our list // look up command in our list
if (!Command::find(device_type, cmd.c_str())) { if (!Command::find(device_type, cmd.c_str())) {
request->send(400, "text/plain", F("invalid cmd")); request->send(400, "text/plain", F("Invalid cmd"));
return; return;
} }
@@ -73,31 +67,54 @@ void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) {
id = request->getParam(F_(id))->value(); id = request->getParam(F_(id))->value();
} }
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
JsonObject output = doc.to<JsonObject>();
bool ok = false;
// execute the command // execute the command
bool ok = false;
if (data.isEmpty()) { if (data.isEmpty()) {
ok = Command::call_command(device_type, cmd.c_str(), nullptr, -1); // command only ok = Command::call(device_type, cmd.c_str(), nullptr, -1, output); // command only
} else if (id.isEmpty()) {
ok = Command::call_command(device_type, cmd.c_str(), data.c_str(), -1); // only ID
} else { } else {
ok = Command::call_command(device_type, cmd.c_str(), data.c_str(), id.toInt()); // has cmd, data and id if (api_enabled) {
// we only allow commands with parameters if the API is enabled
if (id.isEmpty()) {
ok = Command::call(device_type, cmd.c_str(), data.c_str(), -1, output); // only ID
} else {
ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), output); // has cmd, data and id
}
} else {
request->send(401, "text/plain", F("Unauthorized"));
return;
}
} }
// debug // debug
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
std::string output(200, '\0'); std::string debug(200, '\0');
snprintf_P(&output[0], snprintf_P(&debug[0],
output.capacity() + 1, debug.capacity() + 1,
PSTR("API: device=%s cmd=%s data=%s id=%s [%s]"), PSTR("API: device=%s cmd=%s data=%s id=%s [%s]"),
device.c_str(), device.c_str(),
cmd.c_str(), cmd.c_str(),
data.c_str(), data.c_str(),
id.c_str(), id.c_str(),
ok ? F("OK") : F("Failed")); ok ? F("OK") : F("Invalid"));
EMSESP::logger().info(output.c_str()); EMSESP::logger().info(debug.c_str());
if (output.size()) {
char buffer2[EMSESP_MAX_JSON_SIZE_LARGE];
serializeJson(doc, buffer2);
EMSESP::logger().info("output (max 255 chars): %s", buffer2);
}
#endif #endif
request->send(200, "text/plain", ok ? F("OK") : F("Failed")); // if we have returned data in JSON format, send this to the WEB
if (output.size()) {
char buffer[EMSESP_MAX_JSON_SIZE_LARGE];
serializeJson(doc, buffer);
request->send(200, "text/plain", buffer);
} else {
request->send(200, "text/plain", ok ? F("OK") : F("Invalid"));
}
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -33,7 +33,6 @@ class EMSESPAPIService {
private: private:
void emsespAPIService(AsyncWebServerRequest * request); void emsespAPIService(AsyncWebServerRequest * request);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -42,6 +42,7 @@ void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
root["dallas_parasite"] = settings.dallas_parasite; root["dallas_parasite"] = settings.dallas_parasite;
root["led_gpio"] = settings.led_gpio; root["led_gpio"] = settings.led_gpio;
root["hide_led"] = settings.hide_led; root["hide_led"] = settings.hide_led;
root["api_enabled"] = settings.api_enabled;
} }
StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) { StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) {
@@ -59,6 +60,7 @@ StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & set
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE; settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
settings.led_gpio = root["led_gpio"] | EMSESP_DEFAULT_LED_GPIO; settings.led_gpio = root["led_gpio"] | EMSESP_DEFAULT_LED_GPIO;
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED; settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
settings.api_enabled = root["api_enabled"] | EMSESP_DEFAULT_API_ENABLED;
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
} }
@@ -70,8 +72,8 @@ void EMSESPSettingsService::onUpdate() {
// EMSESP::system_.syslog_init(); // changing SysLog will require a restart // EMSESP::system_.syslog_init(); // changing SysLog will require a restart
EMSESP::init_tx(); EMSESP::init_tx();
System::set_led(); System::set_led();
Sensors sensors_; // Dallas sensors Sensor sensor_; // Dallas sensors
sensors_.start(); sensor_.start();
} }
void EMSESPSettingsService::begin() { void EMSESPSettingsService::begin() {

View File

@@ -35,6 +35,7 @@
#define EMSESP_DEFAULT_SHOWER_ALERT false #define EMSESP_DEFAULT_SHOWER_ALERT false
#define EMSESP_DEFAULT_HIDE_LED false #define EMSESP_DEFAULT_HIDE_LED false
#define EMSESP_DEFAULT_DALLAS_PARASITE false #define EMSESP_DEFAULT_DALLAS_PARASITE false
#define EMSESP_DEFAULT_API_ENABLED true
// Default GPIO PIN definitions // Default GPIO PIN definitions
#if defined(ESP8266) #if defined(ESP8266)
@@ -75,6 +76,7 @@ class EMSESPSettings {
bool dallas_parasite; bool dallas_parasite;
uint8_t led_gpio; uint8_t led_gpio;
bool hide_led; bool hide_led;
bool api_enabled;
static void read(EMSESPSettings & settings, JsonObject & root); static void read(EMSESPSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings); static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings);

View File

@@ -29,12 +29,15 @@ std::vector<Command::CmdFunction> Command::cmdfunctions_;
// calls a command, context is the device_type // calls a command, context is the device_type
// id may be used to represent a heating circuit for example // id may be used to represent a heating circuit for example
// returns false if error or not found // returns false if error or not found
bool Command::call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) { bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & output) {
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
if (id == -1) { std::string dname = EMSdevice::device_type_2_device_name(device_type);
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is default"), cmd, value); if (value == nullptr) {
LOG_DEBUG(F("[DEBUG] Calling command %s in %s"), cmd, dname.c_str());
} else if (id == -1) {
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is default in %s"), cmd, value, dname.c_str());
} else { } else {
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is %d"), cmd, value, id); LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is %d in %s"), cmd, value, id, dname.c_str());
} }
#endif #endif
@@ -42,23 +45,37 @@ bool Command::call_command(const uint8_t device_type, const char * cmd, const ch
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if (cf.device_type_ == device_type) { if (cf.device_type_ == device_type) {
const char * cf_cmd = uuid::read_flash_string(cf.cmd_).c_str(); const char * cf_cmd = uuid::read_flash_string(cf.cmd_).c_str();
// find a matching command and call it
if (strcmp(cf_cmd, cmd) == 0) { if (strcmp(cf_cmd, cmd) == 0) {
return ((cf.cmdfunction_)(value, id)); // call function, data needs to be a string and can be null if (cf.cmdfunction_json_) {
// check if json object is empty, if so quit
if (output.isNull()) {
LOG_WARNING(F("Ignore call for command %s in %s because no json"), cmd, EMSdevice::device_type_2_device_name(device_type).c_str());
}
return ((cf.cmdfunction_json_)(value, id, output));
} else {
return ((cf.cmdfunction_)(value, id));
}
} }
} }
} }
} }
return false; return false; // command not found
} }
// add a command to the list // add a command to the list, which does not return json
void Command::add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) { void Command::add(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) {
cmdfunctions_.emplace_back(device_type, device_id, cmd, cb); cmdfunctions_.emplace_back(device_type, cmd, cb, nullptr);
// see if we need to subscribe // see if we need to subscribe
Mqtt::register_command(device_type, device_id, cmd, cb); Mqtt::register_command(device_type, device_id, cmd, cb);
} }
// add a command to the list, which does return json object as output
void Command::add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb) {
cmdfunctions_.emplace_back(device_type, cmd, nullptr, cb);
}
// see if a command exists for that device type // see if a command exists for that device type
bool Command::find(const uint8_t device_type, const char * cmd) { bool Command::find(const uint8_t device_type, const char * cmd) {
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
@@ -70,7 +87,7 @@ bool Command::find(const uint8_t device_type, const char * cmd) {
} }
// output list of all commands to console for a specific DeviceType // output list of all commands to console for a specific DeviceType
void Command::show_commands(uuid::console::Shell & shell, uint8_t device_type) { void Command::show(uuid::console::Shell & shell, uint8_t 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) {
shell.printf("%s ", uuid::read_flash_string(cf.cmd_).c_str()); shell.printf("%s ", uuid::read_flash_string(cf.cmd_).c_str());
@@ -81,9 +98,9 @@ void Command::show_commands(uuid::console::Shell & shell, uint8_t device_type) {
// determines the device_type from the shell context we're in // determines the device_type from the shell context we're in
uint8_t Command::context_2_device_type(unsigned int context) { uint8_t Command::context_2_device_type(unsigned int context) {
if (context == ShellContext::MAIN) { // if (context == ShellContext::MAIN) {
return EMSdevice::DeviceType::SERVICEKEY; // return EMSdevice::DeviceType::SERVICEKEY;
} // }
if (context == ShellContext::BOILER) { if (context == ShellContext::BOILER) {
return EMSdevice::DeviceType::BOILER; return EMSdevice::DeviceType::BOILER;
} }
@@ -99,34 +116,36 @@ uint8_t Command::context_2_device_type(unsigned int context) {
if (context == ShellContext::THERMOSTAT) { if (context == ShellContext::THERMOSTAT) {
return EMSdevice::DeviceType::THERMOSTAT; return EMSdevice::DeviceType::THERMOSTAT;
} }
if (context == ShellContext::SENSOR) {
return EMSdevice::DeviceType::SENSOR;
}
return EMSdevice::DeviceType::UNKNOWN; // unknown type return EMSdevice::DeviceType::UNKNOWN; // unknown type
} }
// show command per current context // show command per current context
void Command::show_commands(uuid::console::Shell & shell) { void Command::show(uuid::console::Shell & shell) {
show_commands(shell, context_2_device_type(shell.context())); show(shell, context_2_device_type(shell.context()));
} }
// output list of all commands to console // output list of all commands to console
void Command::show_all_commands(uuid::console::Shell & shell) { void Command::show_all(uuid::console::Shell & shell) {
// show system first // show system first
shell.printf("%s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SERVICEKEY).c_str()); shell.printf("%s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SERVICEKEY).c_str());
show_commands(shell, EMSdevice::DeviceType::SERVICEKEY); show(shell, EMSdevice::DeviceType::SERVICEKEY);
// do this in the order of factory classes to keep a consistent order when displaying // do this in the order of factory classes to keep a consistent order when displaying
for (const auto & device_class : EMSFactory::device_handlers()) { for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : EMSESP::emsdevices) { for (const auto & emsdevice : EMSESP::emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
shell.printf("%s: ", EMSdevice::device_type_2_device_name(device_class.first).c_str()); shell.printf("%s: ", EMSdevice::device_type_2_device_name(device_class.first).c_str());
show_commands(shell, device_class.first); show(shell, device_class.first);
} }
} }
} }
} }
// given a context, automatically add the commands to the console // Add the console 'call' command to the given context
void Command::add_context_commands(unsigned int context) { void Command::add_context_commands(unsigned int context) {
// if we're adding commands for a thermostat or mixing, then include an additional optional paramter called heating circuit // if we're adding commands for a thermostat or mixing, then include an additional optional paramter called heating circuit
flash_string_vector params; flash_string_vector params;
@@ -147,7 +166,7 @@ void Command::add_context_commands(unsigned int context) {
if (arguments.empty()) { if (arguments.empty()) {
// list options // list options
shell.print("Available commands: "); shell.print("Available commands: ");
show_commands(shell); show(shell);
shell.println(); shell.println();
return; return;
} }
@@ -155,16 +174,35 @@ void Command::add_context_commands(unsigned int context) {
// determine the device_type from the shell context // determine the device_type from the shell context
uint8_t device_type = context_2_device_type(shell.context()); uint8_t device_type = context_2_device_type(shell.context());
// validate the command
const char * cmd = arguments[0].c_str(); const char * cmd = arguments[0].c_str();
if (!find(device_type, cmd)) {
shell.print(F("Unknown command. Available commands are: "));
show(shell);
shell.println();
return;
}
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
JsonObject output = doc.to<JsonObject>();
bool ok = false;
if (arguments.size() == 1) { if (arguments.size() == 1) {
// no value specified // no value specified, just the cmd
(void)Command::call_command(device_type, cmd, nullptr, -1); ok = Command::call(device_type, cmd, nullptr, -1, output);
} else if (arguments.size() == 2) { } else if (arguments.size() == 2) {
// has a value but no id // has a value but no id
(void)Command::call_command(device_type, cmd, arguments.back().c_str(), -1); ok = Command::call(device_type, cmd, arguments.back().c_str(), -1, output);
} else { } else {
// use value, which could be an id or hc // use value, which could be an id or hc
(void)Command::call_command(device_type, cmd, arguments[1].c_str(), atoi(arguments[2].c_str())); ok = Command::call(device_type, cmd, arguments[1].c_str(), atoi(arguments[2].c_str()), output);
}
if (ok) {
shell.print(F("output: "));
serializeJson(doc, shell);
shell.println();
shell.println();
} }
}, },
[&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> { [&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> {
@@ -182,5 +220,4 @@ void Command::add_context_commands(unsigned int context) {
}); });
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -34,21 +34,22 @@ using uuid::console::Shell;
namespace emsesp { namespace emsesp {
using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>; using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
using cmdfunction_json_p = std::function<bool(const char * data, const int8_t id, JsonObject & output)>;
class Command { class Command {
public: public:
struct CmdFunction { struct CmdFunction {
uint8_t device_type_; // DeviceType:: uint8_t device_type_; // DeviceType::
uint8_t device_id_;
const __FlashStringHelper * cmd_; const __FlashStringHelper * cmd_;
cmdfunction_p cmdfunction_; cmdfunction_p cmdfunction_;
cmdfunction_json_p cmdfunction_json_;
CmdFunction(uint8_t device_type, uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cmdfunction) CmdFunction(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cmdfunction, cmdfunction_json_p cmdfunction_json)
: device_type_(device_type) : device_type_(device_type)
, device_id_(device_id)
, cmd_(cmd) , cmd_(cmd)
, cmdfunction_(cmdfunction) { , cmdfunction_(cmdfunction)
, cmdfunction_json_(cmdfunction_json) {
} }
}; };
@@ -56,10 +57,11 @@ class Command {
return cmdfunctions_; return cmdfunctions_;
} }
static bool call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id); static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & output);
static void add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb); static void add(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb);
static void show_all_commands(uuid::console::Shell & shell); static void add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb);
static void show_commands(uuid::console::Shell & shell); static void show_all(uuid::console::Shell & shell);
static void show(uuid::console::Shell & shell);
static void add_context_commands(unsigned int context); static void add_context_commands(unsigned int context);
static bool find(const uint8_t device_type, const char * cmd); static bool find(const uint8_t device_type, const char * cmd);
@@ -68,7 +70,7 @@ class Command {
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
static void show_commands(uuid::console::Shell & shell, uint8_t device_type); static void show(uuid::console::Shell & shell, uint8_t device_type);
static uint8_t context_2_device_type(unsigned int context); static uint8_t context_2_device_type(unsigned int context);
}; };

View File

@@ -153,7 +153,7 @@ void EMSESPShell::add_console_commands() {
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show), F_(commands)}, flash_string_vector{F_(show), F_(commands)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Command::show_all_commands(shell); }); [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Command::show_all(shell); });
commands->add_command( commands->add_command(
ShellContext::MAIN, ShellContext::MAIN,
@@ -466,8 +466,10 @@ void Console::load_standard_commands(unsigned int context) {
// load the commands (console & mqtt topics) for this specific context // load the commands (console & mqtt topics) for this specific context
Command::add_context_commands(context); // unless it's main (the root)
if (context != ShellContext::MAIN) {
Command::add_context_commands(context);
}
} }
// prompt, change per context // prompt, change per context
@@ -485,6 +487,9 @@ std::string EMSESPShell::context_text() {
case ShellContext::THERMOSTAT: case ShellContext::THERMOSTAT:
return std::string{"/thermostat"}; return std::string{"/thermostat"};
case ShellContext::SENSOR:
return std::string{"/sensor"};
default: default:
return std::string{}; return std::string{};
} }
@@ -577,7 +582,6 @@ void Console::start() {
shell->log_level(uuid::log::Level::DEBUG); shell->log_level(uuid::log::Level::DEBUG);
#endif #endif
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
// always start in su/admin mode when running tests // always start in su/admin mode when running tests
shell->add_flags(CommandFlags::ADMIN); shell->add_flags(CommandFlags::ADMIN);

View File

@@ -79,7 +79,8 @@ enum ShellContext : uint8_t {
BOILER, BOILER,
THERMOSTAT, THERMOSTAT,
SOLAR, SOLAR,
MIXING MIXING,
SENSOR
}; };

View File

@@ -69,6 +69,12 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { return set_burn_period(value, id); }); register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { return set_burn_period(value, id); });
register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { return set_pump_delay(value, id); }); register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { return set_pump_delay(value, id); });
// API call
// Command::add_with_json(this->device_type(), F("info"), Boiler::command_info);
Command::add_with_json(this->device_type(), F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha mqtt_format_ = settings.mqtt_format; // single, nested or ha
@@ -140,208 +146,217 @@ void Boiler::device_info_web(JsonArray & root) {
render_value_json(root, "", F("Heat Pump modulation"), pumpMod2_, F_(percent)); render_value_json(root, "", F("Heat Pump modulation"), pumpMod2_, F_(percent));
} }
bool Boiler::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(output));
}
// creates JSON doc from values
// returns false if empty
bool Boiler::export_values(JsonObject & output) {
char s[10]; // for formatting strings
if (Helpers::hasValue(wWComfort_)) {
if (wWComfort_ == 0x00) {
output["wWComfort"] = "Hot";
} else if (wWComfort_ == 0xD8) {
output["wWComfort"] = "Eco";
} else if (wWComfort_ == 0xEC) {
output["wWComfort"] = "Intelligent";
}
}
if (Helpers::hasValue(wWSelTemp_)) {
output["wWSelTemp"] = wWSelTemp_;
}
if (Helpers::hasValue(wWSetTmp_)) {
output["wWSetTemp"] = wWSetTmp_;
}
if (Helpers::hasValue(wWDisinfectTemp_)) {
output["wWDisinfectionTemp"] = wWDisinfectTemp_;
}
if (Helpers::hasValue(selFlowTemp_)) {
output["selFlowTemp"] = selFlowTemp_;
}
if (Helpers::hasValue(selBurnPow_)) {
output["selBurnPow"] = selBurnPow_;
}
if (Helpers::hasValue(curBurnPow_)) {
output["curBurnPow"] = curBurnPow_;
}
if (Helpers::hasValue(pumpMod_)) {
output["pumpMod"] = pumpMod_;
}
if (Helpers::hasValue(pumpMod2_)) {
output["pumpMod2"] = pumpMod2_;
}
if (wWType_ == 0) { // no output if not set
output["wWType"] = F("off");
} else if (wWType_ == 1) {
output["wWType"] = F("flow");
} else if (wWType_ == 2) {
output["wWType"] = F("buffered flow");
} else if (wWType_ == 3) {
output["wWType"] = F("buffer");
} else if (wWType_ == 4) {
output["wWType"] = F("layered buffer");
}
if (Helpers::hasValue(wWChargeType_, EMS_VALUE_BOOL)) {
output["wWChargeType"] = wWChargeType_ ? "valve" : "pump";
}
if (Helpers::hasValue(wWCircPump_, EMS_VALUE_BOOL)) {
output["wWCircPump"] = Helpers::render_value(s, wWCircPump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWCircPumpMode_)) {
output["wWCiPuMode"] = wWCircPumpMode_;
}
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
output["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(extTemp_)) {
output["outdoorTemp"] = (float)extTemp_ / 10;
}
if (Helpers::hasValue(wWCurTmp_)) {
output["wWCurTmp"] = (float)wWCurTmp_ / 10;
}
if (Helpers::hasValue(wWCurTmp2_)) {
output["wWCurTmp2"] = (float)wWCurTmp2_ / 10;
}
if (Helpers::hasValue(wWCurFlow_)) {
output["wWCurFlow"] = (float)wWCurFlow_ / 10;
}
if (Helpers::hasValue(curFlowTemp_)) {
output["curFlowTemp"] = (float)curFlowTemp_ / 10;
}
if (Helpers::hasValue(retTemp_)) {
output["retTemp"] = (float)retTemp_ / 10;
}
if (Helpers::hasValue(switchTemp_)) {
output["switchTemp"] = (float)switchTemp_ / 10;
}
if (Helpers::hasValue(sysPress_)) {
output["sysPress"] = (float)sysPress_ / 10;
}
if (Helpers::hasValue(boilTemp_)) {
output["boilTemp"] = (float)boilTemp_ / 10;
}
if (Helpers::hasValue(wwStorageTemp1_)) {
output["wwStorageTemp1"] = (float)wwStorageTemp1_ / 10;
}
if (Helpers::hasValue(wwStorageTemp2_)) {
output["wwStorageTemp2"] = (float)wwStorageTemp2_ / 10;
}
if (Helpers::hasValue(exhaustTemp_)) {
output["exhaustTemp"] = (float)exhaustTemp_ / 10;
}
if (Helpers::hasValue(wWActivated_, EMS_VALUE_BOOL)) {
output["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) {
output["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWDisinfecting_, EMS_VALUE_BOOL)) {
output["wWDisinfecting"] = Helpers::render_value(s, wWDisinfecting_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) {
output["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWRecharging_, EMS_VALUE_BOOL)) {
output["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWTemperatureOK_, EMS_VALUE_BOOL)) {
output["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
output["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(burnGas_, EMS_VALUE_BOOL)) {
output["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(flameCurr_)) {
output["flameCurr"] = (float)(int16_t)flameCurr_ / 10;
}
if (Helpers::hasValue(heatPmp_, EMS_VALUE_BOOL)) {
output["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(fanWork_, EMS_VALUE_BOOL)) {
output["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(ignWork_, EMS_VALUE_BOOL)) {
output["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWHeat_, EMS_VALUE_BOOL)) {
output["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(heating_activated_, EMS_VALUE_BOOL)) {
output["heatingActivated"] = Helpers::render_value(s, heating_activated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(heating_temp_)) {
output["heatingTemp"] = heating_temp_;
}
if (Helpers::hasValue(pump_mod_max_)) {
output["pumpModMax"] = pump_mod_max_;
}
if (Helpers::hasValue(pump_mod_min_)) {
output["pumpModMin"] = pump_mod_min_;
}
if (Helpers::hasValue(pumpDelay_)) {
output["pumpDelay"] = pumpDelay_;
}
if (Helpers::hasValue(burnPeriod_)) {
output["burnMinPeriod"] = burnPeriod_;
}
if (Helpers::hasValue(burnPowermin_)) {
output["burnMinPower"] = burnPowermin_;
}
if (Helpers::hasValue(burnPowermax_)) {
output["burnMaxPower"] = burnPowermax_;
}
if (Helpers::hasValue(boilTemp_on_)) {
output["boilHystOn"] = boilTemp_on_;
}
if (Helpers::hasValue(boilTemp_off_)) {
output["boilHystOff"] = boilTemp_off_;
}
if (Helpers::hasValue(setFlowTemp_)) {
output["setFlowTemp"] = setFlowTemp_;
}
if (Helpers::hasValue(setWWPumpPow_)) {
output["wWSetPumpPower"] = setWWPumpPow_;
}
if (Helpers::hasValue(wWStarts_)) {
output["wWStarts"] = wWStarts_;
}
if (Helpers::hasValue(wWWorkM_)) {
output["wWWorkM"] = wWWorkM_;
}
if (Helpers::hasValue(UBAuptime_)) {
output["UBAuptime"] = UBAuptime_;
}
if (Helpers::hasValue(burnStarts_)) {
output["burnStarts"] = burnStarts_;
}
if (Helpers::hasValue(burnWorkMin_)) {
output["burnWorkMin"] = burnWorkMin_;
}
if (Helpers::hasValue(heatWorkMin_)) {
output["heatWorkMin"] = heatWorkMin_;
}
if (Helpers::hasValue(serviceCode_)) {
output["serviceCode"] = serviceCodeChar_;
output["serviceCodeNumber"] = serviceCode_;
}
return (output.size());
}
// publish values via MQTT // publish values via MQTT
void Boiler::publish_values() { void Boiler::publish_values() {
// const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/ // const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
// DynamicJsonDocument doc(capacity); // DynamicJsonDocument doc(capacity);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject output = doc.to<JsonObject>();
char s[10]; // for formatting strings if (export_values(output)) {
Mqtt::publish(F("boiler_data"), doc.as<JsonObject>());
if (Helpers::hasValue(wWComfort_)) {
if (wWComfort_ == 0x00) {
doc["wWComfort"] = "Hot";
} else if (wWComfort_ == 0xD8) {
doc["wWComfort"] = "Eco";
} else if (wWComfort_ == 0xEC) {
doc["wWComfort"] = "Intelligent";
}
}
if (Helpers::hasValue(wWSelTemp_)) {
doc["wWSelTemp"] = wWSelTemp_;
}
if (Helpers::hasValue(wWSetTmp_)) {
doc["wWSetTemp"] = wWSetTmp_;
}
if (Helpers::hasValue(wWDisinfectTemp_)) {
doc["wWDisinfectionTemp"] = wWDisinfectTemp_;
}
if (Helpers::hasValue(selFlowTemp_)) {
doc["selFlowTemp"] = selFlowTemp_;
}
if (Helpers::hasValue(selBurnPow_)) {
doc["selBurnPow"] = selBurnPow_;
}
if (Helpers::hasValue(curBurnPow_)) {
doc["curBurnPow"] = curBurnPow_;
}
if (Helpers::hasValue(pumpMod_)) {
doc["pumpMod"] = pumpMod_;
}
if (Helpers::hasValue(pumpMod2_)) {
doc["pumpMod2"] = pumpMod2_;
}
if (wWType_ == 0) { // no output if not set
doc["wWType"] = F("off");
} else if (wWType_ == 1) {
doc["wWType"] = F("flow");
} else if (wWType_ == 2) {
doc["wWType"] = F("buffered flow");
} else if (wWType_ == 3) {
doc["wWType"] = F("buffer");
} else if (wWType_ == 4) {
doc["wWType"] = F("layered buffer");
}
if (Helpers::hasValue(wWChargeType_, EMS_VALUE_BOOL)) {
doc["wWChargeType"] = wWChargeType_ ? "valve" : "pump";
}
if (Helpers::hasValue(wWCircPump_, EMS_VALUE_BOOL)) {
doc["wWCircPump"] = Helpers::render_value(s, wWCircPump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWCircPumpMode_)) {
doc["wWCiPuMode"] = wWCircPumpMode_;
}
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(extTemp_)) {
doc["outdoorTemp"] = (float)extTemp_ / 10;
}
if (Helpers::hasValue(wWCurTmp_)) {
doc["wWCurTmp"] = (float)wWCurTmp_ / 10;
}
if (Helpers::hasValue(wWCurTmp2_)) {
doc["wWCurTmp2"] = (float)wWCurTmp2_ / 10;
}
if (Helpers::hasValue(wWCurFlow_)) {
doc["wWCurFlow"] = (float)wWCurFlow_ / 10;
}
if (Helpers::hasValue(curFlowTemp_)) {
doc["curFlowTemp"] = (float)curFlowTemp_ / 10;
}
if (Helpers::hasValue(retTemp_)) {
doc["retTemp"] = (float)retTemp_ / 10;
}
if (Helpers::hasValue(switchTemp_)) {
doc["switchTemp"] = (float)switchTemp_ / 10;
}
if (Helpers::hasValue(sysPress_)) {
doc["sysPress"] = (float)sysPress_ / 10;
}
if (Helpers::hasValue(boilTemp_)) {
doc["boilTemp"] = (float)boilTemp_ / 10;
}
if (Helpers::hasValue(wwStorageTemp1_)) {
doc["wwStorageTemp1"] = (float)wwStorageTemp1_ / 10;
}
if (Helpers::hasValue(wwStorageTemp2_)) {
doc["wwStorageTemp2"] = (float)wwStorageTemp2_ / 10;
}
if (Helpers::hasValue(exhaustTemp_)) {
doc["exhaustTemp"] = (float)exhaustTemp_ / 10;
}
if (Helpers::hasValue(wWActivated_, EMS_VALUE_BOOL)) {
doc["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) {
doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWDisinfecting_, EMS_VALUE_BOOL)) {
doc["wWDisinfecting"] = Helpers::render_value(s, wWDisinfecting_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) {
doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWRecharging_, EMS_VALUE_BOOL)) {
doc["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWTemperatureOK_, EMS_VALUE_BOOL)) {
doc["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(burnGas_, EMS_VALUE_BOOL)) {
doc["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(flameCurr_)) {
doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10;
}
if (Helpers::hasValue(heatPmp_, EMS_VALUE_BOOL)) {
doc["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(fanWork_, EMS_VALUE_BOOL)) {
doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(ignWork_, EMS_VALUE_BOOL)) {
doc["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWHeat_, EMS_VALUE_BOOL)) {
doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(heating_activated_, EMS_VALUE_BOOL)) {
doc["heatingActivated"] = Helpers::render_value(s, heating_activated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(heating_temp_)) {
doc["heatingTemp"] = heating_temp_;
}
if (Helpers::hasValue(pump_mod_max_)) {
doc["pumpModMax"] = pump_mod_max_;
}
if (Helpers::hasValue(pump_mod_min_)) {
doc["pumpModMin"] = pump_mod_min_;
}
if (Helpers::hasValue(pumpDelay_)) {
doc["pumpDelay"] = pumpDelay_;
}
if (Helpers::hasValue(burnPeriod_)) {
doc["burnMinPeriod"] = burnPeriod_;
}
if (Helpers::hasValue(burnPowermin_)) {
doc["burnMinPower"] = burnPowermin_;
}
if (Helpers::hasValue(burnPowermax_)) {
doc["burnMaxPower"] = burnPowermax_;
}
if (Helpers::hasValue(boilTemp_on_)) {
doc["boilHystOn"] = boilTemp_on_;
}
if (Helpers::hasValue(boilTemp_off_)) {
doc["boilHystOff"] = boilTemp_off_;
}
if (Helpers::hasValue(setFlowTemp_)) {
doc["setFlowTemp"] = setFlowTemp_;
}
if (Helpers::hasValue(setWWPumpPow_)) {
doc["wWSetPumpPower"] = setWWPumpPow_;
}
if (Helpers::hasValue(wWStarts_)) {
doc["wWStarts"] = wWStarts_;
}
if (Helpers::hasValue(wWWorkM_)) {
doc["wWWorkM"] = wWWorkM_;
}
if (Helpers::hasValue(UBAuptime_)) {
doc["UBAuptime"] = UBAuptime_;
}
if (Helpers::hasValue(burnStarts_)) {
doc["burnStarts"] = burnStarts_;
}
if (Helpers::hasValue(burnWorkMin_)) {
doc["burnWorkMin"] = burnWorkMin_;
}
if (Helpers::hasValue(heatWorkMin_)) {
doc["heatWorkMin"] = heatWorkMin_;
}
if (Helpers::hasValue(serviceCode_)) {
doc["serviceCode"] = serviceCodeChar_;
doc["serviceCodeNumber"] = serviceCode_;
}
// if we have data, publish it
if (!doc.isNull()) {
Mqtt::publish(F("boiler_data"), doc);
} }
} }
@@ -801,6 +816,7 @@ bool Boiler::set_heating_activated(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, v)) { if (!Helpers::value2bool(value, v)) {
return false; return false;
} }
LOG_INFO(F("Setting boiler heating "), v ? "on" : "off"); LOG_INFO(F("Setting boiler heating "), v ? "on" : "off");
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
write_command(EMS_TYPE_UBAParametersPlus, 0, v ? 0x01 : 0, EMS_TYPE_UBAParametersPlus); write_command(EMS_TYPE_UBAParametersPlus, 0, v ? 0x01 : 0, EMS_TYPE_UBAParametersPlus);

View File

@@ -50,6 +50,7 @@ class Boiler : public EMSdevice {
void console_commands(Shell & shell, unsigned int context); void console_commands(Shell & shell, unsigned int context);
void register_mqtt_ha_config(); void register_mqtt_ha_config();
void check_active(); void check_active();
bool export_values(JsonObject & doc);
uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off
uint8_t mqtt_format_; // single, nested or ha uint8_t mqtt_format_; // single, nested or ha
@@ -76,7 +77,7 @@ class Boiler : public EMSdevice {
// MC10Status // MC10Status
uint16_t wwMixTemperature_ = EMS_VALUE_USHORT_NOTSET; // mengertemperatuur uint16_t wwMixTemperature_ = EMS_VALUE_USHORT_NOTSET; // mengertemperatuur
uint16_t wwBufferBoilerTemperature_ = EMS_VALUE_USHORT_NOTSET; // bufferboilertemperatuur uint16_t wwBufferBoilerTemperature_ = EMS_VALUE_USHORT_NOTSET; // bufferboilertemperature
// UBAMonitorFast - 0x18 on EMS1 // UBAMonitorFast - 0x18 on EMS1
uint8_t selFlowTemp_ = EMS_VALUE_UINT_NOTSET; // Selected flow temperature uint8_t selFlowTemp_ = EMS_VALUE_UINT_NOTSET; // Selected flow temperature
@@ -146,6 +147,8 @@ class Boiler : public EMSdevice {
uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps) uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
bool command_info(const char * value, const int8_t id, JsonObject & output);
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram); void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram); void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram); void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);

View File

@@ -53,10 +53,24 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) { if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
register_telegram_type(0x010C, F("IPMSetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_IPMStatusMessage(t); }); register_telegram_type(0x010C, F("IPMSetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_IPMStatusMessage(t); });
} }
// API call
Command::add_with_json(this->device_type(), F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
} }
// add context submenu // add context submenu
void Mixing::add_context_menu() { void Mixing::add_context_menu() {
// TODO support for multiple mixing units from a single menu, similar to set master with thermostat
/*
EMSESPShell::commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(mixing)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Mixing::console_commands(shell, ShellContext::MIXING);
});
*/
} }
// output json to web UI // output json to web UI
@@ -89,7 +103,24 @@ bool Mixing::updated_values() {
} }
// add console commands // add console commands
void Mixing::console_commands() { void Mixing::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::MIXING,
CommandFlags::ADMIN,
flash_string_vector{F_(read)},
flash_string_vector{F_(typeid_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint16_t type_id = Helpers::hextoint(arguments.front().c_str());
EMSESP::set_read_id(type_id);
EMSESP::send_read_request(type_id, device_id());
});
EMSESPShell::commands->add_command(ShellContext::MIXING,
CommandFlags::USER,
flash_string_vector{F_(show)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_values(shell); });
// enter the context
Console::enter_custom_context(shell, context);
} }
// display all values into the shell console // display all values into the shell console
@@ -117,49 +148,66 @@ void Mixing::show_values(uuid::console::Shell & shell) {
shell.println(); shell.println();
} }
bool Mixing::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(output));
}
// publish values via MQTT // publish values via MQTT
// ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually // ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually
void Mixing::publish_values() { void Mixing::publish_values() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
char s[5]; // for formatting strings JsonObject output = doc.to<JsonObject>();
if (export_values(output)) {
char topic[30];
char s[5];
strlcpy(topic, "mixing_data", 30);
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
Mqtt::publish(topic, doc.as<JsonObject>());
}
}
// creates JSON doc from values
// returns false if empty
bool Mixing::export_values(JsonObject & output) {
char s[5]; // for formatting strings
switch (type_) { switch (type_) {
case Type::HC: case Type::HC:
doc["type"] = "hc"; output["type"] = "hc";
if (Helpers::hasValue(flowTemp_)) { if (Helpers::hasValue(flowTemp_)) {
doc["flowTemp"] = (float)flowTemp_ / 10; output["flowTemp"] = (float)flowTemp_ / 10;
} }
if (Helpers::hasValue(flowSetTemp_)) { if (Helpers::hasValue(flowSetTemp_)) {
doc["flowSetTemp"] = flowSetTemp_; output["flowSetTemp"] = flowSetTemp_;
} }
if (Helpers::hasValue(pump_)) { if (Helpers::hasValue(pump_)) {
doc["pumpStatus"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL); output["pumpStatus"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(status_)) { if (Helpers::hasValue(status_)) {
doc["valveStatus"] = status_; output["valveStatus"] = status_;
} }
break; break;
case Type::WWC: case Type::WWC:
doc["type"] = "wwc"; output["type"] = "wwc";
if (Helpers::hasValue(flowTemp_)) { if (Helpers::hasValue(flowTemp_)) {
doc["wwTemp"] = (float)flowTemp_ / 10; output["wwTemp"] = (float)flowTemp_ / 10;
} }
if (Helpers::hasValue(pump_)) { if (Helpers::hasValue(pump_)) {
doc["pumpStatus"] = pump_; output["pumpStatus"] = pump_;
} }
if (Helpers::hasValue(status_)) { if (Helpers::hasValue(status_)) {
doc["tempStatus"] = status_; output["tempStatus"] = status_;
} }
break; break;
case Type::NONE: case Type::NONE:
default: default:
return; return false;
break;
} }
char topic[30]; return output.size();
strlcpy(topic, "mixing_data", 30);
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
Mqtt::publish(topic, doc);
} }
// heating circuits 0x02D7, 0x02D8 etc... // heating circuits 0x02D7, 0x02D8 etc...

View File

@@ -25,6 +25,7 @@
#include <uuid/log.h> #include <uuid/log.h>
#include "emsdevice.h" #include "emsdevice.h"
#include "emsesp.h"
#include "telegram.h" #include "telegram.h"
#include "helpers.h" #include "helpers.h"
#include "mqtt.h" #include "mqtt.h"
@@ -44,7 +45,9 @@ class Mixing : public EMSdevice {
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void console_commands(); void console_commands(Shell & shell, unsigned int context);
bool export_values(JsonObject & doc);
bool command_info(const char * value, const int8_t id, JsonObject & output);
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram); void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram); void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);
@@ -66,7 +69,8 @@ class Mixing : public EMSdevice {
int8_t status_ = EMS_VALUE_UINT_NOTSET; int8_t status_ = EMS_VALUE_UINT_NOTSET;
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET; uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
Type type_ = Type::NONE; Type type_ = Type::NONE;
bool changed_ = false;
bool changed_ = false;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -47,10 +47,28 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_ISM1StatusMessage(t); }); register_telegram_type(0x0103, F("ISM1StatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_ISM1StatusMessage(t); });
register_telegram_type(0x0101, F("ISM1Set"), false, [&](std::shared_ptr<const Telegram> t) { process_ISM1Set(t); }); register_telegram_type(0x0101, F("ISM1Set"), false, [&](std::shared_ptr<const Telegram> t) { process_ISM1Set(t); });
} }
// API call
Command::add_with_json(this->device_type(), F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
}
bool Solar::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(output));
} }
// context submenu // context submenu
void Solar::add_context_menu() { void Solar::add_context_menu() {
// TODO support for multiple solar units from a single menu, similar to set master with thermostat
/*
EMSESPShell::commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(solar)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Solar::console_commands(shell, ShellContext::SOLAR);
});
*/
} }
// print to web // print to web
@@ -109,69 +127,74 @@ void Solar::show_values(uuid::console::Shell & shell) {
// publish values via MQTT // publish values via MQTT
void Solar::publish_values() { void Solar::publish_values() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject output = doc.to<JsonObject>();
if (export_values(output)) {
Mqtt::publish(F("sm_data"), doc.as<JsonObject>());
}
}
// creates JSON doc from values
// returns false if empty
bool Solar::export_values(JsonObject & output) {
char s[10]; // for formatting strings char s[10]; // for formatting strings
if (Helpers::hasValue(collectorTemp_)) { if (Helpers::hasValue(collectorTemp_)) {
doc["collectorTemp"] = (float)collectorTemp_ / 10; output["collectorTemp"] = (float)collectorTemp_ / 10;
} }
if (Helpers::hasValue(tankBottomTemp_)) { if (Helpers::hasValue(tankBottomTemp_)) {
doc["tankBottomTemp"] = (float)tankBottomTemp_ / 10; output["tankBottomTemp"] = (float)tankBottomTemp_ / 10;
} }
if (Helpers::hasValue(tankBottomTemp2_)) { if (Helpers::hasValue(tankBottomTemp2_)) {
doc["tankBottomTemp2"] = (float)tankBottomTemp2_ / 10; output["tankBottomTemp2"] = (float)tankBottomTemp2_ / 10;
} }
if (Helpers::hasValue(heatExchangerTemp_)) { if (Helpers::hasValue(heatExchangerTemp_)) {
doc["heatExchangerTemp"] = (float)heatExchangerTemp_ / 10; output["heatExchangerTemp"] = (float)heatExchangerTemp_ / 10;
} }
if (Helpers::hasValue(solarPumpModulation_)) { if (Helpers::hasValue(solarPumpModulation_)) {
doc["solarPumpModulation"] = solarPumpModulation_; output["solarPumpModulation"] = solarPumpModulation_;
} }
if (Helpers::hasValue(cylinderPumpModulation_)) { if (Helpers::hasValue(cylinderPumpModulation_)) {
doc["cylinderPumpModulation"] = cylinderPumpModulation_; output["cylinderPumpModulation"] = cylinderPumpModulation_;
} }
if (Helpers::hasValue(solarPump_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(solarPump_, EMS_VALUE_BOOL)) {
doc["solarPump"] = Helpers::render_value(s, solarPump_, EMS_VALUE_BOOL); output["solarPump"] = Helpers::render_value(s, solarPump_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(valveStatus_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(valveStatus_, EMS_VALUE_BOOL)) {
doc["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL); output["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(pumpWorkMin_)) { if (Helpers::hasValue(pumpWorkMin_)) {
doc["pumpWorkMin"] = pumpWorkMin_; output["pumpWorkMin"] = pumpWorkMin_;
} }
if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) {
doc["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL); output["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(collectorShutdown_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(collectorShutdown_, EMS_VALUE_BOOL)) {
doc["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL); output["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(energyLastHour_)) { if (Helpers::hasValue(energyLastHour_)) {
doc["energyLastHour"] = (float)energyLastHour_ / 10; output["energyLastHour"] = (float)energyLastHour_ / 10;
} }
if (Helpers::hasValue(energyToday_)) { if (Helpers::hasValue(energyToday_)) {
doc["energyToday"] = energyToday_; output["energyToday"] = energyToday_;
} }
if (Helpers::hasValue(energyTotal_)) { if (Helpers::hasValue(energyTotal_)) {
doc["energyTotal"] = (float)energyTotal_ / 10; output["energyTotal"] = (float)energyTotal_ / 10;
} }
// if we have data, publish it return output.size();
if (!doc.isNull()) {
Mqtt::publish(F("sm_data"), doc);
}
} }
// check to see if values have been updated // check to see if values have been updated
@@ -184,7 +207,24 @@ bool Solar::updated_values() {
} }
// add console commands // add console commands
void Solar::console_commands() { void Solar::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::SOLAR,
CommandFlags::ADMIN,
flash_string_vector{F_(read)},
flash_string_vector{F_(typeid_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint16_t type_id = Helpers::hextoint(arguments.front().c_str());
EMSESP::set_read_id(type_id);
EMSESP::send_read_request(type_id, device_id());
});
EMSESPShell::commands->add_command(ShellContext::SOLAR,
CommandFlags::USER,
flash_string_vector{F_(show)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_values(shell); });
// enter the context
Console::enter_custom_context(shell, context);
} }
// SM10Monitor - type 0x97 // SM10Monitor - type 0x97

View File

@@ -25,6 +25,7 @@
#include <uuid/log.h> #include <uuid/log.h>
#include "emsdevice.h" #include "emsdevice.h"
#include "emsesp.h"
#include "telegram.h" #include "telegram.h"
#include "helpers.h" #include "helpers.h"
#include "mqtt.h" #include "mqtt.h"
@@ -44,7 +45,9 @@ class Solar : public EMSdevice {
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void console_commands(); void console_commands(Shell & shell, unsigned int context);
bool export_values(JsonObject & doc);
bool command_info(const char * value, const int8_t id, JsonObject & output);
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1 int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1
int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system) int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system)
@@ -65,7 +68,8 @@ class Solar : public EMSdevice {
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET;
bool changed_ = false;
bool changed_ = false;
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram); void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram); void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);

View File

@@ -248,21 +248,34 @@ bool Thermostat::updated_values() {
return false; return false;
} }
// info API command
// returns the same MQTT publish payload in Nested format
bool Thermostat::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(MQTT_format::NESTED, output));
}
// publish values via MQTT // publish values via MQTT
void Thermostat::publish_values() { void Thermostat::publish_values() {
// only publish on the master thermostat
if (EMSESP::actual_master_thermostat() != this->device_id()) { if (EMSESP::actual_master_thermostat() != this->device_id()) {
return; return;
} }
uint8_t flags = this->model();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject rootThermostat = doc.to<JsonObject>(); JsonObject output = doc.to<JsonObject>();
JsonObject dataThermostat; export_values(mqtt_format_, output);
if (mqtt_format_ == MQTT_format::NESTED) {
Mqtt::publish(F("thermostat_data"), output);
}
}
// creates JSON doc from values
// returns false if empty
bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat) {
uint8_t flags = this->model();
JsonObject dataThermostat;
// add external temp and other stuff specific to the RC30 and RC35 // add external temp and other stuff specific to the RC30 and RC35
// if ((flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) && (mqtt_format_ == MQTT_format::SINGLE || mqtt_format_ == MQTT_format::CUSTOM)) {
if (flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) { if (flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) {
if (datetime_.size()) { if (datetime_.size()) {
rootThermostat["time"] = datetime_.c_str(); rootThermostat["time"] = datetime_.c_str();
@@ -302,9 +315,9 @@ void Thermostat::publish_values() {
} }
// send this specific data using the thermostat_data topic // send this specific data using the thermostat_data topic
if (mqtt_format_ != MQTT_format::NESTED) { if (mqtt_format != MQTT_format::NESTED) {
Mqtt::publish(F("thermostat_data"), doc); Mqtt::publish(F("thermostat_data"), rootThermostat);
rootThermostat = doc.to<JsonObject>(); // clear object rootThermostat.clear(); // clear object
} }
} }
@@ -315,8 +328,7 @@ void Thermostat::publish_values() {
has_data = true; has_data = true;
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n> // if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
// if (mqtt_format_ != MQTT_format::SINGLE) { if ((mqtt_format == MQTT_format::NESTED) || (mqtt_format == MQTT_format::HA)) {
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
char hc_name[10]; // hc{1-4} char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10); strlcpy(hc_name, "hc", 10);
char s[3]; char s[3];
@@ -397,11 +409,11 @@ void Thermostat::publish_values() {
dataThermostat["summertemp"] = hc->summertemp; dataThermostat["summertemp"] = hc->summertemp;
} }
// when using HA always send the mode otherwise it'll may break the component/widget and report an error // mode - always force showing this when in HA so not to break HA's climate component
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == MQTT_format::HA)) { if ((Helpers::hasValue(hc->mode)) || (mqtt_format == MQTT_format::HA)) {
uint8_t hc_mode = hc->get_mode(flags); uint8_t hc_mode = hc->get_mode(flags);
// if we're sending to HA the only valid mode types are heat, auto and off // if we're sending to HA the only valid mode types are heat, auto and off
if (mqtt_format_ == MQTT_format::HA) { if (mqtt_format == MQTT_format::HA) {
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) { if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
hc_mode = HeatingCircuit::Mode::HEAT; hc_mode = HeatingCircuit::Mode::HEAT;
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) { } else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
@@ -425,15 +437,13 @@ void Thermostat::publish_values() {
// if format is single, send immediately and clear object for next hc // if format is single, send immediately and clear object for next hc
// the topic will have the hc number appended // the topic will have the hc number appended
// if (mqtt_format_ == MQTT_format::SINGLE) { if ((mqtt_format == MQTT_format::SINGLE) || (mqtt_format == MQTT_format::CUSTOM)) {
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) {
char topic[30]; char topic[30];
char s[3]; char s[3];
strlcpy(topic, "thermostat_data", 30); strlcpy(topic, "thermostat_data", 30);
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
Mqtt::publish(topic, doc); rootThermostat.clear(); // clear object
rootThermostat = doc.to<JsonObject>(); // clear object } else if (mqtt_format == MQTT_format::HA) {
} else if (mqtt_format_ == MQTT_format::HA) {
// see if we have already registered this with HA MQTT Discovery, if not send the config // see if we have already registered this with HA MQTT Discovery, if not send the config
if (!hc->ha_registered()) { if (!hc->ha_registered()) {
register_mqtt_ha_config(hc->hc_num()); register_mqtt_ha_config(hc->hc_num());
@@ -442,19 +452,12 @@ void Thermostat::publish_values() {
// send the thermostat topic and payload data // send the thermostat topic and payload data
std::string topic(100, '\0'); std::string topic(100, '\0');
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num()); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num());
Mqtt::publish(topic, doc); Mqtt::publish(topic, rootThermostat);
} }
} }
} }
if (!has_data) { return (has_data);
return; // nothing to send, quit
}
// if we're using nested json, send all in one go under one topic called thermostat_data
if (mqtt_format_ == MQTT_format::NESTED) {
Mqtt::publish(F("thermostat_data"), doc);
}
} }
// returns the heating circuit object based on the hc number // returns the heating circuit object based on the hc number
@@ -599,7 +602,7 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc_num); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc_num);
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA // Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
Mqtt::publish_retain(topic, doc, true); // publish the config payload with retain flag Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
// subscribe to the temp and mode commands // subscribe to the temp and mode commands
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/cmd_temp"), hc_num); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/cmd_temp"), hc_num);
@@ -1922,8 +1925,13 @@ bool Thermostat::set_manualtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL); return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL);
} }
// commands for MQTT and Console // API commands for MQTT and Console
void Thermostat::add_commands() { void Thermostat::add_commands() {
// API call
Command::add_with_json(this->device_type(), F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
// if this thermostat doesn't support write, don't add the commands // if this thermostat doesn't support write, don't add the commands
if ((this->flags() & EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) == EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) { if ((this->flags() & EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) == EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) {
return; return;

View File

@@ -108,6 +108,7 @@ class Thermostat : public EMSdevice {
void console_commands(Shell & shell, unsigned int context); void console_commands(Shell & shell, unsigned int context);
void add_commands(); void add_commands();
bool export_values(uint8_t mqtt_format, JsonObject & doc);
// specific thermostat characteristics, stripping the option bits at pos 6 and 7 // specific thermostat characteristics, stripping the option bits at pos 6 and 7
inline uint8_t model() const { inline uint8_t model() const {
@@ -221,6 +222,7 @@ class Thermostat : public EMSdevice {
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num); std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
void register_mqtt_ha_config(uint8_t hc_num); void register_mqtt_ha_config(uint8_t hc_num);
bool command_info(const char * value, const int8_t id, JsonObject & output);
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram); void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_IBASettings(std::shared_ptr<const Telegram> telegram); void process_IBASettings(std::shared_ptr<const Telegram> telegram);

View File

@@ -111,6 +111,10 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
return DeviceType::MIXING; return DeviceType::MIXING;
} }
if (strcmp(topic, "sensor") == 0) {
return DeviceType::SENSOR;
}
return DeviceType::UNKNOWN; return DeviceType::UNKNOWN;
} }
@@ -286,7 +290,7 @@ void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_
// add command to library // add command to library
void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f) { void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f) {
Command::add_command(this->device_type_, this->device_id_, cmd, f); Command::add(this->device_type_, this->device_id_, cmd, f);
} }
// register a call back function for a specific telegram type // register a call back function for a specific telegram type

View File

@@ -229,6 +229,7 @@ class EMSdevice {
enum DeviceType : uint8_t { enum DeviceType : uint8_t {
SERVICEKEY = 0, // this is us (EMS-ESP) SERVICEKEY = 0, // this is us (EMS-ESP)
SENSOR, // for internal dallas sensors
BOILER, BOILER,
THERMOSTAT, THERMOSTAT,
MIXING, MIXING,

View File

@@ -51,7 +51,7 @@ TxService EMSESP::txservice_; // outgoing Telegram Tx handler
Mqtt EMSESP::mqtt_; // mqtt handler Mqtt EMSESP::mqtt_; // mqtt handler
System EMSESP::system_; // core system services System EMSESP::system_; // core system services
Console EMSESP::console_; // telnet and serial console Console EMSESP::console_; // telnet and serial console
Sensors EMSESP::sensors_; // Dallas sensors Sensor EMSESP::sensor_; // Dallas sensors
Shower EMSESP::shower_; // Shower logic Shower EMSESP::shower_; // Shower logic
// static/common variables // static/common variables
@@ -311,8 +311,8 @@ void EMSESP::publish_other_values() {
void EMSESP::publish_sensor_values(const bool force) { void EMSESP::publish_sensor_values(const bool force) {
if (Mqtt::connected()) { if (Mqtt::connected()) {
if (sensors_.updated_values() || force) { if (sensor_.updated_values() || force) {
sensors_.publish_values(); sensor_.publish_values();
} }
} }
} }
@@ -337,7 +337,7 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
doc["value"] = value; doc["value"] = value;
} }
Mqtt::publish(F("response"), doc); Mqtt::publish(F("response"), doc.as<JsonObject>());
} }
// search for recognized device_ids : Me, All, otherwise print hex value // search for recognized device_ids : Me, All, otherwise print hex value
@@ -845,7 +845,7 @@ void EMSESP::start() {
mqtt_.start(); // mqtt init mqtt_.start(); // mqtt init
system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings. system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings.
shower_.start(); // initialize shower timer and shower alert shower_.start(); // initialize shower timer and shower alert
sensors_.start(); // dallas external sensors sensor_.start(); // dallas external sensors
webServer.begin(); // start web server webServer.begin(); // start web server
emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem
@@ -868,7 +868,7 @@ void EMSESP::loop() {
system_.loop(); // does LED and checks system health, and syslog service system_.loop(); // does LED and checks system health, and syslog service
shower_.loop(); // check for shower on/off shower_.loop(); // check for shower on/off
sensors_.loop(); // this will also send out via MQTT sensor_.loop(); // this will also send out via MQTT
mqtt_.loop(); // sends out anything in the queue via MQTT mqtt_.loop(); // sends out anything in the queue via MQTT
console_.loop(); // telnet/serial console console_.loop(); // telnet/serial console
rxservice_.loop(); // process any incoming Rx telegrams rxservice_.loop(); // process any incoming Rx telegrams

View File

@@ -44,7 +44,7 @@
#include "telegram.h" #include "telegram.h"
#include "mqtt.h" #include "mqtt.h"
#include "system.h" #include "system.h"
#include "sensors.h" #include "sensor.h"
#include "console.h" #include "console.h"
#include "shower.h" #include "shower.h"
#include "roomcontrol.h" #include "roomcontrol.h"
@@ -108,8 +108,8 @@ class EMSESP {
static void incoming_telegram(uint8_t * data, const uint8_t length); static void incoming_telegram(uint8_t * data, const uint8_t length);
static const std::vector<Sensors::Device> sensor_devices() { static const std::vector<Sensor::Device> sensor_devices() {
return sensors_.devices(); return sensor_.devices();
} }
enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW }; enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW };
@@ -153,7 +153,7 @@ class EMSESP {
// services // services
static Mqtt mqtt_; static Mqtt mqtt_;
static System system_; static System system_;
static Sensors sensors_; static Sensor sensor_;
static Console console_; static Console console_;
static Shower shower_; static Shower shower_;
static RxService rxservice_; static RxService rxservice_;

View File

@@ -88,7 +88,7 @@ MAKE_PSTR_WORD(connect)
MAKE_PSTR_WORD(heatpump) MAKE_PSTR_WORD(heatpump)
// dallas sensors // dallas sensors
MAKE_PSTR_WORD(sensors) MAKE_PSTR_WORD(sensor)
MAKE_PSTR(kwh, "kWh") MAKE_PSTR(kwh, "kWh")
MAKE_PSTR(wh, "Wh") MAKE_PSTR(wh, "Wh")
@@ -99,7 +99,8 @@ MAKE_PSTR(hostname_fmt, "WiFi Hostname = %s")
MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus") MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus")
MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s") MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s")
MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S") MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S")
MAKE_PSTR(system_heartbeat_fmt, "MQTT Heartbeat is %s") MAKE_PSTR(mqtt_heartbeat_fmt, "MQTT Heartbeat is %s")
MAKE_PSTR(mqtt_format_fmt, "MQTT Format is %d")
MAKE_PSTR(cmd_optional, "[cmd]") MAKE_PSTR(cmd_optional, "[cmd]")
MAKE_PSTR(deep_optional, "[deep]") MAKE_PSTR(deep_optional, "[deep]")
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d") MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")

View File

@@ -28,7 +28,6 @@ AsyncMqttClient * Mqtt::mqttClient_;
std::string Mqtt::hostname_; std::string Mqtt::hostname_;
uint8_t Mqtt::mqtt_qos_; uint8_t Mqtt::mqtt_qos_;
bool Mqtt::mqtt_retain_; bool Mqtt::mqtt_retain_;
uint8_t Mqtt::bus_id_;
uint32_t Mqtt::publish_time_boiler_; uint32_t Mqtt::publish_time_boiler_;
uint32_t Mqtt::publish_time_thermostat_; uint32_t Mqtt::publish_time_thermostat_;
uint32_t Mqtt::publish_time_solar_; uint32_t Mqtt::publish_time_solar_;
@@ -148,9 +147,10 @@ void Mqtt::loop() {
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() ? uuid::read_flash_string(F_(connected)).c_str() : uuid::read_flash_string(F_(disconnected)).c_str());
bool system_heartbeat; EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat = settings.system_heartbeat; }); shell.printfln(F_(mqtt_heartbeat_fmt), settings.system_heartbeat ? F_(enabled) : F_(disabled));
shell.printfln(F_(system_heartbeat_fmt), system_heartbeat ? F_(enabled) : F_(disabled)); shell.printfln(F_(mqtt_format_fmt), settings.mqtt_format);
});
shell.printfln(F("MQTT publish fails count: %lu"), mqtt_publish_fails_); shell.printfln(F("MQTT publish fails count: %lu"), mqtt_publish_fails_);
shell.println(); shell.println();
@@ -253,14 +253,19 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
bool cmd_known = false; bool cmd_known = false;
JsonVariant data = doc["data"]; JsonVariant data = doc["data"];
JsonObject output; // empty object
if (data.is<char *>()) { if (data.is<char *>()) {
cmd_known = Command::call_command(mf.device_type_, command, data.as<char *>(), n); cmd_known = Command::call(mf.device_type_, command, data.as<char *>(), n, output);
} else if (data.is<int>()) { } else if (data.is<int>()) {
char data_str[10]; char data_str[10];
cmd_known = Command::call_command(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), n); cmd_known = Command::call(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), n, output);
} else if (data.is<float>()) { } else if (data.is<float>()) {
char data_str[10]; char data_str[10];
cmd_known = Command::call_command(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n); cmd_known = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n, output);
} else if (data.isNull()) {
cmd_known = Command::call(mf.device_type_, command, "", n, output);
} }
if (!cmd_known) { if (!cmd_known) {
@@ -336,8 +341,6 @@ void Mqtt::start() {
mqtt_retain_ = mqttSettings.mqtt_retain; mqtt_retain_ = mqttSettings.mqtt_retain;
}); });
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { bus_id_ = settings.ems_bus_id; });
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); }); mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) { mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
@@ -443,7 +446,7 @@ void Mqtt::on_connect() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
doc["ip"] = WiFi.localIP().toString(); doc["ip"] = WiFi.localIP().toString();
#endif #endif
publish(F("info"), doc); publish(F("info"), doc.as<JsonObject>());
publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on
@@ -451,10 +454,6 @@ void Mqtt::on_connect() {
resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics
// these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""}
Command::add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("pin"), System::command_pin);
Command::add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("send"), System::command_send);
LOG_INFO(F("MQTT connected")); LOG_INFO(F("MQTT connected"));
} }
@@ -470,12 +469,14 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
std::shared_ptr<MqttMessage> message; std::shared_ptr<MqttMessage> message;
if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0)) { if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0)) {
// leave topic as it is // leave topic as it is
message = std::make_shared<MqttMessage>(operation, topic, std::move(payload), retain); // message = std::make_shared<MqttMessage>(operation, topic, std::move(payload), retain);
message = std::make_shared<MqttMessage>(operation, topic, payload, retain);
} else { } else {
// prefix the hostname // prefix the hostname
std::string full_topic(50, '\0'); std::string full_topic(50, '\0');
snprintf_P(&full_topic[0], full_topic.capacity() + 1, PSTR("%s/%s"), Mqtt::hostname_.c_str(), topic.c_str()); snprintf_P(&full_topic[0], full_topic.capacity() + 1, PSTR("%s/%s"), Mqtt::hostname_.c_str(), topic.c_str());
message = std::make_shared<MqttMessage>(operation, full_topic, std::move(payload), retain); message = std::make_shared<MqttMessage>(operation, full_topic, payload, retain);
} }
// if the queue is full, make room but removing the last one // if the queue is full, make room but removing the last one
@@ -507,7 +508,7 @@ void Mqtt::publish(const __FlashStringHelper * topic, const std::string & payloa
queue_publish_message(uuid::read_flash_string(topic), payload, mqtt_retain_); queue_publish_message(uuid::read_flash_string(topic), payload, mqtt_retain_);
} }
void Mqtt::publish(const __FlashStringHelper * topic, const JsonDocument & payload) { void Mqtt::publish(const __FlashStringHelper * topic, const JsonObject & payload) {
publish(uuid::read_flash_string(topic), payload); publish(uuid::read_flash_string(topic), payload);
} }
@@ -516,17 +517,17 @@ void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string &
queue_publish_message(uuid::read_flash_string(topic), payload, retain); queue_publish_message(uuid::read_flash_string(topic), payload, retain);
} }
void Mqtt::publish_retain(const std::string & topic, const JsonDocument & payload, bool retain) { void Mqtt::publish_retain(const std::string & topic, const JsonObject & payload, bool retain) {
std::string payload_text; std::string payload_text;
serializeJson(payload, payload_text); // convert json to string serializeJson(payload, payload_text); // convert json to string
queue_publish_message(topic, payload_text, retain); queue_publish_message(topic, payload_text, retain);
} }
void Mqtt::publish_retain(const __FlashStringHelper * topic, const JsonDocument & 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(uuid::read_flash_string(topic), payload, retain);
} }
void Mqtt::publish(const std::string & topic, const JsonDocument & payload) { void Mqtt::publish(const std::string & topic, const JsonObject & payload) {
std::string payload_text; std::string payload_text;
serializeJson(payload, payload_text); // convert json to string serializeJson(payload, payload_text); // convert json to string
queue_publish_message(topic, payload_text, mqtt_retain_); queue_publish_message(topic, payload_text, mqtt_retain_);

View File

@@ -45,7 +45,7 @@ using uuid::console::Shell;
namespace emsesp { namespace emsesp {
using mqtt_subfunction_p = std::function<void(const char * message)>; using mqtt_subfunction_p = std::function<void(const char * message)>;
using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>; using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
struct MqttMessage { struct MqttMessage {
~MqttMessage() = default; ~MqttMessage() = default;
@@ -55,7 +55,8 @@ struct MqttMessage {
const std::string payload; const std::string payload;
const bool retain; const bool retain;
MqttMessage(const uint8_t operation, const std::string & topic, const std::string && payload, bool retain) // MqttMessage(const uint8_t operation, const std::string & topic, const std::string && payload, bool retain)
MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
: operation(operation) : operation(operation)
, topic(topic) , topic(topic)
, payload(payload) , payload(payload)
@@ -89,16 +90,16 @@ class Mqtt {
static void register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb); static void register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb);
static void publish(const std::string & topic, const std::string & payload); static void publish(const std::string & topic, const std::string & payload);
static void publish(const std::string & topic, const JsonDocument & payload); static void publish(const std::string & topic, const JsonObject & payload);
static void publish(const __FlashStringHelper * topic, const JsonDocument & payload); static void publish(const __FlashStringHelper * topic, const JsonObject & payload);
static void publish(const __FlashStringHelper * topic, const std::string & payload); static void publish(const __FlashStringHelper * topic, const std::string & payload);
static void publish(const std::string & topic, const bool value); static void publish(const std::string & topic, const bool value);
static void publish(const __FlashStringHelper * topi, const bool value); static void publish(const __FlashStringHelper * topi, const bool value);
static void publish(const std::string & topic); static void publish(const std::string & topic);
static void publish_retain(const std::string & topic, const JsonDocument & payload, bool retain); static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain);
static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain); static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain);
static void publish_retain(const __FlashStringHelper * topic, const JsonDocument & payload, bool retain); static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain);
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);
@@ -194,7 +195,6 @@ class Mqtt {
static uint8_t mqtt_qos_; static uint8_t mqtt_qos_;
static bool mqtt_retain_; static bool mqtt_retain_;
static uint32_t publish_time_; static uint32_t publish_time_;
static uint8_t bus_id_;
static uint32_t publish_time_boiler_; static uint32_t publish_time_boiler_;
static uint32_t publish_time_thermostat_; static uint32_t publish_time_thermostat_;
static uint32_t publish_time_solar_; static uint32_t publish_time_solar_;

View File

@@ -18,7 +18,7 @@
// code originally written by nomis - https://github.com/nomis // code originally written by nomis - https://github.com/nomis
#include "sensors.h" #include "sensor.h"
#include "emsesp.h" #include "emsesp.h"
#ifdef ESP32 #ifdef ESP32
@@ -29,10 +29,10 @@
namespace emsesp { namespace emsesp {
uuid::log::Logger Sensors::logger_{F_(sensors), uuid::log::Facility::DAEMON}; uuid::log::Logger Sensor::logger_{F_(sensor), uuid::log::Facility::DAEMON};
// start the 1-wire // start the 1-wire
void Sensors::start() { void Sensor::start() {
reload(); reload();
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
@@ -40,10 +40,15 @@ void Sensors::start() {
bus_.begin(dallas_gpio_); bus_.begin(dallas_gpio_);
} }
#endif #endif
// API call
Command::add_with_json(EMSdevice::DeviceType::SENSOR, F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
} }
// load the MQTT settings // load the MQTT settings
void Sensors::reload() { void Sensor::reload() {
// copy over values from MQTT so we don't keep on quering the filesystem // copy over values from MQTT so we don't keep on quering the filesystem
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha mqtt_format_ = settings.mqtt_format; // single, nested or ha
@@ -60,7 +65,7 @@ void Sensors::reload() {
} }
} }
void Sensors::loop() { void Sensor::loop() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
uint32_t time_now = uuid::get_uptime(); uint32_t time_now = uuid::get_uptime();
@@ -157,7 +162,7 @@ void Sensors::loop() {
#endif #endif
} }
bool Sensors::temperature_convert_complete() { bool Sensor::temperature_convert_complete() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (parasite_) { if (parasite_) {
return true; // don't care, use the minimum time in loop return true; // don't care, use the minimum time in loop
@@ -171,7 +176,7 @@ bool Sensors::temperature_convert_complete() {
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-parameter"
float Sensors::get_temperature_c(const uint8_t addr[]) { float Sensor::get_temperature_c(const uint8_t addr[]) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (!bus_.reset()) { if (!bus_.reset()) {
LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str()); LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str());
@@ -233,20 +238,20 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
const std::vector<Sensors::Device> Sensors::devices() const { const std::vector<Sensor::Device> Sensor::devices() const {
return devices_; return devices_;
} }
Sensors::Device::Device(const uint8_t addr[]) Sensor::Device::Device(const uint8_t addr[])
: id_(((uint64_t)addr[0] << 56) | ((uint64_t)addr[1] << 48) | ((uint64_t)addr[2] << 40) | ((uint64_t)addr[3] << 32) | ((uint64_t)addr[4] << 24) : id_(((uint64_t)addr[0] << 56) | ((uint64_t)addr[1] << 48) | ((uint64_t)addr[2] << 40) | ((uint64_t)addr[3] << 32) | ((uint64_t)addr[4] << 24)
| ((uint64_t)addr[5] << 16) | ((uint64_t)addr[6] << 8) | (uint64_t)addr[7]) { | ((uint64_t)addr[5] << 16) | ((uint64_t)addr[6] << 8) | (uint64_t)addr[7]) {
} }
uint64_t Sensors::Device::id() const { uint64_t Sensor::Device::id() const {
return id_; return id_;
} }
std::string Sensors::Device::to_string() const { std::string Sensor::Device::to_string() const {
std::string str(20, '\0'); std::string str(20, '\0');
snprintf_P(&str[0], snprintf_P(&str[0],
str.capacity() + 1, str.capacity() + 1,
@@ -260,7 +265,7 @@ std::string Sensors::Device::to_string() const {
} }
// check to see if values have been updated // check to see if values have been updated
bool Sensors::updated_values() { bool Sensor::updated_values() {
if (changed_) { if (changed_) {
changed_ = false; changed_ = false;
return true; return true;
@@ -268,9 +273,35 @@ bool Sensors::updated_values() {
return false; return false;
} }
bool Sensor::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(output));
}
// creates JSON doc from values
// returns false if empty
// e.g. sensors = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.30"},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":"24.0"}}
bool Sensor::export_values(JsonObject & output) {
if (devices_.size() == 0) {
return false;
}
uint8_t i = 1; // sensor count
for (const auto & device : devices_) {
char s[7];
char sensorID[10]; // sensor{1-n}
strlcpy(sensorID, "sensor", 10);
strlcat(sensorID, Helpers::itoa(s, i), 10);
JsonObject dataSensor = output.createNestedObject(sensorID);
dataSensor["id"] = device.to_string();
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c, 1);
i++;
}
return true;
}
// send all dallas sensor values as a JSON package to MQTT // send all dallas sensor values as a JSON package to MQTT
// assumes there are devices // assumes there are devices
void Sensors::publish_values() { void Sensor::publish_values() {
uint8_t num_devices = devices_.size(); uint8_t num_devices = devices_.size();
if (num_devices == 0) { if (num_devices == 0) {
@@ -288,7 +319,7 @@ void Sensors::publish_values() {
char topic[60]; // sensors{1-n} char topic[60]; // sensors{1-n}
strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F
strlcat(topic, device.to_string().c_str(), 60); strlcat(topic, device.to_string().c_str(), 60);
Mqtt::publish(topic, doc); Mqtt::publish(topic, doc.as<JsonObject>());
doc.clear(); // clear json doc so we can reuse the buffer again doc.clear(); // clear json doc so we can reuse the buffer again
} }
return; return;
@@ -342,7 +373,7 @@ void Sensors::publish_values() {
config["uniq_id"] = str; config["uniq_id"] = str;
snprintf_P(&topic[0], 50, PSTR("homeassistant/sensor/ems-esp/sensor%d/config"), i); snprintf_P(&topic[0], 50, PSTR("homeassistant/sensor/ems-esp/sensor%d/config"), i);
Mqtt::publish_retain(topic, config, false); // publish the config payload with no retain flag Mqtt::publish_retain(topic, config.as<JsonObject>(), false); // publish the config payload with no retain flag
registered_ha_[i] = true; registered_ha_[i] = true;
} }
@@ -351,9 +382,10 @@ void Sensors::publish_values() {
} }
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) { if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
Mqtt::publish(F("sensors"), doc); Mqtt::publish(F("sensors"), doc.as<JsonObject>());
} else if (mqtt_format_ == MQTT_format::HA) { } else if (mqtt_format_ == MQTT_format::HA) {
Mqtt::publish(F("homeassistant/sensor/ems-esp/state"), doc); Mqtt::publish(F("homeassistant/sensor/ems-esp/state"), doc.as<JsonObject>());
} }
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -18,8 +18,8 @@
// code originally written by nomis - https://github.com/nomis // code originally written by nomis - https://github.com/nomis
#ifndef EMSESP_SENSORS_H #ifndef EMSESP_SENSOR_H
#define EMSESP_SENSORS_H #define EMSESP_SENSOR_H
#include <string> #include <string>
#include <vector> #include <vector>
@@ -36,7 +36,7 @@
namespace emsesp { namespace emsesp {
class Sensors { class Sensor {
public: public:
class Device { class Device {
public: public:
@@ -53,8 +53,8 @@ class Sensors {
bool registered_ = false; bool registered_ = false;
}; };
Sensors() = default; Sensor() = default;
~Sensors() = default; ~Sensor() = default;
void start(); void start();
void loop(); void loop();
@@ -62,6 +62,9 @@ class Sensors {
void reload(); void reload();
bool updated_values(); bool updated_values();
bool command_info(const char * value, const int8_t id, JsonObject & output);
bool export_values(JsonObject & doc);
const std::vector<Device> devices() const; const std::vector<Device> devices() const;
private: private:

View File

@@ -129,7 +129,7 @@ void Shower::publish_values() {
doc["duration"] = s; doc["duration"] = s;
} }
Mqtt::publish(F("shower_data"), doc); Mqtt::publish(F("shower_data"), doc.as<JsonObject>());
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -152,8 +152,16 @@ void System::start() {
EMSESP::esp8266React.getWiFiSettingsService()->read( EMSESP::esp8266React.getWiFiSettingsService()->read(
[&](WiFiSettings & wifiSettings) { LOG_INFO(F("System %s booted (EMS-ESP version %s)"), wifiSettings.hostname.c_str(), EMSESP_APP_VERSION); }); [&](WiFiSettings & wifiSettings) { LOG_INFO(F("System %s booted (EMS-ESP version %s)"), wifiSettings.hostname.c_str(), EMSESP_APP_VERSION); });
syslog_init(); // init SysLog syslog_init(); // init SysLog
set_led(); // init LED set_led(); // init LED
// these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""}
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
Command::add(EMSdevice::DeviceType::SERVICEKEY, settings.ems_bus_id, F("pin"), System::command_pin);
Command::add(EMSdevice::DeviceType::SERVICEKEY, settings.ems_bus_id, F("send"), System::command_send);
Command::add_with_json(EMSdevice::DeviceType::SERVICEKEY, F("info"), System::command_info);
});
EMSESP::init_tx(); // start UART EMSESP::init_tx(); // start UART
} }
@@ -241,7 +249,7 @@ void System::send_heartbeat() {
doc["rxfails"] = EMSESP::rxservice_.telegram_error_count(); doc["rxfails"] = EMSESP::rxservice_.telegram_error_count();
doc["adc"] = analog_; //analogRead(A0); doc["adc"] = analog_; //analogRead(A0);
Mqtt::publish_retain(F("heartbeat"), doc, false); // send to MQTT with retain off. This will add to MQTT queue. Mqtt::publish_retain(F("heartbeat"), doc.as<JsonObject>(), false); // send to MQTT with retain off. This will add to MQTT queue.
} }
// measure and moving average adc // measure and moving average adc
@@ -806,14 +814,14 @@ bool System::check_upgrade() {
// export all settings to JSON text // export all settings to JSON text
// http://ems-esp/api?device=system&cmd=info // http://ems-esp/api?device=system&cmd=info
String System::export_settings() { // value and id are ignored
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc; bool System::command_info(const char * value, const int8_t id, JsonObject & output) {
#ifdef EMSESP_STANDALONE
#ifndef EMSESP_STANDALONE output["test"] = "testing";
JsonObject root = doc.to<JsonObject>(); #else
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) { EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) {
JsonObject node = root.createNestedObject("WIFI"); JsonObject node = output.createNestedObject("WIFI");
node["ssid"] = settings.ssid; node["ssid"] = settings.ssid;
// node["password"] = settings.password; // node["password"] = settings.password;
node["hostname"] = settings.hostname; node["hostname"] = settings.hostname;
@@ -826,7 +834,7 @@ String System::export_settings() {
}); });
EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) { EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) {
JsonObject node = root.createNestedObject("AP"); JsonObject node = output.createNestedObject("AP");
node["provision_mode"] = settings.provisionMode; node["provision_mode"] = settings.provisionMode;
node["ssid"] = settings.ssid; node["ssid"] = settings.ssid;
// node["password"] = settings.password; // node["password"] = settings.password;
@@ -836,7 +844,7 @@ String System::export_settings() {
}); });
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
JsonObject node = root.createNestedObject("MQTT"); JsonObject node = output.createNestedObject("MQTT");
node["enabled"] = settings.enabled; node["enabled"] = settings.enabled;
node["host"] = settings.host; node["host"] = settings.host;
node["port"] = settings.port; node["port"] = settings.port;
@@ -859,7 +867,7 @@ String System::export_settings() {
}); });
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) { EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
JsonObject node = root.createNestedObject("NTP"); JsonObject node = output.createNestedObject("NTP");
node["enabled"] = settings.enabled; node["enabled"] = settings.enabled;
node["server"] = settings.server; node["server"] = settings.server;
node["tz_label"] = settings.tzLabel; node["tz_label"] = settings.tzLabel;
@@ -867,18 +875,33 @@ String System::export_settings() {
}); });
EMSESP::esp8266React.getOTASettingsService()->read([&](OTASettings & settings) { EMSESP::esp8266React.getOTASettingsService()->read([&](OTASettings & settings) {
JsonObject node = root.createNestedObject("OTA"); JsonObject node = output.createNestedObject("OTA");
node["enabled"] = settings.enabled; node["enabled"] = settings.enabled;
node["port"] = settings.port; node["port"] = settings.port;
// node["password"] = settings.password; // node["password"] = settings.password;
}); });
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
JsonObject node = output.createNestedObject("Settings");
node["tx_mode"] = settings.tx_mode;
node["ems_bus_id"] = settings.ems_bus_id;
node["syslog_level"] = settings.syslog_level;
node["syslog_mark_interval"] = settings.syslog_mark_interval;
node["syslog_host"] = settings.syslog_host;
node["master_thermostat"] = settings.master_thermostat;
node["shower_timer"] = settings.shower_timer;
node["shower_alert"] = settings.shower_alert;
node["rx_gpio"] = settings.rx_gpio;
node["tx_gpio"] = settings.tx_gpio;
node["dallas_gpio"] = settings.dallas_gpio;
node["dallas_parasite"] = settings.dallas_parasite;
node["led_gpio"] = settings.led_gpio;
node["hide_led"] = settings.hide_led;
});
#endif #endif
return true;
String buffer;
serializeJsonPretty(doc, buffer);
return buffer;
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -50,12 +50,13 @@ class System {
static bool command_pin(const char * value, const int8_t id); static bool command_pin(const char * value, const int8_t id);
static bool command_send(const char * value, const int8_t id); static bool command_send(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject & output);
static uint8_t free_mem(); static uint8_t free_mem();
static void upload_status(bool in_progress); static void upload_status(bool in_progress);
static bool upload_status(); static bool upload_status();
static void show_mem(const char * note); static void show_mem(const char * note);
static void set_led(); static void set_led();
static String export_settings();
bool check_upgrade(); bool check_upgrade();
void syslog_init(); void syslog_init();

View File

@@ -28,7 +28,7 @@ namespace emsesp {
// used with the 'test' command, under su/admin // used with the 'test' command, under su/admin
void Test::run_test(uuid::console::Shell & shell, const std::string & command) { void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "default") { if (command == "default") {
run_test(shell, "mqtt"); // add the default test case here run_test(shell, "cmd"); // add the default test case here
} }
if (command.empty()) { if (command.empty()) {
@@ -565,6 +565,45 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::txservice_.flush_tx_queue(); EMSESP::txservice_.flush_tx_queue();
} }
if (command == "cmd") {
shell.printfln(F("Testing Commands..."));
// change MQTT format
EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) {
mqttSettings.mqtt_format = MQTT_format::SINGLE;
// mqttSettings.mqtt_format = MQTT_format::NESTED;
// mqttSettings.mqtt_format = MQTT_format::HA;
return StateUpdateResult::CHANGED;
});
EMSESP::add_context_menus(); // need to add this as it happens later in the code
shell.invoke_command("su");
shell.invoke_command("system");
shell.invoke_command("call");
shell.invoke_command("call info");
shell.invoke_command("exit");
char system_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
strcpy(system_topic, "ems-esp/system");
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"info\"}"); // this should fail
// add a thermostat with 3 HCs
std::string version("1.2.3");
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC2
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); // HC3
EMSESP::add_context_menus(); // need to add this as it happens later in the code
shell.invoke_command("thermostat");
shell.invoke_command("show");
shell.invoke_command("call");
shell.invoke_command("call info");
shell.invoke_command("exit");
shell.invoke_command("show mqtt");
}
if (command == "pin") { if (command == "pin") {
shell.printfln(F("Testing pin...")); shell.printfln(F("Testing pin..."));
@@ -579,6 +618,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "mqtt") { if (command == "mqtt") {
shell.printfln(F("Testing MQTT...")); shell.printfln(F("Testing MQTT..."));
// EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) {
// mqttSettings.mqtt_format = MQTT_format::SINGLE;
// return StateUpdateResult::CHANGED;
// });
// add a boiler // add a boiler
// question: do we need to set the mask? // question: do we need to set the mask?
std::string version("1.2.3"); std::string version("1.2.3");
@@ -700,6 +744,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// check for error "[emsesp] No telegram type handler found for ID 0x255 (src 0x20, dest 0x00)" // check for error "[emsesp] No telegram type handler found for ID 0x255 (src 0x20, dest 0x00)"
rx_telegram({0xA0, 0x00, 0xFF, 0x00, 0x01, 0x55, 0x00, 0x1A}); rx_telegram({0xA0, 0x00, 0xFF, 0x00, 0x01, 0x55, 0x00, 0x1A});
EMSESP::add_context_menus(); // need to add this as it happens later in the code
} }
// finally dump to console // finally dump to console