mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 17:29:50 +03:00
Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev
This commit is contained in:
@@ -643,7 +643,7 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
// called from emsesp.cpp for commands
|
||||
// searches sensor by name
|
||||
bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int8_t id) {
|
||||
// check of it a 'commmands' command
|
||||
// check of it a 'commands' command
|
||||
if (Helpers::toLower(cmd) == F_(commands)) {
|
||||
return Command::list(EMSdevice::DeviceType::ANALOGSENSOR, output);
|
||||
}
|
||||
@@ -700,22 +700,18 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
|
||||
// if we're filtering on an attribute, go find it
|
||||
if (attribute_s) {
|
||||
if (output.containsKey(attribute_s)) {
|
||||
String data = output[attribute_s].as<String>();
|
||||
std::string data = output[attribute_s].as<std::string>();
|
||||
output.clear();
|
||||
output["api_data"] = data;
|
||||
output["api_data"] = data; // always as a string
|
||||
return true;
|
||||
} else {
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, sensor_name);
|
||||
output.clear();
|
||||
output["message"] = error;
|
||||
return false;
|
||||
}
|
||||
return EMSESP::return_not_found(output, "attribute", sensor_name); // not found
|
||||
}
|
||||
return true; // found a match, exit
|
||||
}
|
||||
}
|
||||
return false; // not found
|
||||
|
||||
return EMSESP::return_not_found(output, "analog sensor", cmd); // not found
|
||||
}
|
||||
|
||||
void AnalogSensor::addSensorJson(JsonObject output, const Sensor & sensor) {
|
||||
|
||||
@@ -79,7 +79,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
|
||||
uint8_t device_type = EMSdevice::device_name_2_device_type(device_s);
|
||||
if (!device_has_commands(device_type)) {
|
||||
LOG_DEBUG("Command failed: unknown device '%s'", device_s);
|
||||
return message(CommandRet::ERROR, "unknown device", output);
|
||||
return message(CommandRet::NOT_FOUND, "unknown device", output);
|
||||
}
|
||||
|
||||
// the next value on the path should be the command or entity name
|
||||
@@ -161,21 +161,25 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
|
||||
if (data_p == nullptr) {
|
||||
return CommandRet::INVALID;
|
||||
}
|
||||
|
||||
char data_s[COMMAND_MAX_LENGTH];
|
||||
strlcpy(data_s, Helpers::toLower(data_p).c_str(), 30);
|
||||
if (strstr(data_s, "/value") == nullptr) {
|
||||
strcat(data_s, "/value");
|
||||
}
|
||||
|
||||
uint8_t device_type = EMSdevice::device_name_2_device_type(device_p);
|
||||
if (CommandRet::OK != Command::call(device_type, data_s, "", true, id_d, output)) {
|
||||
return CommandRet::INVALID;
|
||||
}
|
||||
if (!output.containsKey("api_data")) {
|
||||
return CommandRet::INVALID;
|
||||
|
||||
const char * api_data = output["api_data"];
|
||||
if (api_data) {
|
||||
output.clear();
|
||||
return Command::call(device_type, command_p, api_data, is_admin, id_n, output);
|
||||
}
|
||||
String dat = output["api_data"].as<String>();
|
||||
output.clear();
|
||||
return Command::call(device_type, command_p, dat.c_str(), is_admin, id_n, output);
|
||||
|
||||
return CommandRet::INVALID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,11 +300,23 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
|
||||
if (cmd == nullptr) {
|
||||
return CommandRet::NOT_FOUND;
|
||||
}
|
||||
uint8_t return_code = CommandRet::OK;
|
||||
|
||||
auto dname = EMSdevice::device_type_2_device_name(device_type);
|
||||
auto dname = EMSdevice::device_type_2_device_name(device_type); // device name, not translated
|
||||
|
||||
// check first if there is only a command being called without a value
|
||||
// it could be an endpoint like a device's entity or attribute e.g. api/boiler/nrgheat or /api/boiler/nrgheat/value
|
||||
// or a special command like 'info', 'values', 'commands', 'entities' etc
|
||||
bool single_command = (!value || !strlen(value));
|
||||
if (single_command) {
|
||||
if (EMSESP::get_device_value_info(output, cmd, id, device_type)) { // entity = cmd
|
||||
LOG_DEBUG("Fetched device entity/attributes for %s/%s", dname, cmd);
|
||||
return CommandRet::OK;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t device_id = EMSESP::device_id_from_cmd(device_type, cmd, id);
|
||||
|
||||
// determine flags based on id (which is the tag)
|
||||
uint8_t flag = CommandFlag::CMD_FLAG_DEFAULT;
|
||||
int8_t tag = id;
|
||||
if (tag >= DeviceValueTAG::TAG_HC1 && tag <= DeviceValueTAG::TAG_HC8) {
|
||||
@@ -312,31 +328,19 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
|
||||
} else if (tag >= DeviceValueTAG::TAG_AHS1 && tag <= DeviceValueTAG::TAG_AHS1) {
|
||||
flag = CommandFlag::CMD_FLAG_AHS;
|
||||
}
|
||||
// see if there is a command registered
|
||||
|
||||
// first see if there is a command registered and it's valid
|
||||
auto cf = find_command(device_type, device_id, cmd, flag);
|
||||
|
||||
// check if its a call to an end-point of a device
|
||||
// this is used to fetch the attributes of the device entity, or call a command directly
|
||||
// for example info, values, commands, etc
|
||||
bool single_command = (!value || !strlen(value));
|
||||
if (single_command) {
|
||||
// exception: boiler coldshot command
|
||||
bool get_attributes = (!cf || !cf->cmdfunction_json_) && (strcmp(cmd, F_(coldshot)) != 0);
|
||||
|
||||
if (get_attributes) {
|
||||
LOG_DEBUG("Calling %s command '%s' to retrieve attributes", dname, cmd);
|
||||
return EMSESP::get_device_value_info(output, cmd, id, device_type) ? CommandRet::OK : CommandRet::ERROR; // entity = cmd
|
||||
}
|
||||
}
|
||||
|
||||
// check if we have a matching command
|
||||
if (!cf) {
|
||||
// we didn't find the command, report error
|
||||
LOG_WARNING("Command failed: invalid command '%s'", cmd ? cmd : "");
|
||||
return message(CommandRet::NOT_FOUND, "invalid command", output);
|
||||
// if we don't alread have a message set, set it to invalid command
|
||||
if (!output["message"]) {
|
||||
output["message"] = "invalid command";
|
||||
}
|
||||
return CommandRet::ERROR;
|
||||
}
|
||||
|
||||
// check permissions and abort if not authorized
|
||||
// before calling the command, check permissions and abort if not authorized
|
||||
if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !is_admin) {
|
||||
LOG_WARNING("Command failed: authentication failed");
|
||||
output["message"] = "authentication failed";
|
||||
@@ -353,7 +357,8 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
|
||||
} else {
|
||||
snprintf(info_s, sizeof(info_s), "'%s/%s'", dname, cmd);
|
||||
}
|
||||
if ((value == nullptr) || (strlen(value) == 0)) {
|
||||
|
||||
if (single_command) {
|
||||
LOG_DEBUG(("%sCalling command %s"), ro.c_str(), info_s);
|
||||
} else {
|
||||
if (id > 0) {
|
||||
@@ -364,6 +369,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
|
||||
}
|
||||
|
||||
// call the function based on type, either with a json package or no parameters
|
||||
uint8_t return_code = CommandRet::OK;
|
||||
if (cf->cmdfunction_json_) {
|
||||
// JSON
|
||||
return_code = ((cf->cmdfunction_json_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR;
|
||||
@@ -378,7 +384,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
|
||||
|
||||
// report back. If not OK show output from error, other return the HTTP code
|
||||
if (return_code != CommandRet::OK) {
|
||||
if ((value == nullptr) || (strlen(value) == 0)) {
|
||||
if (single_command) {
|
||||
LOG_ERROR("Command '%s' failed with error '%s'", cmd, FL_(cmdRet)[return_code]);
|
||||
} else {
|
||||
LOG_ERROR("Command '%s: %s' failed with error '%s'", cmd, value, FL_(cmdRet)[return_code]);
|
||||
@@ -543,7 +549,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
|
||||
// verbose mode
|
||||
shell.printfln("\n%s%s %s:%s", COLOR_BOLD_ON, COLOR_YELLOW, EMSdevice::device_type_2_device_name(device_type), COLOR_RESET);
|
||||
|
||||
// we hard code 'info' and 'commmands' commands so print them first
|
||||
// we hard code 'info' and 'commands' commands so print them first
|
||||
if (show_info) {
|
||||
shell.printf(" info:\t\t\t\t%slists all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
|
||||
shell.println(COLOR_RESET);
|
||||
@@ -641,7 +647,7 @@ void Command::show_devices(uuid::console::Shell & shell) {
|
||||
shell.println();
|
||||
}
|
||||
|
||||
// 'show commmands' : output list of all commands to console
|
||||
// 'show commands' : output list of all commands to console
|
||||
// calls show with verbose mode set
|
||||
void Command::show_all(uuid::console::Shell & shell) {
|
||||
shell.printfln("Showing all available commands (%s*%s=authentication not required):", COLOR_BRIGHT_GREEN, COLOR_RESET);
|
||||
|
||||
@@ -563,15 +563,21 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
|
||||
}
|
||||
}
|
||||
|
||||
if (return_code == CommandRet::OK && json.size()) {
|
||||
if (json.containsKey("api_data")) {
|
||||
String data = json["api_data"].as<String>();
|
||||
shell.println(data.c_str());
|
||||
if (return_code == CommandRet::OK) {
|
||||
if (json.size()) {
|
||||
if (json.containsKey("api_data")) {
|
||||
String data = json["api_data"].as<String>();
|
||||
shell.println(data.c_str());
|
||||
return;
|
||||
}
|
||||
serializeJsonPretty(doc, shell);
|
||||
shell.println();
|
||||
return;
|
||||
} else {
|
||||
// show message if no data returned (e.g. for analogsensor, temperaturesensor, custom)
|
||||
shell.println("No data.");
|
||||
return;
|
||||
}
|
||||
serializeJsonPretty(doc, shell);
|
||||
shell.println();
|
||||
return;
|
||||
}
|
||||
|
||||
if (return_code == CommandRet::NOT_FOUND) {
|
||||
@@ -637,7 +643,11 @@ void EMSESPShell::stopped() {
|
||||
void EMSESPShell::display_banner() {
|
||||
println();
|
||||
printfln("┌───────────────────────────────────────┐");
|
||||
#ifndef EMSESP_DEBUG
|
||||
printfln("│ %sEMS-ESP version %-20s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF);
|
||||
#else
|
||||
printfln("│ %sEMS-ESP version %s%-8s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, " (D)", COLOR_BOLD_OFF);
|
||||
#endif
|
||||
printfln("│ │");
|
||||
printfln("│ %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET);
|
||||
printfln("│ %ssu%s to access admin commands │", COLOR_UNDERLINE, COLOR_RESET);
|
||||
@@ -685,6 +695,9 @@ void EMSESPShell::end_of_transmission() {
|
||||
}
|
||||
|
||||
void EMSESPShell::main_help_function(Shell & shell, const std::vector<std::string> & arguments) {
|
||||
shell.println();
|
||||
shell.printfln("%s%sEMS-ESP version %s%s", COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_RESET);
|
||||
shell.println();
|
||||
shell.print_all_available_commands();
|
||||
}
|
||||
|
||||
|
||||
@@ -189,6 +189,11 @@
|
||||
#define EMSESP_DEFAULT_PUBLISH_TIME 10
|
||||
#endif
|
||||
|
||||
// default for scheduler etc
|
||||
#ifndef EMSESP_DEFAULT_PUBLISH_TIME_OTHER
|
||||
#define EMSESP_DEFAULT_PUBLISH_TIME_OTHER 60
|
||||
#endif
|
||||
|
||||
#ifndef EMSESP_DEFAULT_PUBLISH_HEARTBEAT
|
||||
#define EMSESP_DEFAULT_PUBLISH_HEARTBEAT 60
|
||||
#endif
|
||||
|
||||
@@ -100,7 +100,7 @@ const char * EMSdevice::brand_to_char() {
|
||||
}
|
||||
}
|
||||
|
||||
// returns the short name of the device, used in MQTT and console commands, all lowercase
|
||||
// returns the short name of the device, used in MQTT and console commands, all lowercase, no translated
|
||||
const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) {
|
||||
switch (device_type) {
|
||||
case DeviceType::SYSTEM:
|
||||
@@ -1424,7 +1424,7 @@ void EMSdevice::dump_telegram_info(std::vector<TelegramFunctionDump> & telegram_
|
||||
}
|
||||
#endif
|
||||
|
||||
// builds json for a specific device value / entity
|
||||
// builds json for a specific EMS device value / entity
|
||||
// cmd is the endpoint or name of the device entity
|
||||
// returns false if failed, otherwise true
|
||||
bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t tag) {
|
||||
@@ -1585,6 +1585,9 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
|
||||
json["writeable"] = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
|
||||
json["visible"] = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
|
||||
|
||||
// TODO refactor to remove containsKey as it's costly and not advisable to use it
|
||||
// https://arduinojson.org/v7/api/jsonobject/containskey/#avoid
|
||||
|
||||
// commented out, leads to issues if type is set to number
|
||||
// if there is no value, mention it
|
||||
// if (!json.containsKey(value)) {
|
||||
@@ -1594,30 +1597,22 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
|
||||
// if we're filtering on an attribute, go find it
|
||||
if (attribute_s) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("Attribute '%s'", attribute_s);
|
||||
EMSESP::logger().debug("[DEBUG] fetching single attribute '%s'", attribute_s);
|
||||
#endif
|
||||
if (json.containsKey(attribute_s)) {
|
||||
String data = json[attribute_s].as<String>();
|
||||
std::string data = json[attribute_s].as<std::string>();
|
||||
output.clear();
|
||||
output["api_data"] = data;
|
||||
output["api_data"] = data; // always as string
|
||||
return true;
|
||||
} else {
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
|
||||
output.clear();
|
||||
output["message"] = error;
|
||||
return false;
|
||||
}
|
||||
return EMSESP::return_not_found(output, "attribute", command_s); // not found
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "cannot find values for entity '%s'", cmd);
|
||||
json["message"] = error;
|
||||
return false;
|
||||
return false; // not found, but don't return a message error yet
|
||||
}
|
||||
|
||||
// mqtt publish all single values from one device (used for time schedule)
|
||||
@@ -1949,7 +1944,7 @@ std::string EMSdevice::name() {
|
||||
// returns true on success.
|
||||
int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result) {
|
||||
// find device value by shortname
|
||||
// TODO linear search is inefficient
|
||||
// TODO replace linear search which is inefficient
|
||||
const auto & it = std::find_if(devicevalues_.begin(), devicevalues_.end(), [&](const DeviceValue & x) { return x.tag == tag && x.short_name == shortname; });
|
||||
if (it == devicevalues_.end())
|
||||
return -1;
|
||||
|
||||
@@ -563,6 +563,7 @@ void EMSESP::publish_all(bool force) {
|
||||
publish_device_values(EMSdevice::DeviceType::WATER);
|
||||
publish_other_values(); // switch and heat pump, ...
|
||||
publish_sensor_values(true); // includes temperature and analog sensors
|
||||
|
||||
system_.send_heartbeat();
|
||||
}
|
||||
}
|
||||
@@ -683,10 +684,11 @@ void EMSESP::publish_other_values() {
|
||||
publish_device_values(EMSdevice::DeviceType::EXTENSION);
|
||||
publish_device_values(EMSdevice::DeviceType::ALERT);
|
||||
publish_device_values(EMSdevice::DeviceType::POOL);
|
||||
// other devices without values yet
|
||||
// other EMS devices without values yet
|
||||
// publish_device_values(EMSdevice::DeviceType::GATEWAY);
|
||||
// publish_device_values(EMSdevice::DeviceType::CONNECT);
|
||||
// publish_device_values(EMSdevice::DeviceType::GENERIC);
|
||||
|
||||
webSchedulerService.publish();
|
||||
webCustomEntityService.publish();
|
||||
}
|
||||
@@ -740,19 +742,19 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
|
||||
buffer = nullptr;
|
||||
}
|
||||
|
||||
// builds json with the detail of each value, for an EMS device
|
||||
// builds json with the detail of each value, for a given EMS device
|
||||
// for other types like sensors, scheduler, custom entities it will process single commands like 'info', 'values', 'commands'...
|
||||
bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8_t id, const uint8_t devicetype) {
|
||||
// check first for EMS devices
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice->device_type() == devicetype) {
|
||||
if (emsdevice->get_value_info(root, cmd, id)) {
|
||||
return true;
|
||||
}
|
||||
return emsdevice->get_value_info(root, cmd, id);
|
||||
}
|
||||
}
|
||||
|
||||
// temperaturesensor
|
||||
// check for other devices...
|
||||
|
||||
// temperature sensor
|
||||
if (devicetype == DeviceType::TEMPERATURESENSOR) {
|
||||
return temperaturesensor_.get_value_info(root, cmd, id);
|
||||
}
|
||||
@@ -772,14 +774,20 @@ bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8
|
||||
return webCustomEntityService.get_value_info(root, cmd);
|
||||
}
|
||||
|
||||
// system
|
||||
if (devicetype == DeviceType::SYSTEM) {
|
||||
return system_.get_value_info(root, cmd);
|
||||
}
|
||||
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "cannot find values for entity '%s'", cmd);
|
||||
root["message"] = error;
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
// sends JSON error message, used with API calls
|
||||
bool EMSESP::return_not_found(JsonObject output, const char * msg, const char * cmd) {
|
||||
output.clear();
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "cannot find %s in '%s'", msg, cmd);
|
||||
output["message"] = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1617,6 +1625,8 @@ void EMSESP::start() {
|
||||
if (!nvs_.begin("ems-esp", false, "nvs1")) { // try bigger nvs partition on 16M flash first
|
||||
nvs_.begin("ems-esp", false, "nvs"); // fallback to small nvs
|
||||
}
|
||||
LOG_DEBUG("NVS device information: %s", system_.getBBQKeesGatewayDetails().c_str());
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
LOG_INFO("Starting EMS-ESP version %s from %s partition", EMSESP_APP_VERSION, esp_ota_get_running_partition()->label); // welcome message
|
||||
#else
|
||||
@@ -1631,6 +1641,8 @@ void EMSESP::start() {
|
||||
system_.system_restart();
|
||||
};
|
||||
|
||||
|
||||
|
||||
webSettingsService.begin(); // load EMS-ESP Application settings...
|
||||
|
||||
// do any system upgrades
|
||||
|
||||
@@ -213,6 +213,8 @@ class EMSESP {
|
||||
static void scan_devices();
|
||||
static void clear_all_devices();
|
||||
|
||||
static bool return_not_found(JsonObject output, const char * msg, const char * cmd);
|
||||
|
||||
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
|
||||
|
||||
// services
|
||||
|
||||
@@ -67,6 +67,7 @@ MAKE_WORD(publish)
|
||||
MAKE_WORD(board_profile)
|
||||
MAKE_WORD(setvalue)
|
||||
MAKE_WORD(service)
|
||||
MAKE_WORD(message)
|
||||
|
||||
// for commands
|
||||
MAKE_WORD(call)
|
||||
|
||||
@@ -75,6 +75,7 @@ MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entit
|
||||
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "", "", "", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(allvalues_cmd, "output all values", "", "", "", "wyświetl wszystkie wartości", "", "", "", "", "vypísať všetky hodnoty") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(message_cmd, "send a message", "", "", "", "", "", "", "", "", "") // TODO translate
|
||||
|
||||
// tags
|
||||
MAKE_WORD_TRANSLATION(tag_hc1, "hc1", "HK1", "hc1", "VK1", "OG1", "hc1", "hc1", "ID1", "hc1", "hc1")
|
||||
|
||||
@@ -68,7 +68,7 @@ bool Modbus::check_parameter_order() {
|
||||
} else if (prev == nullptr) {
|
||||
LOG_ERROR("Error checking modbus parameters %s.", mi.short_name);
|
||||
return false;
|
||||
} else if(!prev->isLessThan(mi)) {
|
||||
} else if (!prev->isLessThan(mi)) {
|
||||
LOG_ERROR("Error in modbus parameters: %s must be listed before %s.", mi.short_name, prev->short_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
#include "modbus.h"
|
||||
#include "emsdevice.h"
|
||||
|
||||
/*
|
||||
* This file is auto-generated by the update_modbus_registers.sh script. Do not modify.
|
||||
*/
|
||||
|
||||
// clang-format off
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using dt = EMSdevice::DeviceType;
|
||||
@@ -513,3 +519,6 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
// clang-format off
|
||||
|
||||
|
||||
132
src/system.cpp
132
src/system.cpp
@@ -226,6 +226,18 @@ bool System::command_syslog_level(const char * value, const int8_t id) {
|
||||
}
|
||||
*/
|
||||
|
||||
// send message - to log and MQTT
|
||||
bool System::command_message(const char * value, const int8_t id) {
|
||||
if (value == nullptr || value[0] == '\0') {
|
||||
return false; // must have a string value
|
||||
}
|
||||
|
||||
LOG_INFO("Message: %s", value);
|
||||
Mqtt::queue_publish(F_(message), value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// watch
|
||||
bool System::command_watch(const char * value, const int8_t id) {
|
||||
uint8_t w = 0xff;
|
||||
@@ -831,10 +843,10 @@ void System::commands_init() {
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY);
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY);
|
||||
|
||||
// restart and watch (and test) are also exposed as Console commands
|
||||
// restart, watch, message (and test) are also exposed as Console commands
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, FL_(watch_cmd));
|
||||
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd));
|
||||
#if defined(EMSESP_TEST)
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), System::command_test, FL_(test_cmd));
|
||||
#endif
|
||||
@@ -1107,7 +1119,7 @@ bool System::check_restore() {
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// see if we have a temp file, if so try and read it
|
||||
// TODO find a nicer way to see if a file exists without reporting an error
|
||||
// TODO find a nicer way to see if a file exists without reporting an error, like using lfs_stat. exists() uses open so same problem.
|
||||
File new_file = LittleFS.open(TEMP_FILENAME_PATH);
|
||||
if (new_file) {
|
||||
JsonDocument jsonDocument;
|
||||
@@ -1288,52 +1300,61 @@ bool System::get_value_info(JsonObject root, const char * command) {
|
||||
LOG_ERROR("empty system command");
|
||||
return false;
|
||||
}
|
||||
|
||||
char cmd[COMMAND_MAX_LENGTH];
|
||||
strlcpy(cmd, command, sizeof(cmd));
|
||||
char * val = strstr(cmd, "/value");
|
||||
if (val) {
|
||||
val[0] = '\0';
|
||||
}
|
||||
char * dash = strchr(cmd, '/');
|
||||
if (dash) {
|
||||
*dash = '\0';
|
||||
dash++;
|
||||
|
||||
char * slash = strchr(cmd, '/');
|
||||
if (slash) {
|
||||
*slash = '\0';
|
||||
slash++;
|
||||
}
|
||||
if (command_info("", 0, root)) {
|
||||
std::string s;
|
||||
// Loop through all the key-value pairs in root to find the key case independent
|
||||
if (dash) { // search the nest first
|
||||
for (JsonPair p : root) {
|
||||
if (p.value().is<JsonObject>() && Helpers::toLower(p.key().c_str()) == cmd) {
|
||||
for (JsonPair p1 : p.value().as<JsonObject>()) {
|
||||
if (Helpers::toLower(p1.key().c_str()) == dash && !p1.value().is<JsonObject>()) {
|
||||
s = p1.value().as<std::string>();
|
||||
break;
|
||||
}
|
||||
|
||||
// fetch all the data from the system
|
||||
(void)command_info("", 0, root);
|
||||
|
||||
// check for hardcoded "info"
|
||||
if (Helpers::toLower(cmd) == F_(info)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string s;
|
||||
// Loop through all the key-value pairs in root to find the key, case independent
|
||||
if (slash) { // search the top level first
|
||||
for (JsonPair p : root) {
|
||||
if (p.value().is<JsonObject>() && Helpers::toLower(p.key().c_str()) == cmd) {
|
||||
for (JsonPair p1 : p.value().as<JsonObject>()) {
|
||||
if (Helpers::toLower(p1.key().c_str()) == slash && !p1.value().is<JsonObject>()) {
|
||||
s = p1.value().as<std::string>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (JsonPair p : root) {
|
||||
if (Helpers::toLower(p.key().c_str()) == cmd && !p.value().is<JsonObject>()) {
|
||||
s = p.value().as<std::string>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s.empty()) {
|
||||
root.clear();
|
||||
if (val) {
|
||||
root["api_data"] = s;
|
||||
} else {
|
||||
root["value"] = s;
|
||||
} else {
|
||||
for (JsonPair p : root) {
|
||||
if (Helpers::toLower(p.key().c_str()) == cmd && !p.value().is<JsonObject>()) {
|
||||
s = p.value().as<std::string>();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
root.clear();
|
||||
LOG_ERROR("system command '%s' not found", command);
|
||||
return false;
|
||||
|
||||
if (!s.empty()) {
|
||||
root.clear();
|
||||
if (val) {
|
||||
root["api_data"] = s;
|
||||
} else {
|
||||
root["value"] = s;
|
||||
}
|
||||
return true; // found
|
||||
}
|
||||
|
||||
return EMSESP::return_not_found(root, "data", command); // not found
|
||||
}
|
||||
|
||||
// export status information including the device information
|
||||
@@ -1342,7 +1363,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
JsonObject node;
|
||||
|
||||
// System
|
||||
node = output["System Info"].to<JsonObject>();
|
||||
node = output["System"].to<JsonObject>();
|
||||
node["version"] = EMSESP_APP_VERSION;
|
||||
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
|
||||
node["uptime (seconds)"] = uuid::get_uptime_sec();
|
||||
@@ -1359,9 +1380,9 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
#endif
|
||||
node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// Network Status
|
||||
node = output["Network Info"].to<JsonObject>();
|
||||
node = output["Network"].to<JsonObject>();
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (EMSESP::system_.ethernet_connected()) {
|
||||
node["network"] = "Ethernet";
|
||||
node["hostname"] = ETH.getHostname();
|
||||
@@ -1384,10 +1405,15 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
// node["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6());
|
||||
// }
|
||||
}
|
||||
#else
|
||||
// for testing
|
||||
node["network"] = "WiFi";
|
||||
node["hostname"] = "ems-esp";
|
||||
node["RSSI"] = -23;
|
||||
#endif
|
||||
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
|
||||
if (WiFi.status() == WL_CONNECTED && !settings.bssid.isEmpty()) {
|
||||
node["BSSID"] = "set";
|
||||
node["BSSID"] = "set"; // TODO why is this not the actual value?
|
||||
}
|
||||
node["TxPower setting"] = settings.tx_power;
|
||||
node["static ip config"] = settings.staticIPConfig;
|
||||
@@ -1409,7 +1435,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
#endif
|
||||
|
||||
// NTP status
|
||||
node = output["NTP Info"].to<JsonObject>();
|
||||
node = output["NTP"].to<JsonObject>();
|
||||
#ifndef EMSESP_STANDALONE
|
||||
node["NTP status"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
|
||||
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
|
||||
@@ -1421,7 +1447,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
#endif
|
||||
|
||||
// MQTT Status
|
||||
node = output["MQTT Info"].to<JsonObject>();
|
||||
node = output["MQTT"].to<JsonObject>();
|
||||
node["MQTT status"] = Mqtt::connected() ? F_(connected) : F_(disconnected);
|
||||
if (Mqtt::enabled()) {
|
||||
node["MQTT publishes"] = Mqtt::publish_count();
|
||||
@@ -1456,7 +1482,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
});
|
||||
|
||||
// Syslog Status
|
||||
node = output["Syslog Info"].to<JsonObject>();
|
||||
node = output["Syslog"].to<JsonObject>();
|
||||
node["enabled"] = EMSESP::system_.syslog_enabled_;
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (EMSESP::system_.syslog_enabled_) {
|
||||
@@ -1468,7 +1494,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
#endif
|
||||
|
||||
// Sensor Status
|
||||
node = output["Sensor Info"].to<JsonObject>();
|
||||
node = output["Sensor"].to<JsonObject>();
|
||||
if (EMSESP::sensor_enabled()) {
|
||||
node["temperature sensors"] = EMSESP::temperaturesensor_.no_sensors();
|
||||
node["temperature sensor reads"] = EMSESP::temperaturesensor_.reads();
|
||||
@@ -1481,12 +1507,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
}
|
||||
|
||||
// API Status
|
||||
node = output["API Info"].to<JsonObject>();
|
||||
node = output["API"].to<JsonObject>();
|
||||
node["API calls"] = WebAPIService::api_count();
|
||||
node["API fails"] = WebAPIService::api_fails();
|
||||
|
||||
// EMS Bus Status
|
||||
node = output["Bus Info"].to<JsonObject>();
|
||||
node = output["Bus"].to<JsonObject>();
|
||||
switch (EMSESP::bus_status()) {
|
||||
case EMSESP::BUS_STATUS_OFFLINE:
|
||||
node["bus status"] = "disconnected";
|
||||
@@ -1589,7 +1615,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true; // this function always returns true!
|
||||
}
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
@@ -1728,4 +1754,18 @@ bool System::ntp_connected() {
|
||||
return ntp_connected_;
|
||||
}
|
||||
|
||||
String System::getBBQKeesGatewayDetails() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!EMSESP::nvs_.isKey("mfg")) {
|
||||
return "";
|
||||
}
|
||||
if (EMSESP::nvs_.getString("mfg") != "BBQKees") {
|
||||
return "";
|
||||
}
|
||||
return "BBQKees Gateway Model " + EMSESP::nvs_.getString("model") + " v" + EMSESP::nvs_.getString("hwrevision") + "/" + EMSESP::nvs_.getString("batch");
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -57,6 +57,7 @@ class System {
|
||||
static bool command_restart(const char * value, const int8_t id);
|
||||
static bool command_syslog_level(const char * value, const int8_t id);
|
||||
static bool command_watch(const char * value, const int8_t id);
|
||||
static bool command_message(const char * value, const int8_t id);
|
||||
static bool command_info(const char * value, const int8_t id, JsonObject output);
|
||||
static bool command_commands(const char * value, const int8_t id, JsonObject output);
|
||||
static bool command_response(const char * value, const int8_t id, JsonObject output);
|
||||
@@ -97,6 +98,8 @@ class System {
|
||||
}
|
||||
#endif
|
||||
|
||||
String getBBQKeesGatewayDetails();
|
||||
|
||||
void led_init(bool refresh);
|
||||
void network_init(bool refresh);
|
||||
void button_init(bool refresh);
|
||||
|
||||
@@ -343,13 +343,14 @@ bool TemperatureSensor::updated_values() {
|
||||
|
||||
// called from emsesp.cpp for commands
|
||||
bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, const int8_t id) {
|
||||
// check of it a 'commmands' command
|
||||
// check of it a 'commands' command
|
||||
if (Helpers::toLower(cmd) == F_(commands)) {
|
||||
return Command::list(EMSdevice::DeviceType::TEMPERATURESENSOR, output);
|
||||
}
|
||||
|
||||
// return empty json if there are no sensors
|
||||
if (sensors_.empty()) {
|
||||
return true; // no sensors, return true
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t show_all = 0;
|
||||
@@ -391,28 +392,23 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
|
||||
for (const auto & sensor : sensors_) {
|
||||
// match custom name or sensor ID
|
||||
if (sensor_name == Helpers::toLower(sensor.name()) || sensor_name == Helpers::toLower(sensor.id())) {
|
||||
// add values
|
||||
// add all the data elements
|
||||
addSensorJson(output, sensor);
|
||||
// if we're filtering on an attribute, go find it
|
||||
if (attribute_s) {
|
||||
if (output.containsKey(attribute_s)) {
|
||||
String data = output[attribute_s].as<String>();
|
||||
std::string data = output[attribute_s].as<std::string>();
|
||||
output.clear();
|
||||
output["api_data"] = data;
|
||||
output["api_data"] = data; // always as string
|
||||
return true;
|
||||
} else {
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, sensor_name);
|
||||
output.clear();
|
||||
output["message"] = error;
|
||||
return false;
|
||||
}
|
||||
return EMSESP::return_not_found(output, "attribute", sensor_name); // not found
|
||||
}
|
||||
return true; // found a match, exit
|
||||
}
|
||||
}
|
||||
|
||||
return false; // not found
|
||||
return EMSESP::return_not_found(output, "temperature sensor", cmd); // not found
|
||||
}
|
||||
|
||||
void TemperatureSensor::addSensorJson(JsonObject output, const Sensor & sensor) {
|
||||
@@ -629,15 +625,15 @@ void TemperatureSensor::test() {
|
||||
uint8_t addr[ADDR_LEN] = {1, 2, 3, 4, 5, 6, 7, 8};
|
||||
sensors_.emplace_back(addr);
|
||||
sensors_.back().apply_customization();
|
||||
sensors_.back().temperature_c = 123;
|
||||
sensors_.back().temperature_c = 123; // 12.3
|
||||
sensors_.back().read = true;
|
||||
publish_sensor(sensors_.back()); // call publish single
|
||||
|
||||
// Sensor ID: 0B-0C0D-0E0F-1011
|
||||
// Sensor ID: 0B_0C0D_0E0F_1011
|
||||
uint8_t addr2[ADDR_LEN] = {11, 12, 13, 14, 15, 16, 17, 18};
|
||||
sensors_.emplace_back(addr2);
|
||||
sensors_.back().apply_customization();
|
||||
sensors_.back().temperature_c = 456;
|
||||
sensors_.back().temperature_c = 456; // 45.6
|
||||
sensors_.back().read = true;
|
||||
publish_sensor(sensors_.back()); // call publish single
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class TemperatureSensor {
|
||||
bool apply_customization();
|
||||
|
||||
// initial values
|
||||
int16_t temperature_c = EMS_VALUE_INT16_NOTSET;
|
||||
int16_t temperature_c = EMS_VALUE_INT16_NOTSET; // value is *10
|
||||
bool read = false;
|
||||
bool ha_registered = false;
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
shell.printfln("Testing adding a boiler, thermostat, all sensors, scheduler and custom entities...");
|
||||
|
||||
// setup fake data
|
||||
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the file
|
||||
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
|
||||
|
||||
// add devices
|
||||
test("general");
|
||||
@@ -324,7 +324,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
EMSESP::webSchedulerService.test(); // add scheduler items
|
||||
EMSESP::webCustomEntityService.test(); // add custom entities
|
||||
|
||||
shell.invoke_command("show devices");
|
||||
// shell.invoke_command("show devices");
|
||||
// shell.invoke_command("show values");
|
||||
// shell.invoke_command("call system allvalues");
|
||||
// shell.invoke_command("call system publish");
|
||||
@@ -944,37 +944,149 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
ok = true;
|
||||
}
|
||||
|
||||
if (command == "api_values") {
|
||||
shell.printfln("Testing API getting values");
|
||||
Mqtt::ha_enabled(false);
|
||||
Mqtt::nested_format(1);
|
||||
// Mqtt::send_response(false);
|
||||
// EMSESP::bool_format(BOOL_FORMAT_10); // BOOL_FORMAT_10_STR
|
||||
if (command == "api3") {
|
||||
shell.printfln("Testing API getting values from system");
|
||||
EMSESP::system_.bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR
|
||||
|
||||
test("boiler");
|
||||
test("thermostat");
|
||||
ok = true;
|
||||
|
||||
bool single;
|
||||
|
||||
// single = true;
|
||||
single = false;
|
||||
|
||||
AsyncWebServerRequest request;
|
||||
JsonDocument doc;
|
||||
JsonVariant json;
|
||||
request.method(HTTP_GET);
|
||||
|
||||
request.url("/api/boiler/values");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/dhw/circ");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/dhw/circ/fullname");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/selburnpow/value");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/dhw/chargetype/writeable");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/flamecurr/value");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/flamecurr/bad");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
ok = true;
|
||||
// load devices
|
||||
test("boiler");
|
||||
// test("thermostat");
|
||||
|
||||
if (single) {
|
||||
// run dedicated tests only
|
||||
EMSESP::webCustomEntityService.test(); // custom entities
|
||||
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
|
||||
EMSESP::temperaturesensor_.test(); // add temperature sensors
|
||||
EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions
|
||||
|
||||
// request.url("/api/analogsensor/test_analog10/bad");
|
||||
// EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
} else {
|
||||
EMSESP::webCustomEntityService.test(); // custom entities
|
||||
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
|
||||
EMSESP::temperaturesensor_.test(); // add temperature sensors
|
||||
EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions
|
||||
|
||||
// boiler
|
||||
request.url("/api/boiler");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/commands");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/values");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/info");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/entities");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/comfort");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/comfort/value");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/comfort/fullname");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/outdoortemp");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/dhw/chargetype/writeable");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/flamecurr/value");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// custom
|
||||
request.url("/api/custom");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/custom/seltemp");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// system
|
||||
request.url("/api/system");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/system/settings/locale");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// scheduler
|
||||
request.url("/api/scheduler/info");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/scheduler/test_scheduler");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// temperaturesensor
|
||||
request.url("/api/temperaturesensor/test_sensor2");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/temperaturesensor/0B_0C0D_0E0F_1011");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/temperaturesensor/test_sensor2/value");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// analogsensor
|
||||
request.url("/api/analogsensor");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/analogsensor/test_analog1");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/analogsensor/test_analog1/offset");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
//
|
||||
// This next batch should all fail
|
||||
//
|
||||
|
||||
Serial.printf("%s**** Testing bad urls ****\n%s", COLOR_RED, COLOR_RESET);
|
||||
Serial.println();
|
||||
|
||||
// boiler
|
||||
request.url("/api/boiler/bad");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/bad/value");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler2/bad");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/bad");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/bad/value");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/boiler/comfort/valu");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// system
|
||||
request.url("/api/system/settings/locale2");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// scheduler
|
||||
request.url("/api/scheduler/test_scheduler2");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// custom
|
||||
request.url("/api/custom/seltemp2");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// temperaturesensor
|
||||
request.url("/api/temperaturesensor/test_sensor20");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/temperaturesensor/0B_0C0D_0E0F_XXXX");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/temperaturesensor/test_sensor2/bad");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// analogsensor
|
||||
request.url("/api/analogsensor/test_analog1/bad");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/analogsensor/test_analog10");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/analogsensor/test_analog10/bad");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
}
|
||||
}
|
||||
|
||||
if (command == "mqtt_post") {
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace emsesp {
|
||||
// #define EMSESP_DEBUG_DEFAULT "310"
|
||||
// #define EMSESP_DEBUG_DEFAULT "render"
|
||||
// #define EMSESP_DEBUG_DEFAULT "api"
|
||||
#define EMSESP_DEBUG_DEFAULT "api3"
|
||||
// #define EMSESP_DEBUG_DEFAULT "crash"
|
||||
// #define EMSESP_DEBUG_DEFAULT "dv"
|
||||
// #define EMSESP_DEBUG_DEFAULT "lastcode"
|
||||
// #define EMSESP_DEBUG_DEFAULT "2thermostats"
|
||||
// #define EMSESP_DEBUG_DEFAULT "temperature"
|
||||
// #define EMSESP_DEBUG_DEFAULT "analog"
|
||||
// #define EMSESP_DEBUG_DEFAULT "api_values"
|
||||
// #define EMSESP_DEBUG_DEFAULT "mqtt_post"
|
||||
// #define EMSESP_DEBUG_DEFAULT "api_wwmode"
|
||||
// #define EMSESP_DEBUG_DEFAULT "customization"
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.7.0-dev.23"
|
||||
#define EMSESP_APP_VERSION "3.7.0-dev.25"
|
||||
|
||||
@@ -128,9 +128,9 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
if (return_code != CommandRet::OK) {
|
||||
char error[100];
|
||||
if (output.size()) {
|
||||
snprintf(error, sizeof(error), "API failed with error %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str());
|
||||
snprintf(error, sizeof(error), "API call failed. %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str());
|
||||
} else {
|
||||
snprintf(error, sizeof(error), "API failed with error %s", Command::return_code_string(return_code).c_str());
|
||||
snprintf(error, sizeof(error), "API call failed (%s)", Command::return_code_string(return_code).c_str());
|
||||
}
|
||||
emsesp::EMSESP::logger().err(error);
|
||||
api_fails_++;
|
||||
@@ -138,17 +138,27 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
|
||||
// if we're returning single values, just sent as plain text
|
||||
// https://github.com/emsesp/EMS-ESP32/issues/462#issuecomment-1093877210
|
||||
if (output.containsKey("api_data")) {
|
||||
String data = output["api_data"].as<String>();
|
||||
request->send(200, "text/plain; charset=utf-8", data);
|
||||
const char * api_data = output["api_data"];
|
||||
if (api_data) {
|
||||
request->send(200, "text/plain; charset=utf-8", api_data);
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
Serial.printf("%sweb output: %s[%s] %s(200)%s ", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str(), COLOR_BRIGHT_GREEN, COLOR_MAGENTA);
|
||||
serializeJson(output, Serial);
|
||||
Serial.println(COLOR_RESET);
|
||||
Serial.println();
|
||||
#endif
|
||||
api_count_++;
|
||||
delete response;
|
||||
return;
|
||||
}
|
||||
|
||||
// send the json that came back from the command call
|
||||
// FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED = 400 (bad request), 200 (OK), 400 (not found), 400 (bad request), 401 (unauthorized)
|
||||
int ret_codes[6] = {400, 200, 400, 400, 401, 400};
|
||||
// sequence is FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED, INVALID
|
||||
// 400 (bad request)
|
||||
// 200 (OK)
|
||||
// 404 (not found)
|
||||
// 401 (unauthorized)
|
||||
int ret_codes[6] = {400, 200, 404, 400, 401, 400};
|
||||
response->setCode(ret_codes[return_code]);
|
||||
response->setLength();
|
||||
response->setContentType("application/json; charset=utf-8");
|
||||
@@ -156,15 +166,11 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
api_count_++;
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
Serial.print(COLOR_YELLOW);
|
||||
Serial.print("data: ");
|
||||
if (output.size()) {
|
||||
serializeJson(output, Serial);
|
||||
}
|
||||
Serial.print(" (response code ");
|
||||
Serial.print(ret_codes[return_code]);
|
||||
Serial.println(")");
|
||||
Serial.print(COLOR_RESET);
|
||||
Serial.printf("%sweb output: %s[%s]", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str());
|
||||
Serial.printf(" %s(%d)%s ", ret_codes[return_code] == 200 ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_RED, ret_codes[return_code], COLOR_YELLOW);
|
||||
serializeJson(output, Serial);
|
||||
Serial.println(COLOR_RESET);
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -237,13 +237,12 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem en
|
||||
}
|
||||
break;
|
||||
case DeviceValueType::STRING:
|
||||
default:
|
||||
// if no type treat it as a string
|
||||
if (entity.data.length() > 0) {
|
||||
output[name] = entity.data;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// EMSESP::logger().warning("unknown value type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +268,8 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
return true;
|
||||
}
|
||||
|
||||
// if no entries, return empty json
|
||||
// if no custom entries, return empty json
|
||||
// even if we're looking for a specific entity
|
||||
// https://github.com/emsesp/EMS-ESP32/issues/1297
|
||||
if (customEntityItems_->size() == 0) {
|
||||
return true;
|
||||
@@ -287,6 +287,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
char command_s[COMMAND_MAX_LENGTH];
|
||||
strlcpy(command_s, Helpers::toLower(cmd).c_str(), sizeof(command_s));
|
||||
char * attribute_s = nullptr;
|
||||
|
||||
// check specific attribute to fetch instead of the complete record
|
||||
char * breakp = strchr(command_s, '/');
|
||||
if (breakp) {
|
||||
@@ -315,21 +316,16 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
output["bytes"] = (uint8_t)entity.factor;
|
||||
}
|
||||
}
|
||||
render_value(output, entity, true);
|
||||
render_value(output, entity, true); // create the "value" field
|
||||
|
||||
if (attribute_s) {
|
||||
if (output.containsKey(attribute_s)) {
|
||||
String data = output[attribute_s].as<String>();
|
||||
std::string data = output[attribute_s].as<std::string>();
|
||||
output.clear();
|
||||
output["api_data"] = data;
|
||||
output["api_data"] = data; // always as string
|
||||
return true;
|
||||
} else {
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
|
||||
output.clear();
|
||||
output["message"] = error;
|
||||
return false;
|
||||
}
|
||||
return EMSESP::return_not_found(output, "attribute", command_s); // not found
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,8 +334,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
}
|
||||
}
|
||||
|
||||
output["message"] = "unknown command";
|
||||
return false;
|
||||
return EMSESP::return_not_found(output, "custom entity", cmd); // not found
|
||||
}
|
||||
|
||||
// publish single value
|
||||
@@ -641,8 +636,10 @@ bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram)
|
||||
void WebCustomEntityService::test() {
|
||||
update([&](WebCustomEntity & webCustomEntity) {
|
||||
webCustomEntity.customEntityItems.clear();
|
||||
auto entityItem = CustomEntityItem();
|
||||
|
||||
// test 1
|
||||
auto entityItem = CustomEntityItem();
|
||||
entityItem.id = 1;
|
||||
entityItem.ram = 0;
|
||||
entityItem.device_id = 8;
|
||||
entityItem.type_id = 24;
|
||||
@@ -656,6 +653,7 @@ void WebCustomEntityService::test() {
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
|
||||
// test 2
|
||||
entityItem.id = 2;
|
||||
entityItem.ram = 0;
|
||||
entityItem.device_id = 24;
|
||||
entityItem.type_id = 677;
|
||||
@@ -668,7 +666,8 @@ void WebCustomEntityService::test() {
|
||||
entityItem.data = "48";
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
|
||||
// test 2
|
||||
// test 3
|
||||
entityItem.id = 3;
|
||||
entityItem.ram = 1;
|
||||
entityItem.device_id = 0;
|
||||
entityItem.type_id = 0;
|
||||
@@ -681,6 +680,21 @@ void WebCustomEntityService::test() {
|
||||
entityItem.data = "14";
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
|
||||
// test 4
|
||||
entityItem.id = 4;
|
||||
entityItem.ram = 1;
|
||||
entityItem.device_id = 0;
|
||||
entityItem.type_id = 0;
|
||||
entityItem.offset = 0;
|
||||
entityItem.factor = 1;
|
||||
entityItem.name = "seltemp";
|
||||
entityItem.uom = 0;
|
||||
entityItem.value_type = 8;
|
||||
entityItem.writeable = true;
|
||||
entityItem.data = "14";
|
||||
entityItem.value = 12;
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
|
||||
EMSESP::system_.restart_requested(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// failed
|
||||
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
|
||||
request->send(response);
|
||||
|
||||
@@ -212,7 +212,7 @@ void WebDataService::device_data(AsyncWebServerRequest * request) {
|
||||
}
|
||||
|
||||
// invalid
|
||||
AsyncWebServerResponse * response = request->beginResponse(400);
|
||||
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id,
|
||||
|
||||
// process json output for info/commands and value_info
|
||||
bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
// check of it a 'commmands' command
|
||||
// check of it a 'commands' command
|
||||
if (Helpers::toLower(cmd) == F_(commands)) {
|
||||
output[F_(info)] = Helpers::translated_word(FL_(info_cmd));
|
||||
output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd));
|
||||
@@ -196,17 +196,17 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
}
|
||||
|
||||
if (attribute_s && output.containsKey(attribute_s)) {
|
||||
String data = output[attribute_s].as<String>();
|
||||
std::string data = output[attribute_s].as<std::string>();
|
||||
output.clear();
|
||||
output["api_data"] = data;
|
||||
output["api_data"] = data; // always as a string
|
||||
return true;
|
||||
}
|
||||
|
||||
if (output.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
output["message"] = "unknown command";
|
||||
return false;
|
||||
return EMSESP::return_not_found(output, "schedule", cmd); // not found
|
||||
}
|
||||
|
||||
// publish single value
|
||||
@@ -304,8 +304,10 @@ void WebSchedulerService::publish(const bool force) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ha_registered_ = ha_created;
|
||||
if (doc.size() > 0) {
|
||||
|
||||
if (!doc.isNull()) {
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
snprintf(topic, sizeof(topic), "%s_data", F_(scheduler));
|
||||
Mqtt::queue_publish(topic, doc.as<JsonObject>());
|
||||
@@ -340,6 +342,7 @@ bool WebSchedulerService::command(const char * cmd, const char * data) {
|
||||
// prefix "api/" to command string
|
||||
char command_str[COMMAND_MAX_LENGTH];
|
||||
snprintf(command_str, sizeof(command_str), "/api/%s", cmd);
|
||||
|
||||
uint8_t return_code = Command::process(command_str, true, input, output); // admin set
|
||||
|
||||
if (return_code == CommandRet::OK) {
|
||||
@@ -471,23 +474,69 @@ void WebSchedulerService::loop() {
|
||||
// hard coded tests
|
||||
#if defined(EMSESP_TEST)
|
||||
void WebSchedulerService::test() {
|
||||
update([&](WebScheduler & webScheduler) {
|
||||
webScheduler.scheduleItems.clear();
|
||||
// test 1
|
||||
auto si = ScheduleItem();
|
||||
si.active = true;
|
||||
si.flags = 1;
|
||||
si.time = "12:00";
|
||||
si.cmd = "system/fetch";
|
||||
si.value = "10";
|
||||
si.name = "test_scheduler";
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
static bool already_added = false;
|
||||
if (!already_added) {
|
||||
update([&](WebScheduler & webScheduler) {
|
||||
// webScheduler.scheduleItems.clear();
|
||||
// test 1
|
||||
auto si = ScheduleItem();
|
||||
si.active = true;
|
||||
si.flags = 1;
|
||||
si.time = "12:00";
|
||||
si.cmd = "system/fetch";
|
||||
si.value = "10";
|
||||
si.name = "test_scheduler";
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
already_added = true;
|
||||
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
}
|
||||
|
||||
// test shunting yard
|
||||
std::string test_cmd = "system/message";
|
||||
std::string test_value;
|
||||
|
||||
// should output 'locale is en'
|
||||
test_value = "\"locale is \"system/settings/locale";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// test with negative value
|
||||
// should output 'rssi is -23'
|
||||
test_value = "\"rssi is \"0+system/network/rssi";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// should output 'rssi is -23 dbm'
|
||||
test_value = "\"rssi is \"(system/network/rssi)\" dBm\"";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(custom/seltemp/value)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "\"seltemp=\"(custom/seltemp/value)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(custom/seltemp)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/outdoortemp)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "boiler/flowtempoffset";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/flowtempoffset/value)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/storagetemp1/value)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// (14 - 40) * 2.8 + 5 = -67.8
|
||||
test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -136,6 +136,9 @@ void WebStatusService::ESPsystemStatus(AsyncWebServerRequest * request) {
|
||||
root["has_loader"] = (buffer != 0xFFFFFFFFFFFFFFFF && running->size != partition->size);
|
||||
}
|
||||
}
|
||||
|
||||
root["model"] = EMSESP::system_.getBBQKeesGatewayDetails();
|
||||
|
||||
#endif
|
||||
|
||||
response->setLength();
|
||||
|
||||
@@ -386,9 +386,8 @@ std::string to_string(double d) {
|
||||
// RPN calculator
|
||||
std::string calculate(const std::string & expr) {
|
||||
auto expr_new = emsesp::Helpers::toLower(expr);
|
||||
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
|
||||
commands(expr_new);
|
||||
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
|
||||
|
||||
const auto tokens = exprToTokens(expr_new);
|
||||
if (tokens.empty()) {
|
||||
return "";
|
||||
@@ -397,6 +396,16 @@ std::string calculate(const std::string & expr) {
|
||||
if (queue.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
// debug only print tokens
|
||||
#ifdef EMSESP_STANDALONE
|
||||
for (const auto & t : queue) {
|
||||
emsesp::EMSESP::logger().debug("shunt token: %s(%d)", t.str.c_str(), t.type);
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
|
||||
std::vector<std::string> stack;
|
||||
|
||||
while (!queue.empty()) {
|
||||
@@ -556,7 +565,16 @@ std::string calculate(const std::string & expr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stack.back();
|
||||
|
||||
// concatenate all elements in stack to a single string, separated by spaces and return
|
||||
// experimental - for MDvP to check
|
||||
std::string result = "";
|
||||
for (const auto & s : stack) {
|
||||
result += s;
|
||||
}
|
||||
return result;
|
||||
|
||||
// return stack.back();
|
||||
}
|
||||
|
||||
// check for multiple instances of <cond> ? <expr1> : <expr2>
|
||||
@@ -586,5 +604,6 @@ std::string compute(const std::string & expr) {
|
||||
}
|
||||
q = expr_new.find_first_of("?"); // search next instance
|
||||
}
|
||||
|
||||
return calculate(expr_new);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user