mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-01-28 17:49:08 +03:00
Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev
This commit is contained in:
@@ -809,7 +809,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
|
||||
// match custom name or sensor GPIO
|
||||
if (cmd == Helpers::toLower(sensor.name()) || Helpers::atoint(cmd) == sensor.gpio()) {
|
||||
get_value_json(output, sensor);
|
||||
return Command::set_attribute(output, cmd, attribute_s);
|
||||
return Command::get_attribute(output, cmd, attribute_s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,8 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
|
||||
}
|
||||
|
||||
// check if data is entity like device/hc/name/value
|
||||
if (data.is<const char *>()) {
|
||||
// unless the command is system/message
|
||||
if ((strcmp(command_p, "message") != 0) && data.is<const char *>()) {
|
||||
const char * d = data.as<const char *>();
|
||||
if (strlen(d)) {
|
||||
char * device_end = (char *)strchr(d, '/');
|
||||
@@ -183,9 +184,9 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
|
||||
}
|
||||
}
|
||||
|
||||
// call the command based on the type
|
||||
uint8_t return_code = CommandRet::OK;
|
||||
|
||||
// call the command based on the type
|
||||
if (data.is<const char *>()) {
|
||||
return_code = Command::call(device_type, command_p, data.as<const char *>(), is_admin, id_n, output);
|
||||
} else if (data.is<int>()) {
|
||||
@@ -289,8 +290,12 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
|
||||
return command;
|
||||
}
|
||||
|
||||
// check if command contains an attribute
|
||||
// check if command string contains an attribute and returns it
|
||||
const char * Command::get_attribute(const char * cmd) {
|
||||
if (cmd == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char * breakp = (char *)strchr(cmd, '/');
|
||||
if (breakp) {
|
||||
*breakp = '\0';
|
||||
@@ -299,9 +304,9 @@ const char * Command::get_attribute(const char * cmd) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// returns the attribute in the given JSON object as a key/value pair called api_data
|
||||
// get the attribute in the given JSON object as a key/value pair called api_data
|
||||
// or errors if the attribute is not found
|
||||
bool Command::set_attribute(JsonObject output, const char * cmd, const char * attribute) {
|
||||
bool Command::get_attribute(JsonObject output, const char * cmd, const char * attribute) {
|
||||
if (attribute == nullptr) {
|
||||
return true;
|
||||
}
|
||||
@@ -325,7 +330,8 @@ bool Command::set_attribute(JsonObject output, const char * cmd, const char * at
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "no attribute '%s' in %s", attribute, cmd);
|
||||
output["message"] = error;
|
||||
return false;
|
||||
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
// calls a command directly
|
||||
|
||||
@@ -137,7 +137,7 @@ class Command {
|
||||
|
||||
static const char * parse_command_string(const char * command, int8_t & id);
|
||||
static const char * get_attribute(const char * cmd);
|
||||
static bool set_attribute(JsonObject output, const char * cmd, const char * attribute);
|
||||
static bool get_attribute(JsonObject output, const char * cmd, const char * attribute);
|
||||
|
||||
static const char * return_code_string(const uint8_t return_code);
|
||||
|
||||
|
||||
@@ -1537,7 +1537,7 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
|
||||
get_value_json(output, dv);
|
||||
// if we're filtering on an attribute, go find it
|
||||
// if we can't find it, maybe it exists but doesn't not have a value assigned yet
|
||||
return Command::set_attribute(output, cmd_s, attribute_s);
|
||||
return Command::get_attribute(output, cmd_s, attribute_s);
|
||||
}
|
||||
}
|
||||
return false; // not found, but don't return a message error yet
|
||||
|
||||
@@ -549,7 +549,7 @@ class EMSdevice {
|
||||
std::vector<uint16_t> handlers_ignored_;
|
||||
|
||||
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
|
||||
public: // so we can call it from WebCustomizationService::test() and EMSESP::dump_all_entities()
|
||||
public: // so we can call it from WebCustomizationService::load_test_data() and EMSESP::dump_all_entities()
|
||||
#endif
|
||||
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
|
||||
std::vector<DeviceValue> devicevalues_; // all the device values
|
||||
|
||||
@@ -29,7 +29,7 @@ static_assert(uuid::console::thread_safe, "uuid-console must be thread-safe");
|
||||
namespace emsesp {
|
||||
|
||||
// Static member definitions
|
||||
std::deque<std::unique_ptr<EMSdevice>> EMSESP::emsdevices;
|
||||
std::deque<std::unique_ptr<EMSdevice>> EMSESP::emsdevices{};
|
||||
std::vector<EMSESP::Device_record> EMSESP::device_library_;
|
||||
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
|
||||
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE;
|
||||
@@ -1619,7 +1619,7 @@ void EMSESP::start() {
|
||||
serial_console_.begin(SERIAL_CONSOLE_BAUD_RATE);
|
||||
|
||||
// always start a serial console if we're running standalone, except if we're running unit tests
|
||||
#if defined(EMSESP_STANDALONE) || defined(EMSESP_DEBUG)
|
||||
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST) || defined(EMSESP_DEBUG)
|
||||
#ifndef EMSESP_UNITY
|
||||
start_serial_console();
|
||||
#endif
|
||||
@@ -1727,7 +1727,11 @@ void EMSESP::start() {
|
||||
|
||||
void EMSESP::start_serial_console() {
|
||||
shell_ = std::make_shared<EMSESPConsole>(*this, serial_console_, true);
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
shell_->maximum_log_messages(500);
|
||||
#else
|
||||
shell_->maximum_log_messages(100);
|
||||
#endif
|
||||
shell_->start();
|
||||
#if defined(EMSESP_DEBUG)
|
||||
shell_->log_level(uuid::log::Level::DEBUG);
|
||||
|
||||
@@ -74,7 +74,7 @@ MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Beobachte eingehen
|
||||
MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "publicera allt till MQTT", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT", "publikovat vše do MQTT") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(system_info_cmd, "show system info", "Zeige Systeminformationen", "toon systeemstatus", "visa systeminformation", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému", "zobrazit informace o systému") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplanelemente", "activeer tijdschema item", "aktivera schemalagt objekt", "aktywuj wybrany harmonogram", "", "", "program öğesini etkinleştir", "abilitare l'elemento programmato", "povoliť položku plánovania", "povolit položku plánování") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "sätt ett eget värde i EMS", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS", "nastaviť vlastnú hodnotu na ems", "nastavit vlastní hodnotu na ems") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value", "Sende eigene Entitäten", "verstuur custom waarde", "sätt ett eget värde", "wyślij własną wartość", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati", "nastaviť vlastnú hodnotu", "nastavit vlastní hodnotu") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "hämta svar", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď", "získat odpověď") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "Zugabe einer Menge kalten Wassers", "", "sckicka en liten mängd kallvatten", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody", "poslat studenou vodu") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(message_cmd, "send a message", "Eine Nachricht senden", "", "skicka ett meddelande", "", "", "", "", "", "poslať správu", "odeslat zprávu") // TODO translate
|
||||
|
||||
@@ -125,13 +125,13 @@ void Mqtt::resubscribe() {
|
||||
|
||||
// Main MQTT loop - sends out top item on publish queue
|
||||
void Mqtt::loop() {
|
||||
queuecount_ = mqttClient_->queueSize();
|
||||
|
||||
// exit if MQTT is not enabled or if there is no network connection
|
||||
if (!connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
queuecount_ = mqttClient_->queueSize();
|
||||
|
||||
uint32_t currentMillis = uuid::get_uptime();
|
||||
|
||||
// send heartbeat
|
||||
|
||||
@@ -17,37 +17,10 @@
|
||||
// copy from https://gist.github.com/t-mat/b9f681b7591cdae712f6
|
||||
// modified MDvP, 06.2024
|
||||
//
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <math.h>
|
||||
|
||||
class Token {
|
||||
public:
|
||||
enum class Type : uint8_t {
|
||||
Unknown,
|
||||
Number,
|
||||
String,
|
||||
Operator,
|
||||
Compare,
|
||||
Logic,
|
||||
Unary,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
};
|
||||
#include "emsesp.h"
|
||||
|
||||
Token(Type type, const std::string & s, int8_t precedence = -1, bool rightAssociative = false)
|
||||
: type{type}
|
||||
, str(s)
|
||||
, precedence{precedence}
|
||||
, rightAssociative{rightAssociative} {
|
||||
}
|
||||
|
||||
const Type type;
|
||||
const std::string str;
|
||||
const int8_t precedence;
|
||||
const bool rightAssociative;
|
||||
};
|
||||
#include "shuntingYard.h"
|
||||
|
||||
// find tokens
|
||||
std::deque<Token> exprToTokens(const std::string & expr) {
|
||||
@@ -362,7 +335,7 @@ bool isnum(const std::string & s) {
|
||||
|
||||
|
||||
// replace commands like "<device>/<hc>/<cmd>" with its value"
|
||||
std::string commands(std::string & expr, bool quotes = true) {
|
||||
std::string commands(std::string & expr, bool quotes) {
|
||||
auto expr_new = emsesp::Helpers::toLower(expr);
|
||||
for (uint8_t device = 0; device < emsesp::EMSdevice::DeviceType::UNKNOWN; device++) {
|
||||
std::string d = (std::string)emsesp::EMSdevice::device_type_2_device_name(device) + "/";
|
||||
@@ -380,6 +353,7 @@ std::string commands(std::string & expr, bool quotes = true) {
|
||||
}
|
||||
expr_new.copy(cmd, l, f);
|
||||
cmd[l] = '\0';
|
||||
|
||||
if (strstr(cmd, "/value") == nullptr) {
|
||||
strlcat(cmd, "/value", sizeof(cmd) - 6);
|
||||
}
|
||||
@@ -458,23 +432,21 @@ std::string calculate(const std::string & expr) {
|
||||
// commands(expr_new);
|
||||
|
||||
const auto tokens = exprToTokens(expr_new);
|
||||
// for debugging only
|
||||
// for (const auto & t : tokens) {
|
||||
// emsesp::EMSESP::logger().debug("shunt token: %s(%d)", t.str.c_str(), t.type);
|
||||
// Serial.printf("shunt token: %s(%d)\n", t.str.c_str(), t.type);
|
||||
// Serial.println();
|
||||
// }
|
||||
if (tokens.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
auto queue = shuntingYard(tokens);
|
||||
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()) {
|
||||
86
src/core/shuntingYard.h
Normal file
86
src/core/shuntingYard.h
Normal file
@@ -0,0 +1,86 @@
|
||||
// Shunting-yard Algorithm
|
||||
// https://en.wikipedia.org/wiki/Shunting-yard_algorithm
|
||||
//
|
||||
// Implementation notes for unary operators by Austin Taylor
|
||||
// https://stackoverflow.com/a/5240912
|
||||
//
|
||||
// Example:
|
||||
// https://ideone.com/VocUTq
|
||||
//
|
||||
// License:
|
||||
// This code uses the following materials.
|
||||
// (1) Wikipedia article [Shunting-yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm),
|
||||
// which is released under the [Creative Commons Attribution-Share-Alike License 3.0](https://creativecommons.org/licenses/by-sa/3.0/).
|
||||
// (2) [Implementation notes for unary operators in Shunting-Yard algorithm](https://stackoverflow.com/a/5240912) by Austin Taylor
|
||||
// which is released under the [Creative Commons Attribution-Share-Alike License 2.5](https://creativecommons.org/licenses/by-sa/2.5/).
|
||||
//
|
||||
// copy from https://gist.github.com/t-mat/b9f681b7591cdae712f6
|
||||
// modified MDvP, 06.2024
|
||||
//
|
||||
|
||||
#ifndef EMSESP_SHUNTING_YARD_H_
|
||||
#define EMSESP_SHUNTING_YARD_H_
|
||||
|
||||
#include <HTTPClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <math.h>
|
||||
|
||||
class Token {
|
||||
public:
|
||||
enum class Type : uint8_t {
|
||||
Unknown,
|
||||
Number,
|
||||
String,
|
||||
Operator,
|
||||
Compare,
|
||||
Logic,
|
||||
Unary,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
};
|
||||
|
||||
Token(Type type, const std::string & s, int8_t precedence = -1, bool rightAssociative = false)
|
||||
: type{type}
|
||||
, str(s)
|
||||
, precedence{precedence}
|
||||
, rightAssociative{rightAssociative} {
|
||||
}
|
||||
|
||||
const Type type;
|
||||
const std::string str;
|
||||
const int8_t precedence;
|
||||
const bool rightAssociative;
|
||||
};
|
||||
|
||||
// find tokens
|
||||
std::deque<Token> exprToTokens(const std::string & expr);
|
||||
|
||||
// sort tokens to RPN form
|
||||
std::deque<Token> shuntingYard(const std::deque<Token> & tokens);
|
||||
|
||||
// check if string is a number
|
||||
bool isnum(const std::string & s);
|
||||
|
||||
// replace commands like "<device>/<hc>/<cmd>" with its value"
|
||||
std::string commands(std::string & expr, bool quotes = true);
|
||||
|
||||
// checks for logic value
|
||||
int to_logic(const std::string & s);
|
||||
|
||||
// number to string, remove trailing zeros
|
||||
std::string to_string(double d);
|
||||
|
||||
// number to hex string
|
||||
std::string to_hex(uint32_t i);
|
||||
|
||||
// RPN calculator
|
||||
std::string calculate(const std::string & expr);
|
||||
|
||||
// check for multiple instances of <cond> ? <expr1> : <expr2>
|
||||
std::string compute(const std::string & expr);
|
||||
|
||||
#endif
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "system.h"
|
||||
#include "emsesp.h" // for send_raw_telegram() command
|
||||
|
||||
#include "shuntingYard.h"
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include "esp_ota_ops.h"
|
||||
#endif
|
||||
@@ -48,8 +50,6 @@
|
||||
#include <esp_mac.h>
|
||||
#endif
|
||||
|
||||
#include <HTTPClient.h>
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include "esp_efuse.h"
|
||||
#endif
|
||||
@@ -205,14 +205,23 @@ 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) {
|
||||
// send message - to system log and MQTT
|
||||
bool System::command_message(const char * value, const int8_t id, JsonObject output) {
|
||||
if (value == nullptr || value[0] == '\0') {
|
||||
LOG_WARNING("Message is empty");
|
||||
return false; // must have a string value
|
||||
}
|
||||
|
||||
LOG_INFO("Message: %s", value);
|
||||
Mqtt::queue_publish(F_(message), value);
|
||||
auto computed_value = compute(value); // process the message via Shunting Yard
|
||||
|
||||
if (computed_value.length() == 0) {
|
||||
LOG_WARNING("Message result is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("Message: %s", computed_value.c_str()); // send to log
|
||||
Mqtt::queue_publish(F_(message), computed_value); // send to MQTT if enabled
|
||||
output["api_data"] = computed_value; // send to API
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -831,9 +840,9 @@ void System::system_check() {
|
||||
// everything is healthy, show LED permanently on or off depending on setting
|
||||
if (led_gpio_) {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||
led_type_ ? neopixelWrite(led_gpio_, 0, hide_led_ ? 0 : 128, 0) : digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
|
||||
led_type_ ? neopixelWrite(led_gpio_, 0, hide_led_ ? 0 : RGB_LED_BRIGHTNESS, 0) : digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
|
||||
#else
|
||||
led_type_ ? rgbLedWrite(led_gpio_, 0, hide_led_ ? 0 : 128, 0) : digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
|
||||
led_type_ ? rgbLedWrite(led_gpio_, 0, hide_led_ ? 0 : RGB_LED_BRIGHTNESS, 0) : digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
@@ -913,36 +922,35 @@ void System::led_monitor() {
|
||||
// 1 flash is the EMS bus is not connected
|
||||
// 2 flashes if the network (wifi or ethernet) is not connected
|
||||
// 3 flashes is both the bus and the network are not connected. Then you know you're truly f*cked.
|
||||
|
||||
if (led_type_) {
|
||||
if (led_flash_step_ == 3) {
|
||||
if ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||
neopixelWrite(led_gpio_, 128, 0, 0); // red
|
||||
neopixelWrite(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red
|
||||
#else
|
||||
rgbLedWrite(led_gpio_, 128, 0, 0); // red
|
||||
rgbLedWrite(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red
|
||||
#endif
|
||||
} else if ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS) {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||
neopixelWrite(led_gpio_, 0, 0, 128); // blue
|
||||
neopixelWrite(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue
|
||||
#else
|
||||
rgbLedWrite(led_gpio_, 0, 0, 128); // blue
|
||||
rgbLedWrite(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (led_flash_step_ == 5 && (healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||
neopixelWrite(led_gpio_, 128, 0, 0); // red
|
||||
neopixelWrite(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red
|
||||
#else
|
||||
rgbLedWrite(led_gpio_, 128, 0, 0); // red
|
||||
rgbLedWrite(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red
|
||||
#endif
|
||||
}
|
||||
if ((led_flash_step_ == 7) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)
|
||||
&& ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS)) {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||
neopixelWrite(led_gpio_, 0, 0, 128); // blue
|
||||
neopixelWrite(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue
|
||||
#else
|
||||
rgbLedWrite(led_gpio_, 0, 0, 128); // blue
|
||||
rgbLedWrite(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
@@ -1433,7 +1441,6 @@ bool System::command_service(const char * cmd, const char * value) {
|
||||
if (!strcmp(cmd, "fuse/mfg")) {
|
||||
ok = esp_efuse_write_reg(EFUSE_BLK3, 0, (uint32_t)n) == ESP_OK;
|
||||
ok ? LOG_INFO("fuse programed with value '%X': successful", n) : LOG_ERROR("fuse programed with value '%X': failed", n);
|
||||
|
||||
}
|
||||
if (!strcmp(cmd, "fuse/mfgadd")) {
|
||||
uint8_t reg = 0;
|
||||
@@ -1521,7 +1528,7 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} // else skipt, but we don't have value pairs in system root
|
||||
} // else skip, but we don't have value pairs in system root
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -1569,11 +1576,11 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
node["sdk"] = ESP.getSdkVersion();
|
||||
node["freeMem"] = getHeapMem();
|
||||
node["maxAlloc"] = getMaxAllocMem();
|
||||
node["freeCaps"] = heap_caps_get_free_size(MALLOC_CAP_8BIT) / 1024; // includes heap and psram
|
||||
node["usedApp"] = EMSESP::system_.appUsed(); // kilobytes
|
||||
node["freeApp"] = EMSESP::system_.appFree(); // kilobytes
|
||||
node["partition"] = (const char *)esp_ota_get_running_partition()->label; // active partition
|
||||
node["flash_chip_size"] = ESP.getFlashChipSize() / 1024; // kilobytes
|
||||
node["freeCaps"] = heap_caps_get_free_size(MALLOC_CAP_8BIT) / 1024; // includes heap and psram
|
||||
node["usedApp"] = EMSESP::system_.appUsed(); // kilobytes
|
||||
node["freeApp"] = EMSESP::system_.appFree(); // kilobytes
|
||||
node["partition"] = (const char *)esp_ota_get_running_partition()->label; // active partition
|
||||
node["flash_chip_size"] = ESP.getFlashChipSize() / 1024; // kilobytes
|
||||
#endif
|
||||
node["resetReason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -1842,6 +1849,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also show EMSESP devices if we have any
|
||||
if (EMSESP::temperaturesensor_.count_entities()) {
|
||||
JsonObject obj = devices.add<JsonObject>();
|
||||
|
||||
@@ -55,6 +55,8 @@ using uuid::console::Shell;
|
||||
|
||||
#define EMSESP_CUSTOMSUPPORT_FILE "/config/customSupport.json"
|
||||
|
||||
#define RGB_LED_BRIGHTNESS 20
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 };
|
||||
@@ -83,7 +85,7 @@ class System {
|
||||
static bool command_restart(const char * value, const int8_t id);
|
||||
static bool command_format(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_message(const char * value, const int8_t id, JsonObject output);
|
||||
static bool command_info(const char * value, const int8_t id, JsonObject output);
|
||||
static bool command_response(const char * value, const int8_t id, JsonObject output);
|
||||
static bool command_service(const char * cmd, const char * value);
|
||||
|
||||
@@ -399,7 +399,7 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
|
||||
// match custom name or sensor ID
|
||||
if (cmd == Helpers::toLower(sensor.name()) || cmd == Helpers::toLower(sensor.id())) {
|
||||
get_value_json(output, sensor);
|
||||
return Command::set_attribute(output, cmd, attribute_s);
|
||||
return Command::get_attribute(output, cmd, attribute_s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,7 +618,9 @@ bool TemperatureSensor::Sensor::apply_customization() {
|
||||
|
||||
// hard coded tests
|
||||
#if defined(EMSESP_TEST)
|
||||
void TemperatureSensor::test() {
|
||||
void TemperatureSensor::load_test_data() {
|
||||
sensors_.clear(); // delete all existing sensors
|
||||
|
||||
// add 2 temperature sensors
|
||||
// Sensor ID: 01_0203_0405_0607
|
||||
uint8_t addr[ADDR_LEN] = {1, 2, 3, 4, 5, 6, 7, 8};
|
||||
|
||||
@@ -113,7 +113,7 @@ class TemperatureSensor {
|
||||
bool update(const std::string & id, const std::string & name, int16_t offset);
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void test();
|
||||
void load_test_data();
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
@@ -327,10 +327,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
// add devices
|
||||
test("general");
|
||||
|
||||
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
|
||||
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
||||
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
||||
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
||||
EMSESP::webSchedulerService.load_test_data(); // add scheduler data
|
||||
|
||||
shell.invoke_command("show values");
|
||||
ok = true;
|
||||
@@ -348,7 +348,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
shell.printfln("Adding custom entities...");
|
||||
|
||||
// add some dummy entities
|
||||
EMSESP::webCustomEntityService.test();
|
||||
EMSESP::webCustomEntityService.load_test_data();
|
||||
|
||||
#ifdef EMSESP_STANDALONE
|
||||
AsyncWebServerRequest request;
|
||||
@@ -372,15 +372,13 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
char data[] = "{\"value\":\"99\"}";
|
||||
deserializeJson(doc, data);
|
||||
json = doc.as<JsonVariant>();
|
||||
// validate
|
||||
request.url("/api/custom/test_ram");
|
||||
EMSESP::webAPIService.webAPIService(&request, json);
|
||||
|
||||
// validate by showing values
|
||||
request.method(HTTP_GET);
|
||||
request.url("/api/custom");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
|
||||
#endif
|
||||
ok = true;
|
||||
}
|
||||
@@ -389,7 +387,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
shell.printfln("Adding Scheduler items...");
|
||||
|
||||
// add some dummy entities
|
||||
EMSESP::webSchedulerService.test();
|
||||
EMSESP::webSchedulerService.load_test_data();
|
||||
|
||||
#ifdef EMSESP_STANDALONE
|
||||
AsyncWebServerRequest request;
|
||||
@@ -832,7 +830,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
// load some EMS data
|
||||
// test("general");
|
||||
|
||||
emsesp::EMSESP::temperaturesensor_.test();
|
||||
emsesp::EMSESP::temperaturesensor_.load_test_data();
|
||||
|
||||
shell.invoke_command("call temperaturesensor");
|
||||
shell.invoke_command("show values");
|
||||
@@ -857,7 +855,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
Mqtt::nested_format(1);
|
||||
// Mqtt::nested_format(0);
|
||||
|
||||
emsesp::EMSESP::temperaturesensor_.test();
|
||||
emsesp::EMSESP::temperaturesensor_.load_test_data();
|
||||
shell.invoke_command("show values");
|
||||
shell.invoke_command("call system publish");
|
||||
|
||||
@@ -879,7 +877,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
// load some EMS data
|
||||
test("general");
|
||||
|
||||
EMSESP::webCustomizationService.test(); // load the analog sensors
|
||||
EMSESP::webCustomizationService.load_test_data(); // load the analog sensors
|
||||
|
||||
shell.invoke_command("call analogsensor");
|
||||
shell.invoke_command("show values");
|
||||
@@ -1073,6 +1071,156 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
shell.invoke_command("call boiler circpump/value");
|
||||
}
|
||||
|
||||
if (command == "shuntingyard") {
|
||||
shell.printfln("Testing shunting yard...");
|
||||
|
||||
// load devices
|
||||
test("general");
|
||||
|
||||
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
||||
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
||||
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
||||
EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions
|
||||
|
||||
JsonDocument doc;
|
||||
AsyncWebServerRequest request;
|
||||
|
||||
// request.method(HTTP_GET);
|
||||
// request.url("/api/custom/test_seltemp/val");
|
||||
// EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// set a custom value
|
||||
// request.method(HTTP_POST);
|
||||
// char data0[] = "{\"value\":\"99\"}";
|
||||
// deserializeJson(doc, data0);
|
||||
// JsonVariant json = doc.as<JsonVariant>();
|
||||
// request.url("/api/custom/test_seltemp");
|
||||
// EMSESP::webAPIService.webAPIService(&request, json);
|
||||
|
||||
// Note, works in test not in production - causes a crash!
|
||||
// request.method(HTTP_POST);
|
||||
// char data1[] = "{\"value\":\"system/settings/locale\"}";
|
||||
// deserializeJson(doc, data1);
|
||||
// JsonVariant json = doc.as<JsonVariant>();
|
||||
// request.url("/api/system/message");
|
||||
// EMSESP::webAPIService.webAPIService(&request, json);
|
||||
|
||||
// output: 70.00
|
||||
request.method(HTTP_POST);
|
||||
char data1[] = "{\"value\":\"custom/test_custom\"}";
|
||||
deserializeJson(doc, data1);
|
||||
JsonVariant json = doc.as<JsonVariant>();
|
||||
request.url("/api/system/message");
|
||||
EMSESP::webAPIService.webAPIService(&request, json);
|
||||
|
||||
request.method(HTTP_POST);
|
||||
request.url("/api/system/message");
|
||||
|
||||
// output: hello world!
|
||||
EMSESP::webAPIService.webAPIService(&request, "'hello world!'");
|
||||
|
||||
// output: hello world!
|
||||
EMSESP::webAPIService.webAPIService(&request, "\"hello world!\"");
|
||||
|
||||
// output: helloworld
|
||||
EMSESP::webAPIService.webAPIService(&request, "hello world");
|
||||
|
||||
// output: en
|
||||
EMSESP::webAPIService.webAPIService(&request, "system/settings/locale");
|
||||
|
||||
// output: locale is en
|
||||
EMSESP::webAPIService.webAPIService(&request, "'locale is 'system/settings/locale");
|
||||
|
||||
// output: locale is en
|
||||
EMSESP::webAPIService.webAPIService(&request, "'locale is '(system/settings/locale)");
|
||||
|
||||
// output: rssi is -23
|
||||
EMSESP::webAPIService.webAPIService(&request, "'rssi is '0+system/network/rssi");
|
||||
|
||||
// output: rssi is -23 dBm
|
||||
EMSESP::webAPIService.webAPIService(&request, "'rssi is '(system/network/rssi)' dBm'");
|
||||
|
||||
// output: 14
|
||||
EMSESP::webAPIService.webAPIService(&request, "custom/test_seltemp");
|
||||
|
||||
// output: 14
|
||||
EMSESP::webAPIService.webAPIService(&request, "custom/test_seltemp/value");
|
||||
|
||||
// output: seltemp=14
|
||||
EMSESP::webAPIService.webAPIService(&request, "'seltemp='custom/test_seltemp/value");
|
||||
|
||||
// output: 14
|
||||
EMSESP::webAPIService.webAPIService(&request, "custom/test_seltemp");
|
||||
|
||||
// output: 40
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/flowtempoffset");
|
||||
|
||||
// output: 40
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/flowtempoffset/value");
|
||||
|
||||
// output: -67.8
|
||||
EMSESP::webAPIService.webAPIService(&request, "(custom/test_seltemp - boiler/flowtempoffset) * 2.8 + 5");
|
||||
|
||||
// output: 1
|
||||
EMSESP::webAPIService.webAPIService(&request, "thermostat/hc1/modetype == 'comfort'");
|
||||
|
||||
// output: 1
|
||||
EMSESP::webAPIService.webAPIService(&request, "thermostat/hc1/modetype == 'Comfort'");
|
||||
|
||||
// output: 0
|
||||
EMSESP::webAPIService.webAPIService(&request, "thermostat/hc1/modetype == 'unknown'");
|
||||
|
||||
// output: 53.8
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp1/value");
|
||||
|
||||
// output: 53.8
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp1");
|
||||
|
||||
// Output is "comfort" == Comfort (because missing closing quote)
|
||||
EMSESP::webAPIService.webAPIService(&request, "'thermostat/hc1/modetype == 'Comfort'");
|
||||
|
||||
// output: 14
|
||||
EMSESP::webAPIService.webAPIService(&request, "custom/test_seltemp");
|
||||
|
||||
//
|
||||
// these next tests should fail or give warnings or strange results
|
||||
//
|
||||
|
||||
// check when entity has no value, should pass (storagetemp2 has no value set)
|
||||
// failed with no entity 'storagetemp' in boiler, Message result is empty
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp2 == \"\"");
|
||||
|
||||
// check when entity has no value, should pass (storagetemp2 has no value set)
|
||||
// failed with no entity 'storagetemp' in boiler, Message result is empty
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp2 == ''");
|
||||
|
||||
// storagetemp2 has no value set
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp2");
|
||||
|
||||
// can't find entity, should fail with no entity 'storagetemp' in boiler
|
||||
// output: "" Message result is empty
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp/value1");
|
||||
|
||||
// can't find entity, should fail with no attribute 'value1' in storagetemp1
|
||||
// output: "" Message result is empty
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp1/value1");
|
||||
|
||||
// check when entity has no value, should pass (storagetemp2 has no value set)
|
||||
// output: "" Message result is empty
|
||||
EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp2/value");
|
||||
|
||||
// can't set empty value! Message result is empty
|
||||
EMSESP::webAPIService.webAPIService(&request, "/api/custom/test_seltemp/val");
|
||||
|
||||
// test HTTP POST to call HA script
|
||||
// test_cmd = "{\"method\":\"POST\",\"url\":\"http://192.168.1.42:8123/api/services/script/test_notify2\", \"header\":{\"authorization\":\"Bearer "
|
||||
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhMmNlYWI5NDgzMmI0ODE2YWQ2NzU4MjkzZDE2YWMxZSIsImlhdCI6MTcyMTM5MTI0NCwiZXhwIjoyMDM2NzUxMjQ0fQ."
|
||||
// "S5sago1tEI6lNhrDCO0dM_WsVQHkD_laAjcks8tWAqo\"}}";
|
||||
// command("test99", test_cmd.c_str(), "");
|
||||
|
||||
ok = true;
|
||||
}
|
||||
|
||||
if (command == "api3") {
|
||||
shell.printfln("Testing API getting values from system");
|
||||
EMSESP::system_.bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR
|
||||
@@ -1081,8 +1229,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
|
||||
bool single;
|
||||
|
||||
single = true;
|
||||
// single = false;
|
||||
// single = true;
|
||||
single = false;
|
||||
|
||||
AsyncWebServerRequest request;
|
||||
JsonDocument doc;
|
||||
@@ -1096,10 +1244,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
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
|
||||
// EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
||||
// EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
||||
// EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
||||
// EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions
|
||||
|
||||
// request.url("/rest/deviceEntities");
|
||||
// EMSESP::webCustomizationService.device_entities(&request);
|
||||
@@ -1134,6 +1282,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
deserializeJson(doc, data0);
|
||||
request.url("/api/thermostat/seltemp");
|
||||
EMSESP::webAPIService.webAPIService(&request, doc.as<JsonVariant>());
|
||||
|
||||
// request.method(HTTP_GET);
|
||||
// request.url("/api/thermostat/seltemp/value");
|
||||
// EMSESP::webAPIService.webAPIService(&request);
|
||||
@@ -1201,10 +1350,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
// shell.invoke_command("call system read \"8 2 27 1\"");
|
||||
|
||||
} 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
|
||||
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
||||
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
||||
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
||||
EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions
|
||||
|
||||
request.method(HTTP_GET);
|
||||
|
||||
@@ -1249,7 +1398,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/custom/info");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/custom/seltemp");
|
||||
request.url("/api/custom/test_seltemp");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// system
|
||||
@@ -1358,9 +1507,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// custom
|
||||
request.url("/api/custom/seltemp2");
|
||||
request.url("/api/custom/test_seltemp2");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
request.url("/api/custom/seltemp/val");
|
||||
request.url("/api/custom/test_seltemp/val");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
// temperaturesensor
|
||||
|
||||
@@ -61,7 +61,8 @@ namespace emsesp {
|
||||
// #define EMSESP_DEBUG_DEFAULT "heat_exchange"
|
||||
// #define EMSESP_DEBUG_DEFAULT "ls"
|
||||
// #define EMSESP_DEBUG_DEFAULT "upload"
|
||||
#define EMSESP_DEBUG_DEFAULT "hpmode"
|
||||
// #define EMSESP_DEBUG_DEFAULT "hpmode"
|
||||
#define EMSESP_DEBUG_DEFAULT "shuntingyard"
|
||||
|
||||
#ifndef EMSESP_DEBUG_DEFAULT
|
||||
#define EMSESP_DEBUG_DEFAULT "general"
|
||||
|
||||
@@ -51,6 +51,14 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant j
|
||||
parse(request, input);
|
||||
}
|
||||
|
||||
// for POSTS accepting plain text data
|
||||
void WebAPIService::webAPIService(AsyncWebServerRequest * request, const char * data) {
|
||||
JsonDocument input_doc;
|
||||
JsonObject input = input_doc.to<JsonObject>();
|
||||
input["data"] = data;
|
||||
parse(request, input);
|
||||
}
|
||||
|
||||
#ifdef EMSESP_TEST
|
||||
// for test.cpp and unit tests so we can invoke GETs to test the API
|
||||
void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
|
||||
@@ -100,7 +108,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
emsesp::EMSESP::system_.refreshHeapMem();
|
||||
|
||||
// output json buffer
|
||||
auto response = new AsyncJsonResponse(false);
|
||||
auto response = new AsyncJsonResponse();
|
||||
|
||||
// add more mem if needed - won't be needed in ArduinoJson 7
|
||||
// while (!response->getSize()) {
|
||||
@@ -155,10 +163,19 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
storeResponse(output);
|
||||
#endif
|
||||
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
||||
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);
|
||||
std::string output_str;
|
||||
serializeJson(output, output_str);
|
||||
Serial.printf("%sweb output: %s[%s] %s(%d)%s %s%s",
|
||||
COLOR_WHITE,
|
||||
COLOR_BRIGHT_CYAN,
|
||||
request->url().c_str(),
|
||||
ret_codes[return_code] == 200 ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_RED,
|
||||
ret_codes[return_code],
|
||||
COLOR_YELLOW,
|
||||
output_str.c_str(),
|
||||
COLOR_RESET);
|
||||
Serial.println();
|
||||
EMSESP::logger().debug("web output: %s %s", request->url().c_str(), output_str.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class WebAPIService {
|
||||
WebAPIService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
void webAPIService(AsyncWebServerRequest * request, JsonVariant input);
|
||||
void webAPIService(AsyncWebServerRequest * request, const char * data); // for plain text data
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
// for test.cpp and running unit tests
|
||||
|
||||
@@ -153,6 +153,12 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
||||
|
||||
// set value by api command
|
||||
bool WebCustomEntityService::command_setvalue(const char * value, const int8_t id, const char * name) {
|
||||
// don't write if there is no value, to prevent setting an empty value by mistake when parsing attributes
|
||||
if (!strlen(value)) {
|
||||
EMSESP::logger().debug("can't set empty value!");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CustomEntityItem & entityItem : *customEntityItems_) {
|
||||
if (Helpers::toLower(entityItem.name) == Helpers::toLower(name)) {
|
||||
if (entityItem.ram == 1) {
|
||||
@@ -217,7 +223,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
|
||||
|
||||
// output of a single value
|
||||
// if add_uom is true it will add the UOM string to the value
|
||||
void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem & entity, const bool useVal, const bool web, const bool add_uom) {
|
||||
void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem const & entity, const bool useVal, const bool web, const bool add_uom) {
|
||||
char payload[20];
|
||||
std::string name = useVal ? "value" : entity.name;
|
||||
switch (entity.value_type) {
|
||||
@@ -286,6 +292,10 @@ void WebCustomEntityService::show_values(JsonObject output) {
|
||||
|
||||
// process json output for info/commands and value_info
|
||||
bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) {
|
||||
if (cmd == nullptr || strlen(cmd) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if no custom entries, return empty json
|
||||
// even if we're looking for a specific entity
|
||||
// https://github.com/emsesp/EMS-ESP32/issues/1297
|
||||
@@ -315,17 +325,17 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
|
||||
// specific value info
|
||||
const char * attribute_s = Command::get_attribute(cmd);
|
||||
for (auto & entity : *customEntityItems_) {
|
||||
for (auto const & entity : *customEntityItems_) {
|
||||
if (Helpers::toLower(entity.name) == cmd) {
|
||||
get_value_json(output, entity);
|
||||
return Command::set_attribute(output, cmd, attribute_s);
|
||||
return Command::get_attribute(output, cmd, attribute_s);
|
||||
}
|
||||
}
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
// build the json for specific entity
|
||||
void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem & entity) {
|
||||
void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem const & entity) {
|
||||
output["name"] = entity.name;
|
||||
output["fullname"] = entity.name;
|
||||
output["storage"] = entity.ram ? "ram" : "ems";
|
||||
@@ -681,9 +691,10 @@ bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram)
|
||||
// hard coded tests
|
||||
// add the entity and also add the command for writeable entities
|
||||
#ifdef EMSESP_TEST
|
||||
void WebCustomEntityService::test() {
|
||||
void WebCustomEntityService::load_test_data() {
|
||||
update([&](WebCustomEntity & webCustomEntity) {
|
||||
webCustomEntity.customEntityItems.clear();
|
||||
webCustomEntity.customEntityItems.clear(); // delete all existing entities
|
||||
|
||||
auto entityItem = CustomEntityItem();
|
||||
|
||||
// test 1
|
||||
@@ -698,6 +709,7 @@ void WebCustomEntityService::test() {
|
||||
entityItem.value_type = 1;
|
||||
entityItem.writeable = true;
|
||||
entityItem.data = "70";
|
||||
entityItem.value = 70;
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
@@ -751,12 +763,12 @@ void WebCustomEntityService::test() {
|
||||
entityItem.type_id = 0;
|
||||
entityItem.offset = 0;
|
||||
entityItem.factor = 1;
|
||||
entityItem.name = "seltemp";
|
||||
entityItem.name = "test_seltemp";
|
||||
entityItem.uom = 0;
|
||||
entityItem.value_type = 8;
|
||||
entityItem.writeable = true;
|
||||
entityItem.data = "14";
|
||||
entityItem.value = 12;
|
||||
entityItem.value = 14;
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
|
||||
@@ -60,10 +60,10 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
|
||||
void publish(const bool force = false);
|
||||
bool command_setvalue(const char * value, const int8_t id, const char * name);
|
||||
bool get_value_info(JsonObject output, const char * cmd);
|
||||
void get_value_json(JsonObject output, CustomEntityItem & entity);
|
||||
void get_value_json(JsonObject output, CustomEntityItem const & entity);
|
||||
bool get_value(std::shared_ptr<const Telegram> telegram);
|
||||
void fetch();
|
||||
void render_value(JsonObject output, CustomEntityItem & entity, const bool useVal = false, const bool web = false, const bool add_uom = false);
|
||||
void render_value(JsonObject output, CustomEntityItem const & entity, const bool useVal = false, const bool web = false, const bool add_uom = false);
|
||||
void show_values(JsonObject output);
|
||||
void generate_value_web(JsonObject output, const bool is_dashboard = false);
|
||||
|
||||
@@ -73,7 +73,7 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
|
||||
}
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void test();
|
||||
void load_test_data();
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
@@ -366,10 +366,11 @@ void WebCustomizationService::begin() {
|
||||
|
||||
// hard coded tests
|
||||
#ifdef EMSESP_TEST
|
||||
void WebCustomizationService::test() {
|
||||
void WebCustomizationService::load_test_data() {
|
||||
update([&](WebCustomization & webCustomization) {
|
||||
// Temperature sensors
|
||||
webCustomization.sensorCustomizations.clear();
|
||||
webCustomization.sensorCustomizations.clear(); // delete all existing sensors
|
||||
|
||||
auto sensor = SensorCustomization();
|
||||
sensor.id = "01_0203_0405_0607";
|
||||
sensor.name = "test_tempsensor1";
|
||||
|
||||
@@ -85,7 +85,7 @@ class WebCustomizationService : public StatefulService<WebCustomization> {
|
||||
void begin();
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void test();
|
||||
void load_test_data();
|
||||
#endif
|
||||
|
||||
// make all functions public so we can test in the debug and standalone mode
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
#include "emsesp.h"
|
||||
#include "WebSchedulerService.h"
|
||||
#include <HTTPClient.h>
|
||||
|
||||
#include "shuntingYard.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -173,7 +174,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (Helpers::toLower(scheduleItem.name) == cmd) {
|
||||
get_value_json(output, scheduleItem);
|
||||
return Command::set_attribute(output, cmd, attribute_s);
|
||||
return Command::get_attribute(output, cmd, attribute_s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,14 +322,14 @@ uint8_t WebSchedulerService::count_entities(bool cmd_only) {
|
||||
return count;
|
||||
}
|
||||
|
||||
#include "shuntingYard.hpp"
|
||||
|
||||
|
||||
// execute scheduled command
|
||||
bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
|
||||
std::string cmd = Helpers::toLower(command);
|
||||
|
||||
// check http commands. e.g.
|
||||
// tasmota(get): http://<tasmotsIP>/cm?cmnd=power%20ON
|
||||
// tasmota(get): http://<tasmotaIP>/cm?cmnd=power%20ON
|
||||
// shelly(get): http://<shellyIP>/relais/0?turn=on
|
||||
// parse json
|
||||
JsonDocument doc;
|
||||
@@ -351,6 +352,7 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
||||
}
|
||||
std::string value = doc["value"] | data.c_str(); // extract value if its in the command, or take the data
|
||||
std::string method = doc["method"] | "GET"; // default GET
|
||||
|
||||
commands(value, false);
|
||||
// if there is data, force a POST
|
||||
int httpResult = 0;
|
||||
@@ -544,110 +546,38 @@ void WebSchedulerService::scheduler_task(void * pvParameters) {
|
||||
|
||||
// hard coded tests
|
||||
#if defined(EMSESP_TEST)
|
||||
void WebSchedulerService::test() {
|
||||
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
|
||||
void WebSchedulerService::load_test_data() {
|
||||
update([&](WebScheduler & webScheduler) {
|
||||
webScheduler.scheduleItems.clear(); // delete all existing schedules
|
||||
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
// 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
|
||||
|
||||
// test 2
|
||||
si = ScheduleItem();
|
||||
si.active = false;
|
||||
si.flags = 1;
|
||||
si.time = "13:00";
|
||||
si.cmd = "system/message";
|
||||
si.value = "20";
|
||||
si.name = ""; // to make sure its excluded from Dashboard
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
already_added = true;
|
||||
// test 2
|
||||
si = ScheduleItem();
|
||||
si.active = false;
|
||||
si.flags = 1;
|
||||
si.time = "13:00";
|
||||
si.cmd = "system/message";
|
||||
si.value = "20";
|
||||
si.name = ""; // to make sure its excluded from Dashboard
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
}
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
|
||||
// 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", 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("test1", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// should output 'rssi is -23 dbm'
|
||||
test_value = "\"rssi is \"(system/network/rssi)\" dBm\"";
|
||||
command("test2", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(custom/seltemp/value)";
|
||||
command("test3", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "\"seltemp=\"(custom/seltemp/value)";
|
||||
command("test4", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(custom/seltemp)";
|
||||
command("test5", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "boiler/flowtempoffset";
|
||||
command("test7", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/flowtempoffset/value)";
|
||||
command("test8", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/storagetemp1/value)";
|
||||
command("test9", 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("test10", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// test case conversion
|
||||
test_value = "(thermostat/hc1/modetype == \"comfort\")";
|
||||
command("test11a", test_cmd.c_str(), compute(test_value).c_str()); // should be 1 true
|
||||
test_value = "(thermostat/hc1/modetype == \"Comfort\")";
|
||||
command("test11b", test_cmd.c_str(), compute(test_value).c_str()); // should be 1 true
|
||||
test_value = "(thermostat/hc1/modetype == \"unknown\")";
|
||||
command("test11c", test_cmd.c_str(), compute(test_value).c_str()); // should be 0 false
|
||||
|
||||
// can't find entity, should fail
|
||||
test_value = "(boiler/storagetemp/value1)";
|
||||
command("test12", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// can't find attribute, should fail
|
||||
test_value = "(boiler/storagetemp1/value1)";
|
||||
command("test13", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// check when entity has no value, should pass
|
||||
test_value = "(boiler/storagetemp2/value)";
|
||||
command("test14", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// should pass
|
||||
test_value = "(boiler/storagetemp1/value)";
|
||||
command("test15", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// test HTTP POST to call HA script
|
||||
// test_cmd = "{\"method\":\"POST\",\"url\":\"http://192.168.1.42:8123/api/services/script/test_notify2\", \"header\":{\"authorization\":\"Bearer "
|
||||
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhMmNlYWI5NDgzMmI0ODE2YWQ2NzU4MjkzZDE2YWMxZSIsImlhdCI6MTcyMTM5MTI0NCwiZXhwIjoyMDM2NzUxMjQ0fQ."
|
||||
// "S5sago1tEI6lNhrDCO0dM_WsVQHkD_laAjcks8tWAqo\"}}";
|
||||
// command("test99", test_cmd.c_str(), "");
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
|
||||
bool onChange(const char * cmd);
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void test();
|
||||
void load_test_data();
|
||||
#endif
|
||||
|
||||
// make all functions public so we can test in the debug and standalone mode
|
||||
|
||||
Reference in New Issue
Block a user