mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
added all devices - API: HTTP read/write #506
This commit is contained in:
@@ -17,7 +17,7 @@ class EMSESPHelp extends Component {
|
||||
</Box>
|
||||
<br></br>
|
||||
<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 variant="body1" paragraph>
|
||||
For live community chat go to <Link href="https://gitter.im/EMS-ESP/community#" color="primary">{'Gitter'}</Link>.
|
||||
|
||||
@@ -48,7 +48,7 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
|
||||
<ValidatorForm onSubmit={saveData}>
|
||||
<Box bgcolor="info.main" p={2} mt={2} mb={2}>
|
||||
<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> 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> for descriptions of each setting.
|
||||
</Typography>
|
||||
</Box>
|
||||
<br></br>
|
||||
@@ -181,6 +181,20 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
|
||||
label="Shower Alert"
|
||||
/>
|
||||
<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" >
|
||||
Syslog
|
||||
</Typography>
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface EMSESPSettings {
|
||||
dallas_parasite: boolean;
|
||||
led_gpio: number;
|
||||
hide_led: boolean;
|
||||
api_enabled: boolean;
|
||||
}
|
||||
|
||||
export enum busConnectionStatus {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
namespace emsesp {
|
||||
class EMSESP {
|
||||
public:
|
||||
static System system_;
|
||||
static Mqtt mqtt_;
|
||||
static Sensors sensors_;
|
||||
static System system_;
|
||||
static Mqtt mqtt_;
|
||||
static Sensor sensor_;
|
||||
};
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -169,7 +169,7 @@ void MqttSettingsService::configureMqtt() {
|
||||
_mqttClient.connect();
|
||||
}
|
||||
|
||||
emsesp::EMSESP::sensors_.reload();
|
||||
emsesp::EMSESP::sensor_.reload();
|
||||
}
|
||||
|
||||
void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "../../src/system.h"
|
||||
#include "../../src/mqtt.h"
|
||||
#include "../../src/sensors.h"
|
||||
#include "../../src/sensor.h"
|
||||
|
||||
#define MQTT_RECONNECTION_DELAY 1000
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ class DummySettings {
|
||||
bool shower_timer = false;
|
||||
bool shower_alert = false;
|
||||
bool hide_led = false;
|
||||
bool api_enabled = true;
|
||||
uint16_t publish_time = 10; // seconds
|
||||
uint8_t mqtt_format = 3; // 1=single, 2=nested, 3=ha, 4=custom
|
||||
uint8_t mqtt_qos = 0;
|
||||
|
||||
@@ -30,9 +30,13 @@ EMSESPAPIService::EMSESPAPIService(AsyncWebServer * server) {
|
||||
|
||||
// http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -40,26 +44,16 @@ void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) {
|
||||
String device = request->getParam(F_(device))->value();
|
||||
uint8_t device_type = EMSdevice::device_name_2_device_type(device.c_str());
|
||||
if (device_type == emsesp::EMSdevice::DeviceType::UNKNOWN) {
|
||||
request->send(400, "text/plain", F("invalid device"));
|
||||
request->send(400, "text/plain", F("Invalid device"));
|
||||
return;
|
||||
}
|
||||
|
||||
// get cmd, we know we have one
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -73,31 +67,54 @@ void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) {
|
||||
id = request->getParam(F_(id))->value();
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
bool ok = false;
|
||||
|
||||
// execute the command
|
||||
bool ok = false;
|
||||
if (data.isEmpty()) {
|
||||
ok = Command::call_command(device_type, cmd.c_str(), nullptr, -1); // command only
|
||||
} else if (id.isEmpty()) {
|
||||
ok = Command::call_command(device_type, cmd.c_str(), data.c_str(), -1); // only ID
|
||||
ok = Command::call(device_type, cmd.c_str(), nullptr, -1, output); // command only
|
||||
} 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
|
||||
#if defined(EMSESP_DEBUG)
|
||||
std::string output(200, '\0');
|
||||
snprintf_P(&output[0],
|
||||
output.capacity() + 1,
|
||||
std::string debug(200, '\0');
|
||||
snprintf_P(&debug[0],
|
||||
debug.capacity() + 1,
|
||||
PSTR("API: device=%s cmd=%s data=%s id=%s [%s]"),
|
||||
device.c_str(),
|
||||
cmd.c_str(),
|
||||
data.c_str(),
|
||||
id.c_str(),
|
||||
ok ? F("OK") : F("Failed"));
|
||||
EMSESP::logger().info(output.c_str());
|
||||
ok ? F("OK") : F("Invalid"));
|
||||
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
|
||||
|
||||
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
|
||||
@@ -33,7 +33,6 @@ class EMSESPAPIService {
|
||||
|
||||
private:
|
||||
void emsespAPIService(AsyncWebServerRequest * request);
|
||||
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -42,6 +42,7 @@ void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
|
||||
root["dallas_parasite"] = settings.dallas_parasite;
|
||||
root["led_gpio"] = settings.led_gpio;
|
||||
root["hide_led"] = settings.hide_led;
|
||||
root["api_enabled"] = settings.api_enabled;
|
||||
}
|
||||
|
||||
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.led_gpio = root["led_gpio"] | EMSESP_DEFAULT_LED_GPIO;
|
||||
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
|
||||
settings.api_enabled = root["api_enabled"] | EMSESP_DEFAULT_API_ENABLED;
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -70,8 +72,8 @@ void EMSESPSettingsService::onUpdate() {
|
||||
// EMSESP::system_.syslog_init(); // changing SysLog will require a restart
|
||||
EMSESP::init_tx();
|
||||
System::set_led();
|
||||
Sensors sensors_; // Dallas sensors
|
||||
sensors_.start();
|
||||
Sensor sensor_; // Dallas sensors
|
||||
sensor_.start();
|
||||
}
|
||||
|
||||
void EMSESPSettingsService::begin() {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#define EMSESP_DEFAULT_SHOWER_ALERT false
|
||||
#define EMSESP_DEFAULT_HIDE_LED false
|
||||
#define EMSESP_DEFAULT_DALLAS_PARASITE false
|
||||
#define EMSESP_DEFAULT_API_ENABLED true
|
||||
|
||||
// Default GPIO PIN definitions
|
||||
#if defined(ESP8266)
|
||||
@@ -75,6 +76,7 @@ class EMSESPSettings {
|
||||
bool dallas_parasite;
|
||||
uint8_t led_gpio;
|
||||
bool hide_led;
|
||||
bool api_enabled;
|
||||
|
||||
static void read(EMSESPSettings & settings, JsonObject & root);
|
||||
static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings);
|
||||
|
||||
@@ -29,12 +29,15 @@ std::vector<Command::CmdFunction> Command::cmdfunctions_;
|
||||
// calls a command, context is the device_type
|
||||
// id may be used to represent a heating circuit for example
|
||||
// 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
|
||||
if (id == -1) {
|
||||
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is default"), cmd, value);
|
||||
std::string dname = EMSdevice::device_type_2_device_name(device_type);
|
||||
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 {
|
||||
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
|
||||
|
||||
@@ -42,23 +45,37 @@ bool Command::call_command(const uint8_t device_type, const char * cmd, const ch
|
||||
for (const auto & cf : cmdfunctions_) {
|
||||
if (cf.device_type_ == device_type) {
|
||||
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) {
|
||||
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
|
||||
void Command::add_command(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);
|
||||
// add a command to the list, which does not return json
|
||||
void Command::add(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) {
|
||||
cmdfunctions_.emplace_back(device_type, cmd, cb, nullptr);
|
||||
|
||||
// see if we need to subscribe
|
||||
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
|
||||
bool Command::find(const uint8_t device_type, const char * cmd) {
|
||||
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
|
||||
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()) {
|
||||
if (cf.device_type_ == device_type) {
|
||||
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
|
||||
uint8_t Command::context_2_device_type(unsigned int context) {
|
||||
if (context == ShellContext::MAIN) {
|
||||
return EMSdevice::DeviceType::SERVICEKEY;
|
||||
}
|
||||
// if (context == ShellContext::MAIN) {
|
||||
// return EMSdevice::DeviceType::SERVICEKEY;
|
||||
// }
|
||||
if (context == ShellContext::BOILER) {
|
||||
return EMSdevice::DeviceType::BOILER;
|
||||
}
|
||||
@@ -99,34 +116,36 @@ uint8_t Command::context_2_device_type(unsigned int context) {
|
||||
if (context == ShellContext::THERMOSTAT) {
|
||||
return EMSdevice::DeviceType::THERMOSTAT;
|
||||
}
|
||||
if (context == ShellContext::SENSOR) {
|
||||
return EMSdevice::DeviceType::SENSOR;
|
||||
}
|
||||
|
||||
return EMSdevice::DeviceType::UNKNOWN; // unknown type
|
||||
}
|
||||
|
||||
// show command per current context
|
||||
void Command::show_commands(uuid::console::Shell & shell) {
|
||||
show_commands(shell, context_2_device_type(shell.context()));
|
||||
void Command::show(uuid::console::Shell & shell) {
|
||||
show(shell, context_2_device_type(shell.context()));
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
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
|
||||
for (const auto & device_class : EMSFactory::device_handlers()) {
|
||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
|
||||
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) {
|
||||
// if we're adding commands for a thermostat or mixing, then include an additional optional paramter called heating circuit
|
||||
flash_string_vector params;
|
||||
@@ -147,7 +166,7 @@ void Command::add_context_commands(unsigned int context) {
|
||||
if (arguments.empty()) {
|
||||
// list options
|
||||
shell.print("Available commands: ");
|
||||
show_commands(shell);
|
||||
show(shell);
|
||||
shell.println();
|
||||
return;
|
||||
}
|
||||
@@ -155,16 +174,35 @@ void Command::add_context_commands(unsigned int context) {
|
||||
// determine the device_type from the shell context
|
||||
uint8_t device_type = context_2_device_type(shell.context());
|
||||
|
||||
// validate the command
|
||||
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) {
|
||||
// no value specified
|
||||
(void)Command::call_command(device_type, cmd, nullptr, -1);
|
||||
// no value specified, just the cmd
|
||||
ok = Command::call(device_type, cmd, nullptr, -1, output);
|
||||
} else if (arguments.size() == 2) {
|
||||
// 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 {
|
||||
// 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> {
|
||||
@@ -182,5 +220,4 @@ void Command::add_context_commands(unsigned int context) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -34,21 +34,22 @@ using uuid::console::Shell;
|
||||
|
||||
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 {
|
||||
public:
|
||||
struct CmdFunction {
|
||||
uint8_t device_type_; // DeviceType::
|
||||
uint8_t device_id_;
|
||||
const __FlashStringHelper * cmd_;
|
||||
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_id_(device_id)
|
||||
, cmd_(cmd)
|
||||
, cmdfunction_(cmdfunction) {
|
||||
, cmdfunction_(cmdfunction)
|
||||
, cmdfunction_json_(cmdfunction_json) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,10 +57,11 @@ class Command {
|
||||
return cmdfunctions_;
|
||||
}
|
||||
|
||||
static bool call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id);
|
||||
static void add_command(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 show_commands(uuid::console::Shell & shell);
|
||||
static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & output);
|
||||
static void add(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb);
|
||||
static void add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb);
|
||||
static void show_all(uuid::console::Shell & shell);
|
||||
static void show(uuid::console::Shell & shell);
|
||||
static void add_context_commands(unsigned int context);
|
||||
static bool find(const uint8_t device_type, const char * cmd);
|
||||
|
||||
@@ -68,7 +70,7 @@ class Command {
|
||||
private:
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ void EMSESPShell::add_console_commands() {
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
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(
|
||||
ShellContext::MAIN,
|
||||
@@ -466,8 +466,10 @@ void Console::load_standard_commands(unsigned int 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
|
||||
@@ -485,6 +487,9 @@ std::string EMSESPShell::context_text() {
|
||||
case ShellContext::THERMOSTAT:
|
||||
return std::string{"/thermostat"};
|
||||
|
||||
case ShellContext::SENSOR:
|
||||
return std::string{"/sensor"};
|
||||
|
||||
default:
|
||||
return std::string{};
|
||||
}
|
||||
@@ -577,7 +582,6 @@ void Console::start() {
|
||||
shell->log_level(uuid::log::Level::DEBUG);
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
// always start in su/admin mode when running tests
|
||||
shell->add_flags(CommandFlags::ADMIN);
|
||||
|
||||
@@ -79,7 +79,8 @@ enum ShellContext : uint8_t {
|
||||
BOILER,
|
||||
THERMOSTAT,
|
||||
SOLAR,
|
||||
MIXING
|
||||
MIXING,
|
||||
SENSOR
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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("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) {
|
||||
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));
|
||||
}
|
||||
|
||||
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
|
||||
void Boiler::publish_values() {
|
||||
// const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
||||
// DynamicJsonDocument doc(capacity);
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
|
||||
|
||||
char s[10]; // for formatting strings
|
||||
|
||||
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);
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
if (export_values(output)) {
|
||||
Mqtt::publish(F("boiler_data"), doc.as<JsonObject>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -801,6 +816,7 @@ bool Boiler::set_heating_activated(const char * value, const int8_t id) {
|
||||
if (!Helpers::value2bool(value, v)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler heating "), v ? "on" : "off");
|
||||
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
|
||||
write_command(EMS_TYPE_UBAParametersPlus, 0, v ? 0x01 : 0, EMS_TYPE_UBAParametersPlus);
|
||||
|
||||
@@ -50,6 +50,7 @@ class Boiler : public EMSdevice {
|
||||
void console_commands(Shell & shell, unsigned int context);
|
||||
void register_mqtt_ha_config();
|
||||
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 mqtt_format_; // single, nested or ha
|
||||
@@ -76,7 +77,7 @@ class Boiler : public EMSdevice {
|
||||
|
||||
// MC10Status
|
||||
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
|
||||
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 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_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
@@ -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) {
|
||||
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
|
||||
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
|
||||
@@ -89,7 +103,24 @@ bool Mixing::updated_values() {
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -117,49 +148,66 @@ void Mixing::show_values(uuid::console::Shell & shell) {
|
||||
shell.println();
|
||||
}
|
||||
|
||||
bool Mixing::command_info(const char * value, const int8_t id, JsonObject & output) {
|
||||
return (export_values(output));
|
||||
}
|
||||
|
||||
// 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
|
||||
void Mixing::publish_values() {
|
||||
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_) {
|
||||
case Type::HC:
|
||||
doc["type"] = "hc";
|
||||
output["type"] = "hc";
|
||||
if (Helpers::hasValue(flowTemp_)) {
|
||||
doc["flowTemp"] = (float)flowTemp_ / 10;
|
||||
output["flowTemp"] = (float)flowTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(flowSetTemp_)) {
|
||||
doc["flowSetTemp"] = flowSetTemp_;
|
||||
output["flowSetTemp"] = flowSetTemp_;
|
||||
}
|
||||
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_)) {
|
||||
doc["valveStatus"] = status_;
|
||||
output["valveStatus"] = status_;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::WWC:
|
||||
doc["type"] = "wwc";
|
||||
output["type"] = "wwc";
|
||||
if (Helpers::hasValue(flowTemp_)) {
|
||||
doc["wwTemp"] = (float)flowTemp_ / 10;
|
||||
output["wwTemp"] = (float)flowTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(pump_)) {
|
||||
doc["pumpStatus"] = pump_;
|
||||
output["pumpStatus"] = pump_;
|
||||
}
|
||||
if (Helpers::hasValue(status_)) {
|
||||
doc["tempStatus"] = status_;
|
||||
output["tempStatus"] = status_;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::NONE:
|
||||
default:
|
||||
return;
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
char topic[30];
|
||||
strlcpy(topic, "mixing_data", 30);
|
||||
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
|
||||
Mqtt::publish(topic, doc);
|
||||
return output.size();
|
||||
}
|
||||
|
||||
// heating circuits 0x02D7, 0x02D8 etc...
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "emsesp.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
@@ -44,7 +45,9 @@ class Mixing : public EMSdevice {
|
||||
private:
|
||||
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_WWC(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -66,7 +69,8 @@ class Mixing : public EMSdevice {
|
||||
int8_t status_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
|
||||
Type type_ = Type::NONE;
|
||||
bool changed_ = false;
|
||||
|
||||
bool changed_ = false;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -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(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
|
||||
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
|
||||
@@ -109,69 +127,74 @@ void Solar::show_values(uuid::console::Shell & shell) {
|
||||
// publish values via MQTT
|
||||
void Solar::publish_values() {
|
||||
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
|
||||
|
||||
if (Helpers::hasValue(collectorTemp_)) {
|
||||
doc["collectorTemp"] = (float)collectorTemp_ / 10;
|
||||
output["collectorTemp"] = (float)collectorTemp_ / 10;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(tankBottomTemp_)) {
|
||||
doc["tankBottomTemp"] = (float)tankBottomTemp_ / 10;
|
||||
output["tankBottomTemp"] = (float)tankBottomTemp_ / 10;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(tankBottomTemp2_)) {
|
||||
doc["tankBottomTemp2"] = (float)tankBottomTemp2_ / 10;
|
||||
output["tankBottomTemp2"] = (float)tankBottomTemp2_ / 10;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(heatExchangerTemp_)) {
|
||||
doc["heatExchangerTemp"] = (float)heatExchangerTemp_ / 10;
|
||||
output["heatExchangerTemp"] = (float)heatExchangerTemp_ / 10;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(solarPumpModulation_)) {
|
||||
doc["solarPumpModulation"] = solarPumpModulation_;
|
||||
output["solarPumpModulation"] = solarPumpModulation_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(cylinderPumpModulation_)) {
|
||||
doc["cylinderPumpModulation"] = cylinderPumpModulation_;
|
||||
output["cylinderPumpModulation"] = cylinderPumpModulation_;
|
||||
}
|
||||
|
||||
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)) {
|
||||
doc["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
|
||||
output["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(pumpWorkMin_)) {
|
||||
doc["pumpWorkMin"] = pumpWorkMin_;
|
||||
output["pumpWorkMin"] = pumpWorkMin_;
|
||||
}
|
||||
|
||||
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)) {
|
||||
doc["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL);
|
||||
output["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL);
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(energyLastHour_)) {
|
||||
doc["energyLastHour"] = (float)energyLastHour_ / 10;
|
||||
output["energyLastHour"] = (float)energyLastHour_ / 10;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(energyToday_)) {
|
||||
doc["energyToday"] = energyToday_;
|
||||
output["energyToday"] = energyToday_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(energyTotal_)) {
|
||||
doc["energyTotal"] = (float)energyTotal_ / 10;
|
||||
output["energyTotal"] = (float)energyTotal_ / 10;
|
||||
}
|
||||
|
||||
// if we have data, publish it
|
||||
if (!doc.isNull()) {
|
||||
Mqtt::publish(F("sm_data"), doc);
|
||||
}
|
||||
return output.size();
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
@@ -184,7 +207,24 @@ bool Solar::updated_values() {
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "emsesp.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
@@ -44,7 +45,9 @@ class Solar : public EMSdevice {
|
||||
private:
|
||||
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 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 configFlag_ = 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_SM100Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
@@ -248,21 +248,34 @@ bool Thermostat::updated_values() {
|
||||
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
|
||||
void Thermostat::publish_values() {
|
||||
// only publish on the master thermostat
|
||||
if (EMSESP::actual_master_thermostat() != this->device_id()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t flags = this->model();
|
||||
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
JsonObject rootThermostat = doc.to<JsonObject>();
|
||||
JsonObject dataThermostat;
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
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
|
||||
// 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 (datetime_.size()) {
|
||||
rootThermostat["time"] = datetime_.c_str();
|
||||
@@ -302,9 +315,9 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
// send this specific data using the thermostat_data topic
|
||||
if (mqtt_format_ != MQTT_format::NESTED) {
|
||||
Mqtt::publish(F("thermostat_data"), doc);
|
||||
rootThermostat = doc.to<JsonObject>(); // clear object
|
||||
if (mqtt_format != MQTT_format::NESTED) {
|
||||
Mqtt::publish(F("thermostat_data"), rootThermostat);
|
||||
rootThermostat.clear(); // clear object
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,8 +328,7 @@ void Thermostat::publish_values() {
|
||||
has_data = true;
|
||||
|
||||
// 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}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
char s[3];
|
||||
@@ -397,11 +409,11 @@ void Thermostat::publish_values() {
|
||||
dataThermostat["summertemp"] = hc->summertemp;
|
||||
}
|
||||
|
||||
// when using HA always send the mode otherwise it'll may break the component/widget and report an error
|
||||
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
// 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)) {
|
||||
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 (mqtt_format_ == MQTT_format::HA) {
|
||||
if (mqtt_format == MQTT_format::HA) {
|
||||
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
|
||||
hc_mode = HeatingCircuit::Mode::HEAT;
|
||||
} 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
|
||||
// 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 s[3];
|
||||
strlcpy(topic, "thermostat_data", 30);
|
||||
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
|
||||
Mqtt::publish(topic, doc);
|
||||
rootThermostat = doc.to<JsonObject>(); // clear object
|
||||
} else if (mqtt_format_ == MQTT_format::HA) {
|
||||
rootThermostat.clear(); // clear object
|
||||
} else if (mqtt_format == MQTT_format::HA) {
|
||||
// see if we have already registered this with HA MQTT Discovery, if not send the config
|
||||
if (!hc->ha_registered()) {
|
||||
register_mqtt_ha_config(hc->hc_num());
|
||||
@@ -442,19 +452,12 @@ void Thermostat::publish_values() {
|
||||
// send the thermostat topic and payload data
|
||||
std::string topic(100, '\0');
|
||||
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; // 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);
|
||||
}
|
||||
return (has_data);
|
||||
}
|
||||
|
||||
// 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
|
||||
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_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
|
||||
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);
|
||||
}
|
||||
|
||||
// commands for MQTT and Console
|
||||
// API commands for MQTT and Console
|
||||
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->flags() & EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) == EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) {
|
||||
return;
|
||||
|
||||
@@ -108,6 +108,7 @@ class Thermostat : public EMSdevice {
|
||||
|
||||
void console_commands(Shell & shell, unsigned int context);
|
||||
void add_commands();
|
||||
bool export_values(uint8_t mqtt_format, JsonObject & doc);
|
||||
|
||||
// specific thermostat characteristics, stripping the option bits at pos 6 and 7
|
||||
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);
|
||||
|
||||
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_IBASettings(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
@@ -111,6 +111,10 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
|
||||
return DeviceType::MIXING;
|
||||
}
|
||||
|
||||
if (strcmp(topic, "sensor") == 0) {
|
||||
return DeviceType::SENSOR;
|
||||
}
|
||||
|
||||
return DeviceType::UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -286,7 +290,7 @@ void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_
|
||||
|
||||
// add command to library
|
||||
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
|
||||
|
||||
@@ -229,6 +229,7 @@ class EMSdevice {
|
||||
|
||||
enum DeviceType : uint8_t {
|
||||
SERVICEKEY = 0, // this is us (EMS-ESP)
|
||||
SENSOR, // for internal dallas sensors
|
||||
BOILER,
|
||||
THERMOSTAT,
|
||||
MIXING,
|
||||
|
||||
@@ -51,7 +51,7 @@ TxService EMSESP::txservice_; // outgoing Telegram Tx handler
|
||||
Mqtt EMSESP::mqtt_; // mqtt handler
|
||||
System EMSESP::system_; // core system services
|
||||
Console EMSESP::console_; // telnet and serial console
|
||||
Sensors EMSESP::sensors_; // Dallas sensors
|
||||
Sensor EMSESP::sensor_; // Dallas sensors
|
||||
Shower EMSESP::shower_; // Shower logic
|
||||
|
||||
// static/common variables
|
||||
@@ -311,8 +311,8 @@ void EMSESP::publish_other_values() {
|
||||
|
||||
void EMSESP::publish_sensor_values(const bool force) {
|
||||
if (Mqtt::connected()) {
|
||||
if (sensors_.updated_values() || force) {
|
||||
sensors_.publish_values();
|
||||
if (sensor_.updated_values() || force) {
|
||||
sensor_.publish_values();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,7 +337,7 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
|
||||
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
|
||||
@@ -845,7 +845,7 @@ void EMSESP::start() {
|
||||
mqtt_.start(); // mqtt init
|
||||
system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings.
|
||||
shower_.start(); // initialize shower timer and shower alert
|
||||
sensors_.start(); // dallas external sensors
|
||||
sensor_.start(); // dallas external sensors
|
||||
webServer.begin(); // start web server
|
||||
|
||||
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
|
||||
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
|
||||
console_.loop(); // telnet/serial console
|
||||
rxservice_.loop(); // process any incoming Rx telegrams
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
#include "telegram.h"
|
||||
#include "mqtt.h"
|
||||
#include "system.h"
|
||||
#include "sensors.h"
|
||||
#include "sensor.h"
|
||||
#include "console.h"
|
||||
#include "shower.h"
|
||||
#include "roomcontrol.h"
|
||||
@@ -108,8 +108,8 @@ class EMSESP {
|
||||
|
||||
static void incoming_telegram(uint8_t * data, const uint8_t length);
|
||||
|
||||
static const std::vector<Sensors::Device> sensor_devices() {
|
||||
return sensors_.devices();
|
||||
static const std::vector<Sensor::Device> sensor_devices() {
|
||||
return sensor_.devices();
|
||||
}
|
||||
|
||||
enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW };
|
||||
@@ -153,7 +153,7 @@ class EMSESP {
|
||||
// services
|
||||
static Mqtt mqtt_;
|
||||
static System system_;
|
||||
static Sensors sensors_;
|
||||
static Sensor sensor_;
|
||||
static Console console_;
|
||||
static Shower shower_;
|
||||
static RxService rxservice_;
|
||||
|
||||
@@ -88,7 +88,7 @@ MAKE_PSTR_WORD(connect)
|
||||
MAKE_PSTR_WORD(heatpump)
|
||||
|
||||
// dallas sensors
|
||||
MAKE_PSTR_WORD(sensors)
|
||||
MAKE_PSTR_WORD(sensor)
|
||||
|
||||
MAKE_PSTR(kwh, "kWh")
|
||||
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(wifi_ssid_fmt, "WiFi SSID = %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(deep_optional, "[deep]")
|
||||
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")
|
||||
|
||||
41
src/mqtt.cpp
41
src/mqtt.cpp
@@ -28,7 +28,6 @@ AsyncMqttClient * Mqtt::mqttClient_;
|
||||
std::string Mqtt::hostname_;
|
||||
uint8_t Mqtt::mqtt_qos_;
|
||||
bool Mqtt::mqtt_retain_;
|
||||
uint8_t Mqtt::bus_id_;
|
||||
uint32_t Mqtt::publish_time_boiler_;
|
||||
uint32_t Mqtt::publish_time_thermostat_;
|
||||
uint32_t Mqtt::publish_time_solar_;
|
||||
@@ -148,9 +147,10 @@ void Mqtt::loop() {
|
||||
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());
|
||||
|
||||
bool system_heartbeat;
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat = settings.system_heartbeat; });
|
||||
shell.printfln(F_(system_heartbeat_fmt), system_heartbeat ? F_(enabled) : F_(disabled));
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
||||
shell.printfln(F_(mqtt_heartbeat_fmt), settings.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.println();
|
||||
@@ -253,14 +253,19 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
|
||||
|
||||
bool cmd_known = false;
|
||||
JsonVariant data = doc["data"];
|
||||
|
||||
JsonObject output; // empty object
|
||||
|
||||
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>()) {
|
||||
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>()) {
|
||||
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) {
|
||||
@@ -336,8 +341,6 @@ void Mqtt::start() {
|
||||
mqtt_retain_ = mqttSettings.mqtt_retain;
|
||||
});
|
||||
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { bus_id_ = settings.ems_bus_id; });
|
||||
|
||||
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
|
||||
|
||||
mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
|
||||
@@ -443,7 +446,7 @@ void Mqtt::on_connect() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
doc["ip"] = WiFi.localIP().toString();
|
||||
#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
|
||||
|
||||
@@ -451,10 +454,6 @@ void Mqtt::on_connect() {
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
@@ -470,12 +469,14 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
|
||||
std::shared_ptr<MqttMessage> message;
|
||||
if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0)) {
|
||||
// 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 {
|
||||
// prefix the hostname
|
||||
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());
|
||||
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
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -516,17 +517,17 @@ void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string &
|
||||
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;
|
||||
serializeJson(payload, payload_text); // convert json to string
|
||||
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);
|
||||
}
|
||||
|
||||
void Mqtt::publish(const std::string & topic, const JsonDocument & payload) {
|
||||
void Mqtt::publish(const std::string & topic, const JsonObject & payload) {
|
||||
std::string payload_text;
|
||||
serializeJson(payload, payload_text); // convert json to string
|
||||
queue_publish_message(topic, payload_text, mqtt_retain_);
|
||||
|
||||
14
src/mqtt.h
14
src/mqtt.h
@@ -45,7 +45,7 @@ using uuid::console::Shell;
|
||||
namespace emsesp {
|
||||
|
||||
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 {
|
||||
~MqttMessage() = default;
|
||||
@@ -55,7 +55,8 @@ struct MqttMessage {
|
||||
const std::string payload;
|
||||
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)
|
||||
, topic(topic)
|
||||
, 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 publish(const std::string & topic, const std::string & payload);
|
||||
static void publish(const std::string & topic, const JsonDocument & payload);
|
||||
static void publish(const __FlashStringHelper * topic, const JsonDocument & payload);
|
||||
static void publish(const std::string & topic, const JsonObject & payload);
|
||||
static void publish(const __FlashStringHelper * topic, const JsonObject & 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 __FlashStringHelper * topi, const bool value);
|
||||
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 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_mqtt(uuid::console::Shell & shell);
|
||||
@@ -194,7 +195,6 @@ class Mqtt {
|
||||
static uint8_t mqtt_qos_;
|
||||
static bool mqtt_retain_;
|
||||
static uint32_t publish_time_;
|
||||
static uint8_t bus_id_;
|
||||
static uint32_t publish_time_boiler_;
|
||||
static uint32_t publish_time_thermostat_;
|
||||
static uint32_t publish_time_solar_;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
// code originally written by nomis - https://github.com/nomis
|
||||
|
||||
#include "sensors.h"
|
||||
#include "sensor.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
#ifdef ESP32
|
||||
@@ -29,10 +29,10 @@
|
||||
|
||||
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
|
||||
void Sensors::start() {
|
||||
void Sensor::start() {
|
||||
reload();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -40,10 +40,15 @@ void Sensors::start() {
|
||||
bus_.begin(dallas_gpio_);
|
||||
}
|
||||
#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
|
||||
void Sensors::reload() {
|
||||
void Sensor::reload() {
|
||||
// copy over values from MQTT so we don't keep on quering the filesystem
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
||||
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
|
||||
uint32_t time_now = uuid::get_uptime();
|
||||
|
||||
@@ -157,7 +162,7 @@ void Sensors::loop() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Sensors::temperature_convert_complete() {
|
||||
bool Sensor::temperature_convert_complete() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (parasite_) {
|
||||
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 ignored "-Wunused-parameter"
|
||||
|
||||
float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
float Sensor::get_temperature_c(const uint8_t addr[]) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!bus_.reset()) {
|
||||
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
|
||||
|
||||
const std::vector<Sensors::Device> Sensors::devices() const {
|
||||
const std::vector<Sensor::Device> Sensor::devices() const {
|
||||
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)
|
||||
| ((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_;
|
||||
}
|
||||
|
||||
std::string Sensors::Device::to_string() const {
|
||||
std::string Sensor::Device::to_string() const {
|
||||
std::string str(20, '\0');
|
||||
snprintf_P(&str[0],
|
||||
str.capacity() + 1,
|
||||
@@ -260,7 +265,7 @@ std::string Sensors::Device::to_string() const {
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Sensors::updated_values() {
|
||||
bool Sensor::updated_values() {
|
||||
if (changed_) {
|
||||
changed_ = false;
|
||||
return true;
|
||||
@@ -268,9 +273,35 @@ bool Sensors::updated_values() {
|
||||
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
|
||||
// assumes there are devices
|
||||
void Sensors::publish_values() {
|
||||
void Sensor::publish_values() {
|
||||
uint8_t num_devices = devices_.size();
|
||||
|
||||
if (num_devices == 0) {
|
||||
@@ -288,7 +319,7 @@ void Sensors::publish_values() {
|
||||
char topic[60]; // sensors{1-n}
|
||||
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);
|
||||
Mqtt::publish(topic, doc);
|
||||
Mqtt::publish(topic, doc.as<JsonObject>());
|
||||
doc.clear(); // clear json doc so we can reuse the buffer again
|
||||
}
|
||||
return;
|
||||
@@ -342,7 +373,7 @@ void Sensors::publish_values() {
|
||||
config["uniq_id"] = str;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -351,9 +382,10 @@ void Sensors::publish_values() {
|
||||
}
|
||||
|
||||
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) {
|
||||
Mqtt::publish(F("homeassistant/sensor/ems-esp/state"), doc);
|
||||
Mqtt::publish(F("homeassistant/sensor/ems-esp/state"), doc.as<JsonObject>());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
// code originally written by nomis - https://github.com/nomis
|
||||
|
||||
#ifndef EMSESP_SENSORS_H
|
||||
#define EMSESP_SENSORS_H
|
||||
#ifndef EMSESP_SENSOR_H
|
||||
#define EMSESP_SENSOR_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Sensors {
|
||||
class Sensor {
|
||||
public:
|
||||
class Device {
|
||||
public:
|
||||
@@ -53,8 +53,8 @@ class Sensors {
|
||||
bool registered_ = false;
|
||||
};
|
||||
|
||||
Sensors() = default;
|
||||
~Sensors() = default;
|
||||
Sensor() = default;
|
||||
~Sensor() = default;
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
@@ -62,6 +62,9 @@ class Sensors {
|
||||
void reload();
|
||||
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;
|
||||
|
||||
private:
|
||||
@@ -129,7 +129,7 @@ void Shower::publish_values() {
|
||||
doc["duration"] = s;
|
||||
}
|
||||
|
||||
Mqtt::publish(F("shower_data"), doc);
|
||||
Mqtt::publish(F("shower_data"), doc.as<JsonObject>());
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -152,8 +152,16 @@ void System::start() {
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read(
|
||||
[&](WiFiSettings & wifiSettings) { LOG_INFO(F("System %s booted (EMS-ESP version %s)"), wifiSettings.hostname.c_str(), EMSESP_APP_VERSION); });
|
||||
|
||||
syslog_init(); // init SysLog
|
||||
set_led(); // init LED
|
||||
syslog_init(); // init SysLog
|
||||
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
|
||||
}
|
||||
|
||||
@@ -241,7 +249,7 @@ void System::send_heartbeat() {
|
||||
doc["rxfails"] = EMSESP::rxservice_.telegram_error_count();
|
||||
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
|
||||
@@ -806,14 +814,14 @@ bool System::check_upgrade() {
|
||||
|
||||
// export all settings to JSON text
|
||||
// http://ems-esp/api?device=system&cmd=info
|
||||
String System::export_settings() {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
JsonObject root = doc.to<JsonObject>();
|
||||
// value and id are ignored
|
||||
bool System::command_info(const char * value, const int8_t id, JsonObject & output) {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
output["test"] = "testing";
|
||||
#else
|
||||
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) {
|
||||
JsonObject node = root.createNestedObject("WIFI");
|
||||
JsonObject node = output.createNestedObject("WIFI");
|
||||
node["ssid"] = settings.ssid;
|
||||
// node["password"] = settings.password;
|
||||
node["hostname"] = settings.hostname;
|
||||
@@ -826,7 +834,7 @@ String System::export_settings() {
|
||||
});
|
||||
|
||||
EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) {
|
||||
JsonObject node = root.createNestedObject("AP");
|
||||
JsonObject node = output.createNestedObject("AP");
|
||||
node["provision_mode"] = settings.provisionMode;
|
||||
node["ssid"] = settings.ssid;
|
||||
// node["password"] = settings.password;
|
||||
@@ -836,7 +844,7 @@ String System::export_settings() {
|
||||
});
|
||||
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
||||
JsonObject node = root.createNestedObject("MQTT");
|
||||
JsonObject node = output.createNestedObject("MQTT");
|
||||
node["enabled"] = settings.enabled;
|
||||
node["host"] = settings.host;
|
||||
node["port"] = settings.port;
|
||||
@@ -859,7 +867,7 @@ String System::export_settings() {
|
||||
});
|
||||
|
||||
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
|
||||
JsonObject node = root.createNestedObject("NTP");
|
||||
JsonObject node = output.createNestedObject("NTP");
|
||||
node["enabled"] = settings.enabled;
|
||||
node["server"] = settings.server;
|
||||
node["tz_label"] = settings.tzLabel;
|
||||
@@ -867,18 +875,33 @@ String System::export_settings() {
|
||||
});
|
||||
|
||||
EMSESP::esp8266React.getOTASettingsService()->read([&](OTASettings & settings) {
|
||||
JsonObject node = root.createNestedObject("OTA");
|
||||
JsonObject node = output.createNestedObject("OTA");
|
||||
node["enabled"] = settings.enabled;
|
||||
node["port"] = settings.port;
|
||||
// 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
|
||||
|
||||
String buffer;
|
||||
serializeJsonPretty(doc, buffer);
|
||||
|
||||
return buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -50,12 +50,13 @@ class System {
|
||||
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_info(const char * value, const int8_t id, JsonObject & output);
|
||||
|
||||
static uint8_t free_mem();
|
||||
static void upload_status(bool in_progress);
|
||||
static bool upload_status();
|
||||
static void show_mem(const char * note);
|
||||
static void set_led();
|
||||
static String export_settings();
|
||||
|
||||
bool check_upgrade();
|
||||
void syslog_init();
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace emsesp {
|
||||
// used with the 'test' command, under su/admin
|
||||
void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
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()) {
|
||||
@@ -565,6 +565,45 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
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") {
|
||||
shell.printfln(F("Testing pin..."));
|
||||
|
||||
@@ -579,6 +618,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
if (command == "mqtt") {
|
||||
shell.printfln(F("Testing MQTT..."));
|
||||
|
||||
// EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) {
|
||||
// mqttSettings.mqtt_format = MQTT_format::SINGLE;
|
||||
// return StateUpdateResult::CHANGED;
|
||||
// });
|
||||
|
||||
// add a boiler
|
||||
// question: do we need to set the mask?
|
||||
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)"
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user