This commit is contained in:
MichaelDvP
2025-10-07 07:26:24 +02:00
43 changed files with 7927 additions and 7832 deletions

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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>();

View File

@@ -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);

View File

@@ -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};

View File

@@ -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:

View File

@@ -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

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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:

View File

@@ -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";

View File

@@ -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

View File

@@ -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

View File

@@ -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