diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 02dc06d9b..04a924280 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -36,13 +36,14 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/). - RT800 remote emulation [#1867](https://github.com/emsesp/EMS-ESP32/issues/1867) - RC310 cooling parameters [#1857](https://github.com/emsesp/EMS-ESP32/issues/1857) - command `api/device/entities` [#1897](https://github.com/emsesp/EMS-ESP32/issues/1897) -- switchprogmode [#1903] +- switchprogmode [#1903](https://github.com/emsesp/EMS-ESP32/discussions/1903) - autodetect and download firmware upgrades via the WebUI - command 'show log' that lists out the current weblog buffer, showing last messages. - default web log buffer to 25 lines for ESP32s with no PSRAM - try and determine correct board profile if none is set during boot - auto Scroll in WebLog UI - reduced delay so incoming logs are faster -- uploading custom support info for Guest users [#2054] +- uploading custom support info, shown to Guest users in Help page [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054) +- feature: Dashboard showing all data (favorites, sensors, custom) [#1958](https://github.com/emsesp/EMS-ESP32/issues/1958) ## Fixed @@ -69,7 +70,7 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/). - Change key-names in JSON to be compliant and consistent [#1860](https://github.com/emsesp/EMS-ESP32/issues/1860) - Updates to webUI [#1920](https://github.com/emsesp/EMS-ESP32/issues/1920) - Correct firmware naming #1933 [#1933](https://github.com/emsesp/EMS-ESP32/issues/1933) -- Don't start Serial console if not connected to a Serial port. Will initiate manually after a CTRL-C +- Don't start Serial console if not connected to a Serial port. Will initiate manually after a CTRL-C/CTRL-S - WebLog UI matches color schema of the terminal console correctly - Updated Web libraries, ArduinoJson - Help page doesn't show detailed tech info if the user is not 'admin' role [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 190a2ef93..f27a58201 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -225,6 +225,7 @@ const Dashboard = () => { {tableList.map((di: DashboardItem) => ( + {/* TODO add a comment about the number 99 */} {di.id > 99 ? ( {showName(di)} ) : ( diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index c44325825..9389f65f9 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -117,13 +117,13 @@ export interface CoreData { export interface DashboardItem { id: number; // unique index t?: number; // type from DeviceType - n?: string; // name - dv?: DeviceValue; + n?: string; // name, optional + dv?: DeviceValue; // device value, optional nodes?: DashboardItem[]; // children nodes, optional } export interface DashboardData { - data: DashboardItem[]; + nodes: DashboardItem[]; } export interface DeviceValue { @@ -139,7 +139,7 @@ export interface DeviceValue { } export interface DeviceData { - data: DeviceValue[]; + nodes: DeviceValue[]; } export interface DeviceEntity { diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 545f2b470..c87fd1f3d 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -33,7 +33,16 @@ uint8_t EMSdevice::count_entities() { return count; } -// see if there are entities, excluding any commands +// count favorites, used in Dashboard +uint8_t EMSdevice::count_entities_fav() { + uint8_t count = 0; + for (const auto & dv : devicevalues_) { + count += dv.has_state(DeviceValueState::DV_FAVORITE); + } + return count; +} + +// see if there are customized entities, excluding any commands bool EMSdevice::has_entities() const { for (const auto & dv : devicevalues_) { if (dv.type != DeviceValueType::CMD) { @@ -513,6 +522,7 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const ch } // add to device value library, also know now as a "device entity" +// this function will also apply any customizations to the entity void EMSdevice::add_device_value(int8_t tag, // to be used to group mqtt together, either as separate topics as a nested object void * value_p, // pointer to the value from the .h file uint8_t type, // one of DeviceValueType @@ -900,24 +910,39 @@ bool EMSdevice::export_values(uint8_t device_type, JsonObject output, const int8 } // prepare array of device values used for the WebUI +// this is used for the Dashboard and also the Devices page // this is loosely based of the function generate_values used for the MQTT and Console // except additional data is stored in the JSON document needed for the Web UI like the UOM and command // v=value, u=uom, n=name, c=cmd, h=help string, s=step, m=min, x=max -void EMSdevice::generate_values_web(JsonObject output) { +// see types.ts::DeviceValue for the structure +void EMSdevice::generate_values_web(JsonObject output, const bool is_dashboard) { // output["label"] = to_string_short(); // output["label"] = name_; - JsonArray data = output["data"].to(); + + JsonArray nodes = output["nodes"].to(); + uint8_t count = 0; for (auto & dv : devicevalues_) { auto fullname = dv.get_fullname(); // check conditions: // 1. fullname cannot be empty - // 2. it must have a valid value, if it is not a command like 'reset' - // 3. show favorites first - if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && !fullname.empty() && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) { - JsonObject obj = data.add(); // create the object, we know there is a value - uint8_t fahrenheit = 0; + // 2. it must have a valid value, unless its a command like 'reset' + // 3. if is_dashboard then only show favs + bool matching_states = (is_dashboard) ? dv.has_state(DeviceValueState::DV_FAVORITE) : !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE); + + if (matching_states && !fullname.empty() && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) { + JsonObject root_obj = nodes.add(); // create the object, we know there is a value + + JsonObject obj; + if (is_dashboard) { + root_obj["id"] = (unique_id() * 100) + count++; // make unique + obj = root_obj["dv"].to(); + } else { + obj = root_obj; + } + + uint8_t fahrenheit = 0; // handle Booleans (true, false), output as strings according to the user settings if (dv.type == DeviceValueType::BOOL) { @@ -1021,11 +1046,11 @@ void EMSdevice::generate_values_web(JsonObject output) { } } -// as generate_values_web() but stripped down to only show all entities and their state -// this is used only for WebCustomizationService::device_entities() (rest/deviceEntities?id=n) +// as generate_values_web() but with extra data for WebCustomizationService::device_entities() (rest/deviceEntities?id=n) +// also show commands and entities that have an empty fullname +// see types.ts::DeviceEntity for the structure void EMSdevice::generate_values_web_customization(JsonArray output) { for (auto & dv : devicevalues_) { - // also show commands and entities that have an empty fullname JsonObject obj = output.add(); uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0; @@ -1066,7 +1091,8 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { } } - // id holds the shortname and must always have a value for the WebUI table to work + // create the id + // it holds the shortname and must always have a unique value for the WebUI table to work if (dv.tag >= DeviceValueTAG::TAG_HC1) { char id_s[50]; snprintf(id_s, sizeof(id_s), "%s/%s", tag_to_mqtt(dv.tag), dv.short_name); @@ -1082,7 +1108,6 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { if (fullname) { // obj["n"] = dv.has_tag() ? std::string(tag_to_string(dv.tag)) + " " + fullname : fullname; // prefix tag obj["n"] = fullname; - // TAG https://github.com/emsesp/EMS-ESP32/issues/1338 // obj["n"] = (dv.has_tag()) ? fullname + " " + tag_to_string(dv.tag) : fullname; // suffix tag } @@ -1098,8 +1123,15 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { if (dv.has_tag()) { obj["t"] = tag_to_string(dv.tag); } - obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble - obj["w"] = dv.has_cmd; // if writable + + // the mask state. We're only interested in the high nibble which contains the flags, so shift right + // 0x80 = 128 = DV_FAVORITE + // 0x40 = 64 = DV_READONLY + // 0x20 = 32 = DV_API_MQTT_EXCLUDE + // 0x10 = 16 = DV_WEB_EXCLUDE + obj["m"] = dv.state >> 4; + + obj["w"] = dv.has_cmd; // if writable if (dv.has_cmd && (obj["v"].is() || obj["v"].is())) { // set the min and max values if there are any and if entity has a value @@ -1112,16 +1144,20 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { } } + // apply and blacklisted/removed entities + // this is when the mask has it's high bit (0x80) set + // https://github.com/emsesp/EMS-ESP32/issues/891 EMSESP::webCustomizationService.read([&](WebCustomization & settings) { for (EntityCustomization entityCustomization : settings.entityCustomizations) { if (entityCustomization.device_id == device_id()) { + // entity_ids is a list of all entities with the mask prefixed in the string for (const std::string & entity_id : entityCustomization.entity_ids) { uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); if (mask & 0x80) { JsonObject obj = output.add(); - obj["id"] = DeviceValue::get_name(entity_id); - obj["m"] = mask; - obj["w"] = false; + obj["id"] = DeviceValue::get_name(entity_id); // set the name, it could be custom following a '|' + obj["m"] = mask; // update the mask + obj["w"] = false; // not writeable as it won't be shown } } break; diff --git a/src/emsdevice.h b/src/emsdevice.h index cb7716ed9..b217f1e23 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -230,7 +230,7 @@ class EMSdevice { enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT, CONSOLE }; bool generate_values(JsonObject output, const int8_t tag_filter, const bool nested, const uint8_t output_target); - void generate_values_web(JsonObject output); + void generate_values_web(JsonObject output, const bool is_dashboard = false); void generate_values_web_customization(JsonArray output); void add_device_value(int8_t tag, @@ -452,6 +452,7 @@ class EMSdevice { static constexpr uint8_t EMS_DEVICE_FLAG_CR120 = 16; // mostly like RC300, but some changes uint8_t count_entities(); + uint8_t count_entities_fav(); bool has_entities() const; // void reserve_device_values(uint8_t elements) { @@ -509,9 +510,12 @@ class EMSdevice { std::vector telegram_functions_; // each EMS device has its own set of registered telegram types - std::vector devicevalues_; // all the device values - std::vector handlers_ignored_; + +#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST) + public: // so we can call it from WebCustomizationService::test() +#endif + std::vector devicevalues_; // all the device values }; } // namespace emsesp diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index bb71fc6cf..268c9b4ff 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -473,15 +473,25 @@ uint8_t WebCustomEntityService::count_entities() { } // send to dashboard, msgpack don't like serialized, use number -void WebCustomEntityService::generate_value_web(JsonObject output) { - JsonArray data = output["data"].to(); +void WebCustomEntityService::generate_value_web(JsonObject output, const bool is_dashboard) { + JsonArray nodes = output["nodes"].to(); uint8_t index = 0; for (const CustomEntityItem & entity : *customEntityItems_) { - bool include = false; - JsonObject obj = data.add(); // create the object, we know there is a value - obj["id"] = "00" + entity.name; - obj["u"] = entity.uom; + bool include = false; + JsonObject root_obj = nodes.add(); // create the object, we know there is a value + + JsonObject obj; + if (is_dashboard) { + // TODO make #define for 99 + root_obj["id"] = (99 * 100) + index; // make unique + obj = root_obj["dv"].to(); + } else { + obj = root_obj; + } + + obj["id"] = "00" + entity.name; + obj["u"] = entity.uom; if (entity.writeable) { obj["c"] = entity.name; @@ -549,7 +559,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output) { if (include) { index++; } else { - data.remove(index); + nodes.remove(index); } } } diff --git a/src/web/WebCustomEntityService.h b/src/web/WebCustomEntityService.h index cacefd3b8..ae037c314 100644 --- a/src/web/WebCustomEntityService.h +++ b/src/web/WebCustomEntityService.h @@ -64,7 +64,7 @@ class WebCustomEntityService : public StatefulService { void fetch(); void render_value(JsonObject output, CustomEntityItem & 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); + void generate_value_web(JsonObject output, const bool is_dashboard = false); uint8_t count_entities(); void ha_reset() { diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index f1470df8f..f8d8e7979 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -43,9 +43,13 @@ WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securi server->on(EMSESP_SENSOR_DATA_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest([this](AsyncWebServerRequest * request) { sensor_data(request); }, AuthenticationPredicates::IS_AUTHENTICATED)); + + server->on(EMSESP_DASHBOARD_DATA_SERVICE_PATH, + HTTP_GET, + securityManager->wrapRequest([this](AsyncWebServerRequest * request) { dashboard_data(request); }, AuthenticationPredicates::IS_AUTHENTICATED)); } -// this is used in the dashboard and contains all ems device information +// this is used in the Devices page and contains all EMS device information // /coreData endpoint void WebDataService::core_data(AsyncWebServerRequest * request) { auto * response = new AsyncJsonResponse(false); @@ -150,6 +154,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { } // The unique_id is the unique record ID from the Web table to identify which device to load +// endpoint /rest/deviceData?id=n // Compresses the JSON using MsgPack https://msgpack.org/index.html void WebDataService::device_data(AsyncWebServerRequest * request) { uint8_t id; @@ -346,4 +351,53 @@ void WebDataService::write_analog_sensor(AsyncWebServerRequest * request, JsonVa request->send(response); } +// this is used in the dashboard and contains all ems device information +// /dashboardData endpoint +void WebDataService::dashboard_data(AsyncWebServerRequest * request) { + auto * response = new AsyncJsonResponse(true, true); // its an Array and also msgpack'd + +#if defined(EMSESP_STANDALONE) + JsonDocument doc; + JsonArray root = doc.to(); +#else + JsonArray root = response->getRoot(); +#endif + + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice->count_entities_fav()) { + JsonObject obj = root.add(); + obj["id"] = emsdevice->unique_id(); // it's unique id + obj["n"] = emsdevice->name(); // custom name + obj["t"] = emsdevice->device_type(); // device type number + emsdevice->generate_values_web(obj, true); // is_dashboard = true + } + } + + // add custom entities, if we have any + if (EMSESP::webCustomEntityService.count_entities()) { + JsonObject obj = root.add(); + obj["id"] = 99; // it's unique id + obj["n"] = Helpers::translated_word(FL_(custom_device_name)); // custom name + obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number + EMSESP::webCustomEntityService.generate_value_web(obj, true); + } + + // add temperature sensors + + // add analog sensors + + // show scheduler, with name, on/off - and pencil edit + +#if defined(EMSESP_TEST) && defined(EMSESP_STANDALONE) + Serial.println(); + Serial.print("ALL dashboard_data: "); + serializeJsonPretty(root, Serial); + Serial.println(); +#endif + + response->setLength(); + request->send(response); +} + + } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebDataService.h b/src/web/WebDataService.h index bf8008893..53bf2be88 100644 --- a/src/web/WebDataService.h +++ b/src/web/WebDataService.h @@ -23,6 +23,7 @@ #define EMSESP_CORE_DATA_SERVICE_PATH "/rest/coreData" #define EMSESP_DEVICE_DATA_SERVICE_PATH "/rest/deviceData" #define EMSESP_SENSOR_DATA_SERVICE_PATH "/rest/sensorData" +#define EMSESP_DASHBOARD_DATA_SERVICE_PATH "/rest/dashboardData" // POST #define EMSESP_WRITE_DEVICE_VALUE_SERVICE_PATH "/rest/writeDeviceValue" @@ -44,6 +45,7 @@ class WebDataService { void core_data(AsyncWebServerRequest * request); void sensor_data(AsyncWebServerRequest * request); void device_data(AsyncWebServerRequest * request); + void dashboard_data(AsyncWebServerRequest * request); // POST void write_device_value(AsyncWebServerRequest * request, JsonVariant json);