diff --git a/interface/.env.development b/interface/.env.development index 965a95993..bacf9c796 100644 --- a/interface/.env.development +++ b/interface/.env.development @@ -1,4 +1,4 @@ # Change the IP address to that of your ESP device to enable local development of the UI. # Remember to also enable CORS in platformio.ini before uploading the code to the device. -REACT_APP_HTTP_ROOT=http://10.10.10.81 -REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.81 +REACT_APP_HTTP_ROOT=http://10.10.10.140 +REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.140 diff --git a/interface/src/project/EMSESP.tsx b/interface/src/project/EMSESP.tsx index c8c546576..69be47411 100644 --- a/interface/src/project/EMSESP.tsx +++ b/interface/src/project/EMSESP.tsx @@ -9,6 +9,7 @@ import { AuthenticatedRoute } from '../authentication'; import EMSESPSettingsController from './EMSESPSettingsController'; import EMSESPStatusController from './EMSESPStatusController'; +import EMSESPDevicesController from './EMSESPDevicesController'; class EMSESP extends Component { @@ -21,10 +22,12 @@ class EMSESP extends Component { + + diff --git a/interface/src/project/EMSESPDevicesController.tsx b/interface/src/project/EMSESPDevicesController.tsx new file mode 100644 index 000000000..4db6bac63 --- /dev/null +++ b/interface/src/project/EMSESPDevicesController.tsx @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; + +import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { ENDPOINT_ROOT } from '../api'; +import EMSESPDevicesForm from './EMSESPDevicesForm'; +import { EMSESPDevices } from './types'; + +export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + "emsespDevices"; + +type EMSESPDevicesControllerProps = RestControllerProps; + +class EMSESPDevicesController extends Component { + + componentDidMount() { + this.props.loadData(); + } + + render() { + return ( + + } + /> + + ) + } +} + +export default restController(EMSESP_DEVICES_ENDPOINT, EMSESPDevicesController); diff --git a/interface/src/project/EMSESPDevicesForm.tsx b/interface/src/project/EMSESPDevicesForm.tsx new file mode 100644 index 000000000..5ad32f2c0 --- /dev/null +++ b/interface/src/project/EMSESPDevicesForm.tsx @@ -0,0 +1,184 @@ +import React, { Component, Fragment } from "react"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableRow, + List, + withWidth, + WithWidthProps, + isWidthDown, + Button, + DialogTitle, DialogContent, DialogActions, Box, Dialog, Typography +} from "@material-ui/core"; + +import RefreshIcon from "@material-ui/icons/Refresh"; + +import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; + +import { + RestFormProps, + FormButton, +} from "../components"; + +import { EMSESPDevices, Device } from "./types"; + +import { ENDPOINT_ROOT } from '../api'; +export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices"; + +function compareDevices(a: Device, b: Device) { + if (a.type < b.type) { + return -1; + } + if (a.type > b.type) { + return 1; + } + return 0; +} + +interface EMSESPDevicesFormState { + confirmScanDevices: boolean; + processing: boolean; +} + +type EMSESPDevicesFormProps = RestFormProps & AuthenticatedContextProps & WithWidthProps; + +class EMSESPDevicesForm extends Component { + + state: EMSESPDevicesFormState = { + confirmScanDevices: false, + processing: false + } + + noData = () => { + return (this.props.data.devices.length === 0); + }; + + createListItems() { + const { width, data } = this.props; + return ( + + + + + Type + Brand + Name + Device ID + Product ID + Version + + + + {data.devices.sort(compareDevices).map(device => ( + + + {device.type} + + + {device.brand} + + + {device.name} + + + 0x{('00' + device.deviceid.toString(16).toUpperCase()).slice(-2)} + + + {device.productid} + + + {device.version} + + + ))} + +
+ {this.noData() && + ( + + + No EMS devices found. + + + ) + } +
+ ); + } + + renderScanDevicesDialog() { + return ( + + Confirm Scan Devices + + Are you sure you want to scan the EMS bus for all new devices? + + + + + + + ) + } + + onScanDevices = () => { + this.setState({ confirmScanDevices: true }); + } + + onScanDevicesRejected = () => { + this.setState({ confirmScanDevices: false }); + } + + onScanDevicesConfirmed = () => { + this.setState({ processing: true }); + redirectingAuthorizedFetch(SCANDEVICES_ENDPOINT, { method: 'POST' }) + .then(response => { + if (response.status === 200) { + this.props.enqueueSnackbar("Device scan is starting...", { variant: 'info' }); + this.setState({ processing: false, confirmScanDevices: false }); + } else { + throw Error("Invalid status code: " + response.status); + } + }) + .catch(error => { + this.props.enqueueSnackbar(error.message || "Problem with scan", { variant: 'error' }); + this.setState({ processing: false, confirmScanDevices: false }); + }); + } + + render() { + return ( + + {this.createListItems()} + + + + } variant="contained" color="secondary" onClick={this.props.loadData}> + Refresh + + + + + } variant="contained" color="primary" onClick={this.onScanDevices}> + Scan Devices + + + + {this.renderScanDevicesDialog()} + + ); + } + +} + +export default withAuthenticatedContext(withWidth()(EMSESPDevicesForm)); diff --git a/interface/src/project/EMSESPSettingsController.tsx b/interface/src/project/EMSESPSettingsController.tsx index 3b743ad30..8385577e1 100644 --- a/interface/src/project/EMSESPSettingsController.tsx +++ b/interface/src/project/EMSESPSettingsController.tsx @@ -55,7 +55,7 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps) validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:255']} errorMessages={['TX mode is required', "Must be a number", "Must be 0 or higher", "Max value is 255"]} name="tx_mode" - label="Tx mode (0=off)" + label="Tx Mode (0=off)" fullWidth variant="outlined" value={data.tx_mode} @@ -71,10 +71,10 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps) value="system_heartbeat" /> } - label="MQTT heartbeat" + label="MQTT Heartbeat" /> { @@ -125,7 +125,7 @@ class EMSESPStatusForm extends Component { - (Tx) Send failures + (Tx) Send Errors {data.tx_errors} diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 87a40a41c..1b812c17d 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -30,3 +30,17 @@ export interface EMSESPStatus { uptime: string; free_mem: number; } + +export interface Device { + type: string; + brand: string; + name: string; + deviceid: number; + productid: number; + version: string; +} + +export interface EMSESPDevices { + devices: Device[]; +} + diff --git a/src/EMSESPDevicesService.cpp b/src/EMSESPDevicesService.cpp new file mode 100644 index 000000000..0d02fa82d --- /dev/null +++ b/src/EMSESPDevicesService.cpp @@ -0,0 +1,36 @@ +#include "EMSESPDevicesService.h" +#include "emsesp.h" +#include "mqtt.h" + +namespace emsesp { + +EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager) { + server->on(EMSESP_DEVICES_SERVICE_PATH, + HTTP_GET, + securityManager->wrapRequest(std::bind(&EMSESPDevicesService::emsespDevicesService, this, std::placeholders::_1), + AuthenticationPredicates::IS_AUTHENTICATED)); +} + +void EMSESPDevicesService::emsespDevicesService(AsyncWebServerRequest * request) { + AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_STATUS_SIZE); + JsonObject root = response->getRoot(); + + JsonArray devices = root.createNestedArray("devices"); + + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice) { + JsonObject deviceRoot = devices.createNestedObject(); + deviceRoot["type"] = emsdevice->device_type_name(); + deviceRoot["brand"] = emsdevice->brand_to_string(); + deviceRoot["name"] = emsdevice->name(); + deviceRoot["deviceid"] = emsdevice->device_id(); + deviceRoot["productid"] = emsdevice->product_id(); + deviceRoot["version"] = emsdevice->version(); + } + + response->setLength(); + request->send(response); + } +} + +} // namespace emsesp \ No newline at end of file diff --git a/src/EMSESPDevicesService.h b/src/EMSESPDevicesService.h new file mode 100644 index 000000000..02f7187f1 --- /dev/null +++ b/src/EMSESPDevicesService.h @@ -0,0 +1,30 @@ +#ifndef EMSESPDevicesService_h +#define EMSESPDevicesService_h + +#include +#include +#include +#include + +// #include +// #include +// #include + +#include "version.h" + +#define MAX_EMSESP_STATUS_SIZE 1024 +#define EMSESP_DEVICES_SERVICE_PATH "/rest/emsespDevices" + +namespace emsesp { + +class EMSESPDevicesService { + public: + EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager); + + private: + void emsespDevicesService(AsyncWebServerRequest * request); +}; + +} // namespace emsesp + +#endif diff --git a/src/EMSESPScanDevicesService.cpp b/src/EMSESPScanDevicesService.cpp new file mode 100644 index 000000000..0abb917be --- /dev/null +++ b/src/EMSESPScanDevicesService.cpp @@ -0,0 +1,20 @@ +#include + +#include "emsesp.h" + +namespace emsesp { + +EMSESPScanDevicesService::EMSESPScanDevicesService(AsyncWebServer * server, SecurityManager * securityManager) { + server->on(SCAN_DEVICES_SERVICE_PATH, + HTTP_POST, + securityManager->wrapRequest(std::bind(&EMSESPScanDevicesService::scan_devices, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)); +} + +void EMSESPScanDevicesService::scan_devices(AsyncWebServerRequest * request) { + request->onDisconnect([]() { + EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER); + }); + request->send(200); +} + +} // namespace emsesp diff --git a/src/EMSESPScanDevicesService.h b/src/EMSESPScanDevicesService.h new file mode 100644 index 000000000..0b3a873e4 --- /dev/null +++ b/src/EMSESPScanDevicesService.h @@ -0,0 +1,21 @@ +#ifndef EMSESPScanDevicesService_h +#define EMSESPScanDevicesService_h + +#include +#include + +#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices" + +namespace emsesp { + +class EMSESPScanDevicesService { + public: + EMSESPScanDevicesService(AsyncWebServer * server, SecurityManager * securityManager); + + private: + void scan_devices(AsyncWebServerRequest * request); +}; + +} // namespace emsesp + +#endif diff --git a/src/EMSESPStatusService.cpp b/src/EMSESPStatusService.cpp index 2d59e8e68..1ae7ccbc0 100644 --- a/src/EMSESPStatusService.cpp +++ b/src/EMSESPStatusService.cpp @@ -1,6 +1,7 @@ #include "EMSESPStatusService.h" #include "emsesp.h" #include "mqtt.h" +#include "version.h" namespace emsesp { diff --git a/src/EMSESPStatusService.h b/src/EMSESPStatusService.h index 62bfbd211..318cd6bf3 100644 --- a/src/EMSESPStatusService.h +++ b/src/EMSESPStatusService.h @@ -5,14 +5,7 @@ #include #include #include - - -#include -#include -#include - -#include "EMSESPSettingsService.h" -#include "version.h" +#include #define MAX_EMSESP_STATUS_SIZE 1024 #define EMSESP_STATUS_SERVICE_PATH "/rest/emsespStatus" @@ -24,9 +17,8 @@ class EMSESPStatusService { EMSESPStatusService(AsyncWebServer * server, SecurityManager * securityManager, AsyncMqttClient * mqttClient); private: - EMSESPSettingsService * _emsespSettingsService; - void emsespStatusService(AsyncWebServerRequest * request); - AsyncMqttClient * _mqttClient; + void emsespStatusService(AsyncWebServerRequest * request); + AsyncMqttClient * _mqttClient; void init_mqtt(); }; diff --git a/src/console.cpp b/src/console.cpp index dc5874e23..5d84061f7 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -47,20 +47,6 @@ EMSESPShell::EMSESPShell() void EMSESPShell::started() { logger().log(LogLevel::INFO, LogFacility::CONSOLE, F("User session opened on console %s"), console_name().c_str()); - - // set console name - EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { console_hostname_ = wifiSettings.hostname.c_str(); }); - - if (console_hostname_.empty()) { - console_hostname_.resize(16, '\0'); - - -#if defined(ESP8266) - snprintf_P(&console_hostname_[0], console_hostname_.capacity() + 1, PSTR("esp8266")); -#else - snprintf_P(&console_hostname_[0], console_hostname_.capacity() + 1, PSTR("esp32")); -#endif - } } void EMSESPShell::stopped() { @@ -76,6 +62,7 @@ void EMSESPShell::stopped() { } // show welcome banner +// this is one of the first functions called when the shell is started void EMSESPShell::display_banner() { println(); printfln(F("┌──────────────────────────────────────────┐")); @@ -86,6 +73,18 @@ void EMSESPShell::display_banner() { printfln(F("└──────────────────────────────────────────┘")); println(); + // set console name + EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { console_hostname_ = wifiSettings.hostname.c_str(); }); + + if (console_hostname_.empty()) { + console_hostname_.resize(16, '\0'); +#if defined(ESP8266) + snprintf_P(&console_hostname_[0], console_hostname_.capacity() + 1, PSTR("esp8266")); +#else + snprintf_P(&console_hostname_[0], console_hostname_.capacity() + 1, PSTR("esp32")); +#endif + } + // load the list of commands add_console_commands(); @@ -197,7 +196,7 @@ void EMSESPShell::add_console_commands() { if (arguments.size() == 0) { EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER); } else { - shell.printfln(F("Performing a deep scan by pinging our device library...")); + shell.printfln(F("Performing a deep scan...")); std::vector Device_Ids; Device_Ids.push_back(0x08); // Boilers - 0x08 diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index 62adb9d63..af01bf061 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -239,16 +239,18 @@ void Solar::process_SM100Energy(std::shared_ptr telegram) { void Solar::process_ISM1StatusMessage(std::shared_ptr telegram) { telegram->read_value(collectorTemp_, 4); // Collector Temperature telegram->read_value(bottomTemp_, 6); // Temperature Bottom of Solar Boiler + uint16_t Wh = 0xFFFF; telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10 if (Wh != 0xFFFF) { energyLastHour_ = Wh * 10; // set to *10 } + telegram->read_bitvalue(pump_, 8, 0); // Solar pump on (1) or off (0) telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes - telegram->read_bitvalue(tankHeated_, 9, 2); // issue #422 - telegram->read_bitvalue(collectorOnOff_, 9, 0); + telegram->read_bitvalue(collectorOnOff_, 9, 0); // collector on/off + telegram->read_bitvalue(tankHeated_, 9, 2); // tank full } /* diff --git a/src/emsdevice.h b/src/emsdevice.h index 55de3445c..8b691bcee 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -82,6 +82,10 @@ class EMSdevice { version_ = version; } + inline std::string version() const { + return version_; + } + inline void brand(uint8_t brand) { brand_ = brand; } @@ -94,6 +98,10 @@ class EMSdevice { name_ = name; } + inline std::string name() const { + return name_; + } + std::string brand_to_string() const; static uint8_t decode_brand(uint8_t value); diff --git a/src/emsesp.cpp b/src/emsesp.cpp index bd81b8147..f7a957dce 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -38,6 +38,10 @@ EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&web EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager(), EMSESP::esp8266React.getMqttClient()); +EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager()); + +EMSESPScanDevicesService EMSESP::emsespScanDevicesService = EMSESPScanDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager()); + std::vector> EMSESP::emsdevices; // array of all the detected EMS devices std::vector EMSESP::device_library_; // libary of all our known EMS devices so far @@ -60,7 +64,7 @@ bool EMSESP::tap_water_active_ = false; / uint32_t EMSESP::last_fetch_ = 0; // for a specific EMS device go and request data values -// or if device_id is 0 it will fetch from all known devices +// or if device_id is 0 it will fetch from all our known and active devices void EMSESP::fetch_device_values(const uint8_t device_id) { for (const auto & emsdevice : emsdevices) { if (emsdevice) { @@ -103,13 +107,16 @@ void EMSESP::watch_id(uint16_t watch_id) { } } +// change the tx_mode +// resets all counters and bumps the UART void EMSESP::reset_tx(uint8_t const tx_mode) { txservice_.telegram_read_count(0); txservice_.telegram_write_count(0); txservice_.telegram_fail_count(0); if (tx_mode) { EMSuart::stop(); - EMSuart::start(tx_mode); // reset the UART + EMSuart::start(tx_mode); + EMSESP::fetch_device_values(); } } diff --git a/src/emsesp.h b/src/emsesp.h index afe885d17..145f2c43f 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -35,7 +35,9 @@ #include #include "EMSESPStatusService.h" +#include "EMSESPDevicesService.h" #include "EMSESPSettingsService.h" +#include "EMSESPScanDevicesService.h" #include "emsdevice.h" #include "emsfactory.h" @@ -133,6 +135,9 @@ class EMSESP { static bool add_device(const uint8_t device_id, const uint8_t product_id, std::string & version, const uint8_t brand); + static std::vector> emsdevices; + + // services static Mqtt mqtt_; static System system_; static Sensors sensors_; @@ -141,9 +146,12 @@ class EMSESP { static RxService rxservice_; static TxService txservice_; - static ESP8266React esp8266React; - static EMSESPSettingsService emsespSettingsService; - static EMSESPStatusService emsespStatusService; + // web controllers + static ESP8266React esp8266React; + static EMSESPSettingsService emsespSettingsService; + static EMSESPStatusService emsespStatusService; + static EMSESPDevicesService emsespDevicesService; + static EMSESPScanDevicesService emsespScanDevicesService; private: EMSESP() = delete; @@ -165,8 +173,7 @@ class EMSESP { uint8_t flags; }; - static std::vector> emsdevices; - static std::vector device_library_; + static std::vector device_library_; static uint8_t actual_master_thermostat_; static uint16_t watch_id_; diff --git a/src/version.h b/src/version.h index 698f79597..75ca79984 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.0.0_b1" // based off b32 +#define EMSESP_APP_VERSION "2.0.0_b2" // based off a32