diff --git a/README.md b/README.md index 213e9fde1..6a2a745c4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ ## **Breaking changes** -- MQTT base has been removed. The hostname is only used. +- MQTT base has been removed. The hostname is only used +- refresh command renamed to fetch +- have to 'wifi reconnect' after changing wifi in console ## **New Features in v2** @@ -65,7 +67,7 @@ common commands available in all contexts: (from the root) set - refresh + fetch system (enters a context) boiler (enters a context) thermostat (enters a context) @@ -82,11 +84,13 @@ system set show show mqtt + show users passwd restart set wifi hostname set wifi password set wifi ssid + wifi reconnect boiler comfort @@ -96,6 +100,8 @@ boiler wwonetime wwtemp read + maxpower <%> + minpower <%> thermostat set @@ -106,6 +112,61 @@ thermostat ``` ---------- +### **mqtt commands** + +commands can be written as `{"cmd": ,"data": }` or direct. +To set thermostat commands depending on heatingcircuits add `"hc": ` or nest the command. Without `"hc":` the first ative heatingcircuit is set. +These commands are equivalent: +`{"hc":2,"cmd":"daytemp","data":21}` or `{"hc":2,"daytemp":21}`or `{"hc2"{"daytemp":21}}` +In direct commands it's possible to combine commands, i.e. `{"hc1":{"daytemp":21,"nighttemp":17},"hc2":{"daytemp":20}}` +``` +*boiler_cmd* + comfort + flowtemp + wwtemp + boilhyston (negative value) + boilhystoff (positive value) + burnperiod + burnminpower <%> + burnmaxpower <%> + pumpdelay + +*thermostat_cmd* +--- without hc --- + wwmode + calinttemp + minexttemp + building + language (0=de, 1=nl, 2=fr, 3=it) only RC30 + display (0=int temp, 1= int set, 2=ext. temp, 3=burner, 4=ww, 5=mode, 6=time, 7=date, 8=smoke) only RC30 + clockoffset (only RC30) +--- with hc --- + mode + temp + nighttemp + daytemp + nofrosttemp + ecotemp + heattemp + summertemp + designtemp + offsettemp + holidaytemp + remotetemp + control <0 | 1 | 2> + pause + party + holiday + date + +*cmd* + send <"0B XX XX .."> + D0 <0 | 1> + D1 <0 | 1> + D2 <0 | 1> + D3 <0 | 1> + +``` ### **Basic Design Principles** @@ -178,7 +239,7 @@ The Web is based off Rick's awesome [esp8266-react](https://github.com/rjwats/es * `MqttStatus.cpp` added root["mqtt_fails"] * `SecuritySettingsService.cpp` added version to the JWT payload * `SecuritySettingsService.h` #include "../../src/version.h" - * `WiFiSettingsService.cpp` added WiFi.setOutputPower(20.0f) - removed + * `WiFiSettingsService.cpp` added WiFi.setOutputPower(20.0f), moved setHostname * `OTASettingsService.h` added #include "../../src/system.h" * `OTASettingsService.cpp` added call to emsesp::System::upload_status(true) * `features.ini`: -D FT_NTP=0 diff --git a/factory_settings.ini b/factory_settings.ini index 92058a03b..12a99b948 100644 --- a/factory_settings.ini +++ b/factory_settings.ini @@ -36,12 +36,10 @@ build_flags = -D FACTORY_MQTT_PORT=1883 -D FACTORY_MQTT_USERNAME=\"\" -D FACTORY_MQTT_PASSWORD=\"\" - ; if unspecified the devices hardware ID will be used -D FACTORY_MQTT_CLIENT_ID=\"ems-esp\" -D FACTORY_MQTT_KEEP_ALIVE=60 -D FACTORY_MQTT_CLEAN_SESSION=false -D FACTORY_MQTT_MAX_TOPIC_LENGTH=128 ; JWT Secret - ; if unspecified the devices hardware ID will be used -D FACTORY_JWT_SECRET=\"ems-esp-neo\" diff --git a/features.ini b/features.ini deleted file mode 100644 index 00c36df21..000000000 --- a/features.ini +++ /dev/null @@ -1,8 +0,0 @@ -[features] -build_flags = - -D FT_PROJECT=1 - -D FT_SECURITY=1 - -D FT_MQTT=1 - -D FT_NTP=0 - -D FT_OTA=1 - -D FT_UPLOAD_FIRMWARE=1 diff --git a/interface/.env.development b/interface/.env.development index 6fb8acf00..acbd05bab 100644 --- a/interface/.env.development +++ b/interface/.env.development @@ -2,9 +2,9 @@ # Remember to also enable CORS in platformio.ini before uploading the code to the device. # ESP32 dev -#REACT_APP_HTTP_ROOT=http://10.10.10.194 -#REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.194 +REACT_APP_HTTP_ROOT=http://10.10.10.194 +REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.194 # ESP8266 dev -REACT_APP_HTTP_ROOT=http://10.10.10.140 -REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.140 \ No newline at end of file +#REACT_APP_HTTP_ROOT=http://10.10.10.140 +#REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.140 \ No newline at end of file diff --git a/interface/src/project/EMSESP.tsx b/interface/src/project/EMSESP.tsx index bc2e19971..2d88cd1a9 100644 --- a/interface/src/project/EMSESP.tsx +++ b/interface/src/project/EMSESP.tsx @@ -22,7 +22,7 @@ class EMSESP extends Component { - + diff --git a/interface/src/project/EMSESPDevicesController.tsx b/interface/src/project/EMSESPDevicesController.tsx index 52945963a..bd035f933 100644 --- a/interface/src/project/EMSESPDevicesController.tsx +++ b/interface/src/project/EMSESPDevicesController.tsx @@ -5,7 +5,7 @@ import { ENDPOINT_ROOT } from '../api'; import EMSESPDevicesForm from './EMSESPDevicesForm'; import { EMSESPDevices } from './EMSESPtypes'; -export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + "emsespDevices"; +export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + "allDevices"; type EMSESPDevicesControllerProps = RestControllerProps; @@ -17,7 +17,7 @@ class EMSESPDevicesController extends Component { render() { return ( - + } diff --git a/interface/src/project/EMSESPDevicesForm.tsx b/interface/src/project/EMSESPDevicesForm.tsx index 41baa437d..50673747e 100644 --- a/interface/src/project/EMSESPDevicesForm.tsx +++ b/interface/src/project/EMSESPDevicesForm.tsx @@ -24,10 +24,11 @@ import { FormButton, } from "../components"; -import { EMSESPDevices, Device } from "./EMSESPtypes"; +import { EMSESPDevices, EMSESPDeviceData, Device } from "./EMSESPtypes"; import { ENDPOINT_ROOT } from '../api'; export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices"; +export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + "deviceData"; const StyledTableCell = withStyles((theme: Theme) => createStyles({ @@ -54,6 +55,7 @@ function compareDevices(a: Device, b: Device) { interface EMSESPDevicesFormState { confirmScanDevices: boolean; processing: boolean; + deviceData?: EMSESPDeviceData; } type EMSESPDevicesFormProps = RestFormProps & AuthenticatedContextProps & WithWidthProps; @@ -65,15 +67,26 @@ class EMSESPDevicesForm extends Component { + noDevices = () => { return (this.props.data.devices.length === 0); }; - createTableItems() { + noSensors = () => { + return (this.props.data.sensors.length === 0); + }; + + noDeviceData = () => { + return (this.state.deviceData?.deviceData.length === 0); + }; + + createDeviceItems() { const { width, data } = this.props; return ( - {!this.noData() && ( + + Devices: + + {!this.noDevices() && ( @@ -87,7 +100,9 @@ class EMSESPDevicesForm extends Component {data.devices.sort(compareDevices).map(device => ( - + this.handleRowClick(device.id)} + > {device.type} @@ -111,11 +126,54 @@ class EMSESPDevicesForm extends Component
)} - {this.noData() && + {this.noDevices() && ( - No EMS devices found. Check the connection and for possible Tx errors and try scanning for new devices. + No EMS devices found. Check the connections and for possible Tx errors. + + + ) + } +
+ ); + } + + createSensorItems() { + const { data } = this.props; + return ( + +

+ + Sensors: + + {!this.noSensors() && ( + + + + ID + Temperature + + + + {data.sensors.map(sensorData => ( + + + {sensorData.id} + + + {sensorData.temp}°C + + + ))} + +
+ )} + {this.noSensors() && + ( + + + No external temperature sensors detected. ) @@ -156,26 +214,97 @@ class EMSESPDevicesForm extends Component { 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); - } - }) + redirectingAuthorizedFetch(SCANDEVICES_ENDPOINT).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 }); }); } + handleRowClick = (id: any) => { + this.setState({ deviceData: undefined }); + redirectingAuthorizedFetch(DEVICE_DATA_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ id: id }), + headers: { + 'Content-Type': 'application/json' + } + }).then(response => { + if (response.status === 200) { + return response.json(); + // this.setState({ errorMessage: undefined }, this.props.loadData); + } + throw Error("Unexpected response code: " + response.status); + }).then(json => { + this.setState({ deviceData: json }); + }).catch(error => { + this.props.enqueueSnackbar(error.message || "Problem getting device data", { variant: 'error' }); + this.setState({ deviceData: undefined }); + }); + } + + renderDeviceData() { + const { deviceData } = this.state; + const { width } = this.props; + + if (this.noDevices()) { + return; + } + + if (!deviceData) { + return; + } + + if ((deviceData.deviceData || []).length === 0) { + return; + } + + return ( + +

+ + + {deviceData.deviceName} + + + + + + + + + {deviceData.deviceData.map(deviceData => ( + + + {deviceData.name} + + + {deviceData.value} + + + ))} + +
+
+
+ ); + + } + render() { return (

- {this.createTableItems()} + {this.createDeviceItems()} + {this.renderDeviceData()} + {this.createSensorItems()}

diff --git a/interface/src/project/EMSESPtypes.ts b/interface/src/project/EMSESPtypes.ts index 91fed67ee..43084bf9b 100644 --- a/interface/src/project/EMSESPtypes.ts +++ b/interface/src/project/EMSESPtypes.ts @@ -24,6 +24,7 @@ export interface EMSESPStatus { } export interface Device { + id: number; type: string; brand: string; name: string; @@ -32,7 +33,23 @@ export interface Device { version: string; } -export interface EMSESPDevices { - devices: Device[]; +export interface Sensor { + id: string; + temp: number; +} + +export interface EMSESPDevices { + devices: Device[]; + sensors: Sensor[]; +} + +export interface DeviceData { + name: string; + value: string; +} + +export interface EMSESPDeviceData { + deviceName: string; + deviceData: DeviceData[]; } diff --git a/lib/OneWire/OneWire.cpp b/lib/OneWire/OneWire.cpp index 7a4000e64..cd9dc8448 100644 --- a/lib/OneWire/OneWire.cpp +++ b/lib/OneWire/OneWire.cpp @@ -144,7 +144,7 @@ sample code bearing this copyright. #include "OneWire_direct_gpio.h" #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-variable" void OneWire::begin(uint8_t pin) { pinMode(pin, INPUT); @@ -155,14 +155,17 @@ void OneWire::begin(uint8_t pin) { #endif } - // Perform the onewire reset function. We will wait up to 250uS for // the bus to come high, if it doesn't then it is broken or shorted // and we return a 0; // // Returns 1 if a device asserted a presence pulse, 0 otherwise. // +#ifdef ARDUINO_ARCH_ESP32 +uint8_t IRAM_ATTR OneWire::reset(void) { +#else uint8_t OneWire::reset(void) { +#endif IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg; uint8_t r; @@ -196,7 +199,11 @@ uint8_t OneWire::reset(void) { // Write a bit. Port and bit is used to cut lookup time and provide // more certain timing. // +#ifdef ARDUINO_ARCH_ESP32 +void IRAM_ATTR OneWire::write_bit(uint8_t v) { +#else void OneWire::write_bit(uint8_t v) { +#endif IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg; @@ -223,7 +230,11 @@ void OneWire::write_bit(uint8_t v) { // Read a bit. Port and bit is used to cut lookup time and provide // more certain timing. // +#ifdef ARDUINO_ARCH_ESP32 +uint8_t IRAM_ATTR OneWire::read_bit(void) { +#else uint8_t OneWire::read_bit(void) { +#endif IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg; uint8_t r; @@ -474,6 +485,7 @@ bool OneWire::search(uint8_t * newAddr, bool search_mode /* = true */) { for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; } + // depower(); // https://github.com/PaulStoffregen/OneWire/pull/80 return search_result; } diff --git a/lib/OneWire/OneWire.h b/lib/OneWire/OneWire.h index 4b1933060..436eecd1f 100644 --- a/lib/OneWire/OneWire.h +++ b/lib/OneWire/OneWire.h @@ -78,7 +78,11 @@ class OneWire { // Perform a 1-Wire reset cycle. Returns 1 if a device responds // with a presence pulse. Returns 0 if there is no device or the // bus is shorted or otherwise held low for more than 250uS +#ifdef ARDUINO_ARCH_ESP32 + uint8_t IRAM_ATTR reset(void); +#else uint8_t reset(void); +#endif // Issue a 1-Wire rom select command, you do the reset first. void select(const uint8_t rom[8]); @@ -101,11 +105,18 @@ class OneWire { // Write a bit. The bus is always left powered at the end, see // note in write() about that. - void write_bit(uint8_t v); + #ifdef ARDUINO_ARCH_ESP32 + void IRAM_ATTR write_bit(uint8_t v); + #else + void write_bit(uint8_t v); +#endif // Read a bit. +#ifdef ARDUINO_ARCH_ESP32 + uint8_t IRAM_ATTR read_bit(void); +#else uint8_t read_bit(void); - +#endif // Stop forcing power onto the bus. You only need to do this if // you used the 'power' flag to write() or used a write_bit() call // and aren't about to do another read or write. You would rather diff --git a/lib/framework/SystemStatus.cpp b/lib/framework/SystemStatus.cpp index 1a5c1c6df..017e5c723 100644 --- a/lib/framework/SystemStatus.cpp +++ b/lib/framework/SystemStatus.cpp @@ -7,6 +7,8 @@ SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityMa } void SystemStatus::systemStatus(AsyncWebServerRequest * request) { + uint8_t free_mem_percent = emsesp::System::free_mem(); // added by proddy + AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE); JsonObject root = response->getRoot(); #ifdef ESP32 @@ -39,7 +41,7 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) { #endif root["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); // proddy added - root["free_mem"] = emsesp::System::free_mem(); // proddy added + root["free_mem"] = free_mem_percent; // proddy added response->setLength(); request->send(response); diff --git a/lib/framework/WiFiSettingsService.cpp b/lib/framework/WiFiSettingsService.cpp index 8f03a3e2f..2c9c9ad5e 100644 --- a/lib/framework/WiFiSettingsService.cpp +++ b/lib/framework/WiFiSettingsService.cpp @@ -1,27 +1,25 @@ #include -WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : - _httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager), - _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE), - _lastConnectionAttempt(0) { - // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. - // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. - if (WiFi.getMode() != WIFI_OFF) { - WiFi.mode(WIFI_OFF); - } +WiFiSettingsService::WiFiSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) + : _httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager) + , _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE) + , _lastConnectionAttempt(0) { + // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. + // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. + if (WiFi.getMode() != WIFI_OFF) { + WiFi.mode(WIFI_OFF); + } - // Disable WiFi config persistance and auto reconnect - WiFi.persistent(false); - WiFi.setAutoReconnect(false); + // Disable WiFi config persistance and auto reconnect + WiFi.persistent(false); + WiFi.setAutoReconnect(false); #ifdef ESP32 - // Init the wifi driver on ESP32 - WiFi.mode(WIFI_MODE_MAX); - WiFi.mode(WIFI_MODE_NULL); - WiFi.onEvent( - std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), - WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); - WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2), - WiFiEvent_t::SYSTEM_EVENT_STA_STOP); + // Init the wifi driver on ESP32 + WiFi.mode(WIFI_MODE_MAX); + WiFi.mode(WIFI_MODE_NULL); + WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), + WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); + WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_STOP); #elif defined(ESP8266) // proddy added @@ -31,78 +29,81 @@ WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, Securit // high tx power causing weird behavior, slightly lowering from 20.5 to 20.0 may help stability // WiFi.setOutputPower(20.0f); // in dBm - _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected( - std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1)); + _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1)); #endif - addUpdateHandler([&](const String& originId) { reconfigureWiFiConnection(); }, false); + addUpdateHandler([&](const String & originId) { reconfigureWiFiConnection(); }, false); } void WiFiSettingsService::begin() { - _fsPersistence.readFromFS(); - reconfigureWiFiConnection(); + _fsPersistence.readFromFS(); + reconfigureWiFiConnection(); } void WiFiSettingsService::reconfigureWiFiConnection() { - // reset last connection attempt to force loop to reconnect immediately - _lastConnectionAttempt = 0; + // reset last connection attempt to force loop to reconnect immediately + _lastConnectionAttempt = 0; // disconnect and de-configure wifi #ifdef ESP32 - if (WiFi.disconnect(true)) { - _stopping = true; - } + if (WiFi.disconnect(true)) { + _stopping = true; + } #elif defined(ESP8266) - WiFi.disconnect(true); + WiFi.disconnect(true); #endif } void WiFiSettingsService::loop() { - unsigned long currentMillis = millis(); - if (!_lastConnectionAttempt || (unsigned long)(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) { - _lastConnectionAttempt = currentMillis; - manageSTA(); - } + unsigned long currentMillis = millis(); + if (!_lastConnectionAttempt || (unsigned long)(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) { + _lastConnectionAttempt = currentMillis; + manageSTA(); + } } void WiFiSettingsService::manageSTA() { - // Abort if already connected, or if we have no SSID - if (WiFi.isConnected() || _state.ssid.length() == 0) { - return; - } - // Connect or reconnect as required - if ((WiFi.getMode() & WIFI_STA) == 0) { - // Serial.println(F("Connecting to WiFi.")); - if (_state.staticIPConfig) { - // configure for static IP - WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); - } else { - // configure for DHCP -#ifdef ESP32 - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - WiFi.setHostname(_state.hostname.c_str()); -#elif defined(ESP8266) - WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY); - WiFi.hostname(_state.hostname); -#endif + // Abort if already connected, or if we have no SSID + if (WiFi.isConnected() || _state.ssid.length() == 0) { + return; + } + // Connect or reconnect as required + if ((WiFi.getMode() & WIFI_STA) == 0) { + // Serial.println(F("Connecting to WiFi.")); + if (_state.staticIPConfig) { + // configure for static IP + WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); + } else { + // configure for DHCP +#ifdef ESP32 + WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); +#elif defined(ESP8266) + WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY); +#endif + } + // set hostname +#ifdef ESP32 + WiFi.setHostname(_state.hostname.c_str()); +#elif defined(ESP8266) + WiFi.hostname(_state.hostname); +#endif + // attempt to connect to the network + WiFi.begin(_state.ssid.c_str(), _state.password.c_str()); } - // attempt to connect to the network - WiFi.begin(_state.ssid.c_str(), _state.password.c_str()); - } } #ifdef ESP32 void WiFiSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { - WiFi.disconnect(true); + WiFi.disconnect(true); } void WiFiSettingsService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info) { - if (_stopping) { - _lastConnectionAttempt = 0; - _stopping = false; - } + if (_stopping) { + _lastConnectionAttempt = 0; + _stopping = false; + } } #elif defined(ESP8266) -void WiFiSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) { - WiFi.disconnect(true); +void WiFiSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) { + WiFi.disconnect(true); } #endif diff --git a/lib_standalone/Arduino.cpp b/lib_standalone/Arduino.cpp index b2bf854ac..453a1539e 100644 --- a/lib_standalone/Arduino.cpp +++ b/lib_standalone/Arduino.cpp @@ -111,71 +111,3 @@ int digitalRead(uint8_t pin) { return LOW; } } - - - - -/* - * Copy string src to buffer dst of size dsize. At most dsize-1 - * chars will be copied. Always NUL terminates (unless dsize == 0). - * Returns strlen(src); if retval >= dsize, truncation occurred. - * - * https://github.com/freebsd/freebsd/blob/master/sys/libkern/strlcpy.c - */ -size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize) { - const char * osrc = src; - size_t nleft = dsize; - - /* Copy as many bytes as will fit. */ - if (nleft != 0) { - while (--nleft != 0) { - if ((*dst++ = *src++) == '\0') - break; - } - } - - /* Not enough room in dst, add NUL and traverse rest of src. */ - if (nleft == 0) { - if (dsize != 0) - *dst = '\0'; /* NUL-terminate dst */ - while (*src++) - ; - } - - return (src - osrc - 1); /* count does not include NUL */ -} - -/* - * Appends src to string dst of size siz (unlike strncat, siz is the - * full size of dst, not space left). At most siz-1 characters - * will be copied. Always NUL terminates (unless siz <= strlen(dst)). - * Returns strlen(src) + MIN(siz, strlen(initial dst)). - * If retval >= siz, truncation occurred. - * - * https://github.com/freebsd/freebsd/blob/master/sys/libkern/strlcat.c - */ -size_t strlcat(char * dst, const char * src, size_t siz) { - char * d = dst; - const char * s = src; - size_t n = siz; - size_t dlen; - - /* Find the end of dst and adjust bytes left but don't go past end */ - while (n-- != 0 && *d != '\0') - d++; - dlen = d - dst; - n = siz - dlen; - - if (n == 0) - return (dlen + strlen(s)); - while (*s != '\0') { - if (n != 1) { - *d++ = *s; - n--; - } - s++; - } - *d = '\0'; - - return (dlen + (s - src)); /* count does not include NUL */ -} diff --git a/lib_standalone/Arduino.h b/lib_standalone/Arduino.h index 459fba0d8..fbefdc5f7 100644 --- a/lib_standalone/Arduino.h +++ b/lib_standalone/Arduino.h @@ -136,34 +136,6 @@ class Stream : public Print { virtual int peek() = 0; }; -/* -class String { - public: - String(const char * data = "") - : data_(data) { - } - - long toInt() const { - return std::stol(data_); - } - - const char * c_str() const { - return data_.c_str(); - } - - bool equals(String comp) { - return (data_ == comp.c_str()); - } - - bool isEmpty() { - return data_.empty(); - } - - private: - std::string data_; -}; -*/ - class NativeConsole : public Stream { public: void begin(unsigned long baud __attribute__((unused))) { @@ -225,10 +197,6 @@ void yield(void); void setup(void); void loop(void); - -size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize); -size_t strlcat(char * dst, const char * src, size_t siz); - #include "WString.h" #endif diff --git a/lib_standalone/AsyncJson.h b/lib_standalone/AsyncJson.h index e9a36ba76..c957646d5 100644 --- a/lib_standalone/AsyncJson.h +++ b/lib_standalone/AsyncJson.h @@ -3,16 +3,11 @@ #define ASYNC_JSON_H_ #include #include -// #include #define DYNAMIC_JSON_DOCUMENT_SIZE 1024 constexpr const char * JSON_MIMETYPE = "application/json"; -/* - * Json Response - * */ - class ChunkPrint : public Print { private: uint8_t * _destination; @@ -45,9 +40,8 @@ class ChunkPrint : public Print { } }; -class AsyncJsonResponse { +class AsyncJsonResponse { protected: - DynamicJsonDocument _jsonBuffer; JsonVariant _root; @@ -71,7 +65,7 @@ class AsyncJsonResponse { return _isValid; } size_t setLength() { -return 0; + return 0; } size_t getSize() { @@ -79,9 +73,6 @@ return 0; } size_t _fillBuffer(uint8_t * data, size_t len) { - // ChunkPrint dest(data, 0, len); - - // serializeJson(_root, dest); return len; } }; diff --git a/lib_standalone/AsyncMqttClient.h b/lib_standalone/AsyncMqttClient.h index 60751f0f9..65e40b2c8 100644 --- a/lib_standalone/AsyncMqttClient.h +++ b/lib_standalone/AsyncMqttClient.h @@ -5,139 +5,114 @@ #include enum class AsyncMqttClientDisconnectReason : int8_t { - TCP_DISCONNECTED = 0, + TCP_DISCONNECTED = 0, - MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, - MQTT_IDENTIFIER_REJECTED = 2, - MQTT_SERVER_UNAVAILABLE = 3, - MQTT_MALFORMED_CREDENTIALS = 4, - MQTT_NOT_AUTHORIZED = 5, + MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, + MQTT_IDENTIFIER_REJECTED = 2, + MQTT_SERVER_UNAVAILABLE = 3, + MQTT_MALFORMED_CREDENTIALS = 4, + MQTT_NOT_AUTHORIZED = 5, - ESP8266_NOT_ENOUGH_SPACE = 6, + ESP8266_NOT_ENOUGH_SPACE = 6, - TLS_BAD_FINGERPRINT = 7 + TLS_BAD_FINGERPRINT = 7 }; struct AsyncMqttClientMessageProperties { - uint8_t qos; - bool dup; - bool retain; + uint8_t qos; + bool dup; + bool retain; }; - namespace AsyncMqttClientInternals { -// user callbacks -typedef std::function OnConnectUserCallback; + +typedef std::function OnConnectUserCallback; typedef std::function OnDisconnectUserCallback; -typedef std::function OnSubscribeUserCallback; -typedef std::function OnUnsubscribeUserCallback; -typedef std::function OnMessageUserCallback; +typedef std::function OnSubscribeUserCallback; +typedef std::function OnUnsubscribeUserCallback; +typedef std::function OnMessageUserCallback; typedef std::function OnPublishUserCallback; -}; +}; // namespace AsyncMqttClientInternals class AsyncMqttClient { - public: - AsyncMqttClient(); - ~AsyncMqttClient(); + public: + AsyncMqttClient(); + ~AsyncMqttClient(); - AsyncMqttClient& setKeepAlive(uint16_t keepAlive); - AsyncMqttClient& setClientId(const char* clientId); - AsyncMqttClient& setCleanSession(bool cleanSession); - AsyncMqttClient& setMaxTopicLength(uint16_t maxTopicLength); - AsyncMqttClient& setCredentials(const char* username, const char* password = nullptr); - AsyncMqttClient& setWill(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0) { return *this; } - AsyncMqttClient& setServer(IPAddress ip, uint16_t port); - AsyncMqttClient& setServer(const char* host, uint16_t port); + AsyncMqttClient & setKeepAlive(uint16_t keepAlive); + AsyncMqttClient & setClientId(const char * clientId); + AsyncMqttClient & setCleanSession(bool cleanSession); + AsyncMqttClient & setMaxTopicLength(uint16_t maxTopicLength); + AsyncMqttClient & setCredentials(const char * username, const char * password = nullptr); + AsyncMqttClient & setWill(const char * topic, uint8_t qos, bool retain, const char * payload = nullptr, size_t length = 0) { + return *this; + } + AsyncMqttClient & setServer(IPAddress ip, uint16_t port); + AsyncMqttClient & setServer(const char * host, uint16_t port); - AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) { return *this; } - AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) { return *this; } - AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) { return *this; } - AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) { return *this; } - AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) { return *this; } - AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) { return *this; } + AsyncMqttClient & onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) { + return *this; + } + AsyncMqttClient & onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) { + return *this; + } + AsyncMqttClient & onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) { + return *this; + } + AsyncMqttClient & onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) { + return *this; + } + AsyncMqttClient & onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) { + return *this; + } + AsyncMqttClient & onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) { + return *this; + } - bool connected() const { return false; } - void connect() {} - void disconnect(bool force = false) {} - uint16_t subscribe(const char* topic, uint8_t qos) {return 0;} - uint16_t unsubscribe(const char* topic) {return 0;} - uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0) {return 0;} + bool connected() const { + return false; + } + void connect() { + } + void disconnect(bool force = false) { + } + uint16_t subscribe(const char * topic, uint8_t qos) { + return 0; + } + uint16_t unsubscribe(const char * topic) { + return 0; + } + uint16_t publish(const char * topic, uint8_t qos, bool retain, const char * payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0) { + return 0; + } - const char* getClientId() {return "12";} + const char * getClientId() { + return "12"; + } - private: -// AsyncClient _client; - - bool _connected; - bool _connectPacketNotEnoughSpace; - bool _disconnectOnPoll; - bool _tlsBadFingerprint; - uint32_t _lastClientActivity; - uint32_t _lastServerActivity; - uint32_t _lastPingRequestTime; - char _generatedClientId[18 + 1]; // esp8266-abc123 and esp32-abcdef123456 - IPAddress _ip; - const char* _host; - bool _useIp; - uint16_t _port; - uint16_t _keepAlive; - bool _cleanSession; - const char* _clientId; - const char* _username; - const char* _password; - const char* _willTopic; - const char* _willPayload; - uint16_t _willPayloadLength; - uint8_t _willQos; - bool _willRetain; - -// std::vector _onConnectUserCallbacks; -// std::vector _onDisconnectUserCallbacks; -// std::vector _onSubscribeUserCallbacks; -// std::vector _onUnsubscribeUserCallbacks; -// std::vector _onMessageUserCallbacks; -// std::vector _onPublishUserCallbacks; - -// AsyncMqttClientInternals::ParsingInformation _parsingInformation; -// AsyncMqttClientInternals::Packet* _currentParsedPacket; -// uint8_t _remainingLengthBufferPosition; -// char _remainingLengthBuffer[4]; - -// uint16_t _nextPacketId; - -// std::vector _pendingPubRels; - -// std::vector _toSendAcks; - -// void _clear(); -// void _freeCurrentParsedPacket(); - - // TCP -// void _onConnect(AsyncClient* client); -// void _onDisconnect(AsyncClient* client); -// static void _onError(AsyncClient* client, int8_t error); -// void _onTimeout(AsyncClient* client, uint32_t time); -// static void _onAck(AsyncClient* client, size_t len, uint32_t time); -// void _onData(AsyncClient* client, char* data, size_t len); -// void _onPoll(AsyncClient* client); - -// // MQTT -// void _onPingResp(); -// void _onConnAck(bool sessionPresent, uint8_t connectReturnCode); -// void _onSubAck(uint16_t packetId, char status); -// void _onUnsubAck(uint16_t packetId); -// void _onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId); -// void _onPublish(uint16_t packetId, uint8_t qos); -// void _onPubRel(uint16_t packetId); -// void _onPubAck(uint16_t packetId); -// void _onPubRec(uint16_t packetId); -// void _onPubComp(uint16_t packetId); - -// bool _sendPing(); -// void _sendAcks(); -// bool _sendDisconnect(); - -// uint16_t _getNextPacketId(); + private: + bool _connected; + bool _connectPacketNotEnoughSpace; + bool _disconnectOnPoll; + bool _tlsBadFingerprint; + uint32_t _lastClientActivity; + uint32_t _lastServerActivity; + uint32_t _lastPingRequestTime; + char _generatedClientId[18 + 1]; // esp8266-abc123 and esp32-abcdef123456 + IPAddress _ip; + const char * _host; + bool _useIp; + uint16_t _port; + uint16_t _keepAlive; + bool _cleanSession; + const char * _clientId; + const char * _username; + const char * _password; + const char * _willTopic; + const char * _willPayload; + uint16_t _willPayloadLength; + uint8_t _willQos; + bool _willRetain; }; #endif \ No newline at end of file diff --git a/lib_standalone/AsyncTCP.h b/lib_standalone/AsyncTCP.h index 3b82e964d..c06bbd0a5 100644 --- a/lib_standalone/AsyncTCP.h +++ b/lib_standalone/AsyncTCP.h @@ -1,212 +1,29 @@ -/* - Asynchronous TCP library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - #ifndef ASYNCTCP_H_ #define ASYNCTCP_H_ #include "Arduino.h" #include -//If core is not defined, then we are running in Arduino or PIO -#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE -#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core -#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event -#endif - class AsyncClient; -#define ASYNC_MAX_ACK_TIME 5000 -#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) -#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. - -typedef std::function AcConnectHandler; -typedef std::function AcAckHandler; -typedef std::function AcErrorHandler; -typedef std::function AcDataHandler; -typedef std::function AcPacketHandler; -typedef std::function AcTimeoutHandler; - struct tcp_pcb; struct ip_addr; class AsyncClient { public: - AsyncClient(tcp_pcb* pcb = 0); + AsyncClient(tcp_pcb * pcb = 0); ~AsyncClient(); - - AsyncClient & operator=(const AsyncClient &other); - AsyncClient & operator+=(const AsyncClient &other); - - bool operator==(const AsyncClient &other); - - bool operator!=(const AsyncClient &other) { - return !(*this == other); - } - bool connect(IPAddress ip, uint16_t port); - bool connect(const char* host, uint16_t port); - void close(bool now = false); - void stop(); - int8_t abort(); - bool free(); - - bool canSend();//ack is not pending - size_t space();//space available in the TCP window - size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending - bool send();//send all data added with the method above - - //write equals add()+send() - size_t write(const char* data); - size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true - - uint8_t state(); - bool connecting(); - bool connected(); - bool disconnecting(); - bool disconnected(); - bool freeable();//disconnected or disconnecting - - uint16_t getMss(); - - uint32_t getRxTimeout(); - void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds - - uint32_t getAckTimeout(); - void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds - - void setNoDelay(bool nodelay); - bool getNoDelay(); - - uint32_t getRemoteAddress(); - uint16_t getRemotePort(); - uint32_t getLocalAddress(); - uint16_t getLocalPort(); - - //compatibility - IPAddress remoteIP(); - uint16_t remotePort(); - IPAddress localIP(); - uint16_t localPort(); - - void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect - void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected - void onAck(AcAckHandler cb, void* arg = 0); //ack received - void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error - void onData(AcDataHandler cb, void* arg = 0); //data received (called if onPacket is not used) - void onPacket(AcPacketHandler cb, void* arg = 0); //data received - void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout - void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected - - void ackPacket(struct pbuf * pb);//ack pbuf from onPacket - size_t ack(size_t len); //ack data that you have not acked using the method below - void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData - - const char * errorToString(int8_t error); - const char * stateToString(); - - //Do not use any of the functions below! - static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb); - static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err); - static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); - static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); - static void _s_error(void *arg, int8_t err); - static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len); - static int8_t _s_connected(void* arg, void* tpcb, int8_t err); - static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg); - - int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err); - tcp_pcb * pcb(){ return _pcb; } - - protected: - tcp_pcb* _pcb; - int8_t _closed_slot; - - AcConnectHandler _connect_cb; - void* _connect_cb_arg; - AcConnectHandler _discard_cb; - void* _discard_cb_arg; - AcAckHandler _sent_cb; - void* _sent_cb_arg; - AcErrorHandler _error_cb; - void* _error_cb_arg; - AcDataHandler _recv_cb; - void* _recv_cb_arg; - AcPacketHandler _pb_cb; - void* _pb_cb_arg; - AcTimeoutHandler _timeout_cb; - void* _timeout_cb_arg; - AcConnectHandler _poll_cb; - void* _poll_cb_arg; - - bool _pcb_busy; - uint32_t _pcb_sent_at; - bool _ack_pcb; - uint32_t _rx_ack_len; - uint32_t _rx_last_packet; - uint32_t _rx_since_timeout; - uint32_t _ack_timeout; - uint16_t _connect_port; - - int8_t _close(); - int8_t _connected(void* pcb, int8_t err); - void _error(int8_t err); - int8_t _poll(tcp_pcb* pcb); - int8_t _sent(tcp_pcb* pcb, uint16_t len); - int8_t _fin(tcp_pcb* pcb, int8_t err); - int8_t _lwip_fin(tcp_pcb* pcb, int8_t err); - void _dns_found(struct ip_addr *ipaddr); - - public: - AsyncClient* prev; - AsyncClient* next; }; class AsyncServer { public: - AsyncServer(IPAddress addr, uint16_t port) : _port(port), _addr("poep") {}; - - AsyncServer(uint16_t port) : _port(port) {}; - - ~AsyncServer() {}; - void onClient(AcConnectHandler cb, void* arg); - void begin(); - void end(); - void setNoDelay(bool nodelay); - bool getNoDelay(); - uint8_t status(); - - //Do not use any of the functions below! - static int8_t _s_accept(void *arg, tcp_pcb* newpcb, int8_t err); - static int8_t _s_accepted(void *arg, AsyncClient* client); + AsyncServer(uint16_t port) + : _port(port){}; + ~AsyncServer(){}; protected: uint16_t _port; - IPAddress _addr; - bool _noDelay; - tcp_pcb* _pcb; - AcConnectHandler _connect_cb; - void* _connect_cb_arg; - - int8_t _accept(tcp_pcb* newpcb, int8_t err); - int8_t _accepted(AsyncClient* client); }; -#endif /* ASYNCTCP_H_ */ +#endif diff --git a/lib_standalone/ESP8266React.h b/lib_standalone/ESP8266React.h index 7c40f4ea5..6621670c2 100644 --- a/lib_standalone/ESP8266React.h +++ b/lib_standalone/ESP8266React.h @@ -4,32 +4,31 @@ #include #include #include -#include +#include #include #include -#include +#include +#include #include class DummySettings { public: - uint8_t tx_mode; - uint8_t ems_bus_id; - bool system_heartbeat; - int8_t syslog_level; // uuid::log::Level - uint32_t syslog_mark_interval; - String syslog_host; - uint8_t master_thermostat; - bool shower_timer; - bool shower_alert; - - uint16_t publish_time; // seconds - uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom - uint8_t mqtt_qos; - - String hostname; - String jwtSecret; - String ssid; - String password; + uint8_t tx_mode = 1; + uint8_t ems_bus_id = 0x0B; + bool system_heartbeat = false; + int8_t syslog_level = 1; // uuid::log::Level + uint32_t syslog_mark_interval = 0; + String syslog_host = "192.168.1.4"; + uint8_t master_thermostat = 0; + bool shower_timer = false; + bool shower_alert = false; + uint16_t publish_time = 10; // seconds + uint8_t mqtt_format = 1; // 1=single, 2=nested, 3=ha, 4=custom + uint8_t mqtt_qos = 0; + String hostname = "ems-esp"; + String jwtSecret = "ems-esp"; + String ssid = "ems-esp"; + String password = "ems-esp"; static void read(DummySettings & settings, JsonObject & root){}; static void read(DummySettings & settings){}; @@ -56,13 +55,14 @@ class DummySettingsService : public StatefulService { class ESP8266React { public: ESP8266React(AsyncWebServer * server, FS * fs) - : _settings(server, fs, nullptr){}; + : _settings(server, fs, nullptr) + , _securitySettingsService(server, fs){}; void begin(){}; void loop(){}; SecurityManager * getSecurityManager() { - return nullptr; + return &_securitySettingsService; } AsyncMqttClient * getMqttClient() { @@ -82,8 +82,10 @@ class ESP8266React { } private: - DummySettingsService _settings; - AsyncMqttClient * _mqttClient; + DummySettingsService _settings; + SecuritySettingsService _securitySettingsService; + + AsyncMqttClient * _mqttClient; }; class EMSESPSettingsService { @@ -93,8 +95,6 @@ class EMSESPSettingsService { void begin(); private: - // HttpEndpoint _httpEndpoint; - // FSPersistence _fsPersistence; }; #endif diff --git a/lib_standalone/ESPAsyncWebServer.h b/lib_standalone/ESPAsyncWebServer.h index 5a0ce867b..7b4a188e2 100644 --- a/lib_standalone/ESPAsyncWebServer.h +++ b/lib_standalone/ESPAsyncWebServer.h @@ -7,22 +7,11 @@ #include #include -#define DEBUGF(...) //Serial.printf(__VA_ARGS__) - class AsyncWebServer; class AsyncWebServerRequest; class AsyncWebServerResponse; -class AsyncWebHeader; -class AsyncWebParameter; -class AsyncWebRewrite; -class AsyncWebHandler; -class AsyncStaticWebHandler; -class AsyncCallbackWebHandler; -class AsyncResponseStream; class AsyncJsonResponse; - - typedef enum { HTTP_GET = 0b00000001, HTTP_POST = 0b00000010, @@ -34,151 +23,17 @@ typedef enum { HTTP_ANY = 0b01111111, } WebRequestMethod; -//if this value is returned when asked for data, packet will not be sent and you will be asked for data again -#define RESPONSE_TRY_AGAIN 0xFFFFFFFF - typedef uint8_t WebRequestMethodComposite; typedef std::function ArDisconnectHandler; -/* - * PARAMETER :: Chainable object to hold GET/POST and FILE parameters - * */ - -class AsyncWebParameter { - private: - String _name; - String _value; - size_t _size; - bool _isForm; - bool _isFile; - - public: - AsyncWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0) - : _name(name) - , _value(value) - , _size(size) - , _isForm(form) - , _isFile(file) { - } - const String & name() const { - return _name; - } - const String & value() const { - return _value; - } - size_t size() const { - return _size; - } - bool isPost() const { - return _isForm; - } - bool isFile() const { - return _isFile; - } -}; - -/* - * HEADER :: Chainable object to hold the headers - * */ - -class AsyncWebHeader { - private: - String _name; - String _value; - - public: - AsyncWebHeader(const String & name, const String & value) - : _name(name) - , _value(value) { - } - AsyncWebHeader(const String & data) - : _name() - , _value() { - } - ~AsyncWebHeader() { - } - const String & name() const { - return _name; - } - const String & value() const { - return _value; - } - String toString() const { - return _value; - } -}; - -/* - * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect - * */ - -typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; - -typedef std::function AwsResponseFiller; -typedef std::function AwsTemplateProcessor; - class AsyncWebServerRequest { friend class AsyncWebServer; friend class AsyncCallbackWebHandler; private: - AsyncClient * _client; - AsyncWebServer * _server; - AsyncWebHandler * _handler; - AsyncWebServerResponse * _response; - ArDisconnectHandler _onDisconnectfn; - - String _temp; - uint8_t _parseState; - - uint8_t _version; + AsyncClient * _client; + AsyncWebServer * _server; WebRequestMethodComposite _method; - String _url; - String _host; - String _contentType; - String _boundary; - String _authorization; - RequestedConnectionType _reqconntype; - void _removeNotInterestingHeaders(); - bool _isDigest; - bool _isMultipart; - bool _isPlainPost; - bool _expectingContinue; - size_t _contentLength; - size_t _parsedLength; - - uint8_t _multiParseState; - uint8_t _boundaryPosition; - size_t _itemStartIndex; - size_t _itemSize; - String _itemName; - String _itemFilename; - String _itemType; - String _itemValue; - uint8_t * _itemBuffer; - size_t _itemBufferIndex; - bool _itemIsFile; - - void _onPoll(); - void _onAck(size_t len, uint32_t time); - void _onError(int8_t error); - void _onTimeout(uint32_t time); - void _onDisconnect(){}; - void _onData(void * buf, size_t len); - - void _addParam(AsyncWebParameter *); - void _addPathParam(const char * param); - - bool _parseReqHead(); - bool _parseReqHeader(); - void _parseLine(); - void _parsePlainPostChar(uint8_t data); - void _parseMultipartPostByte(uint8_t data, bool last); - void _addGetParams(const String & params); - - void _handleUploadStart(); - void _handleUploadByte(uint8_t data, bool last); - void _handleUploadEnd(); public: void * _tempObject; @@ -189,177 +44,39 @@ class AsyncWebServerRequest { AsyncClient * client() { return _client; } - uint8_t version() const { - return _version; - } + WebRequestMethodComposite method() const { return _method; } - const String & url() const { - return _url; - } - const String & host() const { - return _host; - } - const String & contentType() const { - return _contentType; - } - size_t contentLength() const { - return _contentLength; - } - bool multipart() const { - return _isMultipart; - } - const char * methodToString() const; - const char * requestedConnTypeToString() const; - RequestedConnectionType requestedConnType() const { - return _reqconntype; - } - bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); - void onDisconnect(ArDisconnectHandler fn){}; - //hash is the string representation of: - // base64(user:pass) for basic or - // user:realm:md5(user:realm:pass) for digest - bool authenticate(const char * hash); - bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); - void requestAuthentication(const char * realm = NULL, bool isDigest = true); - - void setHandler(AsyncWebHandler * handler) { - _handler = handler; - } - void addInterestingHeader(const String & name); - - void redirect(const String & url); + void addInterestingHeader(const String & name){}; void send(AsyncWebServerResponse * response){}; void send(AsyncJsonResponse * response){}; void send(int code, const String & contentType = String(), const String & content = String()){}; - void send(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback = nullptr); - void send(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - void sendChunked(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - void send_P(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr); - void send_P(int code, const String & contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); + AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()) { + // AsyncWebServerResponse *a = new AsyncWebServerResponse() + return nullptr; + } - AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()); - AsyncWebServerResponse * beginResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse * beginResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncWebServerResponse * beginChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncResponseStream * beginResponseStream(const String & contentType, size_t bufferSize = 1460); - AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); - - size_t headers() const; // get header count - bool hasHeader(const String & name) const; // check if header exists - bool hasHeader(const __FlashStringHelper * data) const; // check if header exists - - AsyncWebHeader * getHeader(const String & name) const; - AsyncWebHeader * getHeader(const __FlashStringHelper * data) const; - AsyncWebHeader * getHeader(size_t num) const; - - size_t params() const; // get arguments count - bool hasParam(const String & name, bool post = false, bool file = false) const; - bool hasParam(const __FlashStringHelper * data, bool post = false, bool file = false) const; - - AsyncWebParameter * getParam(const String & name, bool post = false, bool file = false) const; - AsyncWebParameter * getParam(const __FlashStringHelper * data, bool post, bool file) const; - AsyncWebParameter * getParam(size_t num) const; - - size_t args() const { - return params(); - } // get arguments count - const String & arg(const String & name) const; // get request argument value by name - const String & arg(const __FlashStringHelper * data) const; // get request argument value by F(name) - const String & arg(size_t i) const; // get request argument value by number - const String & argName(size_t i) const; // get request argument name by number - bool hasArg(const char * name) const; // check if argument exists - bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists - - const String & pathArg(size_t i) const; - - const String & header(const char * name) const; // get request header value by name - const String & header(const __FlashStringHelper * data) const; // get request header value by F(name) - const String & header(size_t i) const; // get request header value by number - const String & headerName(size_t i) const; // get request header name by number - String urlDecode(const String & text) const; + size_t headers() const; // get header count + size_t params() const; // get arguments count }; -/* - * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) - * */ - typedef std::function ArRequestFilterFunction; -bool ON_STA_FILTER(AsyncWebServerRequest * request); - -bool ON_AP_FILTER(AsyncWebServerRequest * request); - -/* - * REWRITE :: One instance can be handle any Request (done by the Server) - * */ - -class AsyncWebRewrite { - protected: - String _from; - String _toUrl; - String _params; - ArRequestFilterFunction _filter; - - public: - AsyncWebRewrite(const char * from, const char * to) - : _from(from) - , _toUrl(to) - , _params(String()) - , _filter(NULL) { - } - virtual ~AsyncWebRewrite() { - } - AsyncWebRewrite & setFilter(ArRequestFilterFunction fn) { - _filter = fn; - return *this; - } - bool filter(AsyncWebServerRequest * request) const { - return _filter == NULL || _filter(request); - } - const String & from(void) const { - return _from; - } - const String & toUrl(void) const { - return _toUrl; - } - const String & params(void) const { - return _params; - } -}; - -/* - * HANDLER :: One instance can be attached to any Request (done by the Server) - * */ - class AsyncWebHandler { protected: - ArRequestFilterFunction _filter; - String _username; - String _password; + String _username; + String _password; public: AsyncWebHandler() : _username("") , _password("") { } - AsyncWebHandler & setFilter(ArRequestFilterFunction fn) { - _filter = fn; - return *this; - } - AsyncWebHandler & setAuthentication(const char * username, const char * password) { - _username = String(username); - _password = String(password); - return *this; - }; - bool filter(AsyncWebServerRequest * request) { - return _filter == NULL || _filter(request); - } + virtual ~AsyncWebHandler() { } virtual bool canHandle(AsyncWebServerRequest * request __attribute__((unused))) { @@ -380,142 +97,41 @@ class AsyncWebHandler { size_t index __attribute__((unused)), size_t total __attribute__((unused))) { } + virtual bool isRequestHandlerTrivial() { return true; } }; -/* - * RESPONSE :: One instance is created for each Request (attached by the Handler) - * */ - -typedef enum { RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED } WebResponseState; - class AsyncWebServerResponse { - protected: - int _code; - String _contentType; - size_t _contentLength; - bool _sendContentLength; - bool _chunked; - size_t _headLength; - size_t _sentLength; - size_t _ackedLength; - size_t _writtenLength; - WebResponseState _state; - const char * _responseCodeToString(int code); - public: AsyncWebServerResponse(); virtual ~AsyncWebServerResponse(); - virtual void setCode(int code); - virtual void setContentLength(size_t len); - virtual void setContentType(const String & type); - virtual void addHeader(const String & name, const String & value); - virtual String _assembleHead(uint8_t version); - virtual bool _started() const; - virtual bool _finished() const; - virtual bool _failed() const; - virtual bool _sourceValid() const; - virtual void _respond(AsyncWebServerRequest * request); - virtual size_t _ack(AsyncWebServerRequest * request, size_t len, uint32_t time); }; -/* - * SERVER :: One instance - * */ - typedef std::function ArRequestHandlerFunction; typedef std::function ArUploadHandlerFunction; typedef std::function ArBodyHandlerFunction; class AsyncWebServer { protected: - AsyncServer _server; - AsyncCallbackWebHandler * _catchAllHandler; + AsyncServer _server; public: - // proddy AsyncWebServer(uint16_t port) : _server(port){}; - // , _rewrites(LinkedList([](AsyncWebRewrite* r){ delete r; })) - // , _handlers(LinkedList([](AsyncWebHandler* h){ delete h; })) - ~AsyncWebServer(){}; void begin(){}; void end(); -#if ASYNC_TCP_SSL_ENABLED - void onSslFileRequest(AcSSlFileHandler cb, void * arg); - void beginSecure(const char * cert, const char * private_key_file, const char * password); -#endif - - AsyncWebRewrite & addRewrite(AsyncWebRewrite * rewrite); - bool removeRewrite(AsyncWebRewrite * rewrite); - AsyncWebRewrite & rewrite(const char * from, const char * to); - - AsyncWebHandler & addHandler(AsyncWebHandler * handler); - bool removeHandler(AsyncWebHandler * handler); - - /* - - AsyncCallbackWebHandler & on(const char * uri, ArRequestHandlerFunction onRequest) { - AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler(); + AsyncWebHandler & addHandler(AsyncWebHandler * handler) { return *handler; - }; - AsyncCallbackWebHandler & on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) { - AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler(); - return handler; - }; - AsyncCallbackWebHandler & on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) { - AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler(); - return *handler; - }; - AsyncCallbackWebHandler & - on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) { - AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler(); - return *handler; - }; - */ + } void on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){}; - - void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned - void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads - void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) - - void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody - - void _handleDisconnect(AsyncWebServerRequest * request); - void _attachHandler(AsyncWebServerRequest * request); - void _rewriteRequest(AsyncWebServerRequest * request); }; -// class DefaultHeaders { -// headers_t _headers; -// DefaultHeaders() -// public: - -// void addHeader(const String& name, const String& value){ -// _headers.add(new AsyncWebHeader(name, value)); -// } - -// DefaultHeaders(DefaultHeaders const &) = delete; -// DefaultHeaders &operator=(DefaultHeaders const &) = delete; -// static DefaultHeaders &Instance() { -// static DefaultHeaders instance; -// return instance; -// } -// }; - -// #include "WebResponseImpl.h" -// #include "WebHandlerImpl.h" -// #include "AsyncWebSocket.h" -// #include "AsyncEventSource.h" - -typedef std::function ArJsonRequestHandlerFunction; - -#endif /* _AsyncWebServer_H_ */ +#endif diff --git a/lib_standalone/FSPersistence.h b/lib_standalone/FSPersistence.h index 754a90cda..e6c43a69f 100644 --- a/lib_standalone/FSPersistence.h +++ b/lib_standalone/FSPersistence.h @@ -24,46 +24,13 @@ class FSPersistence { } void readFromFS() { - /* - File settingsFile = _fs->open(_filePath, "r"); - - if (settingsFile) { - DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); - DeserializationError error = deserializeJson(jsonDocument, settingsFile); - if (error == DeserializationError::Ok && jsonDocument.is()) { - JsonObject jsonObject = jsonDocument.as(); - _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); - settingsFile.close(); - return; - } - settingsFile.close(); - } - */ - // If we reach here we have not been successful in loading the config, - // hard-coded emergency defaults are now applied. - applyDefaults(); } bool writeToFS() { - // create and populate a new json object DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); JsonObject jsonObject = jsonDocument.to(); _statefulService->read(jsonObject, _stateReader); - - // serialize it to filesystem - /* - File settingsFile = _fs->open(_filePath, "w"); - - // failed to open file, return false - if (!settingsFile) { - return false; - } - - // serialize the data to the file - serializeJson(jsonDocument, settingsFile); - settingsFile.close(); - */ return true; } @@ -90,8 +57,6 @@ class FSPersistence { update_handler_id_t _updateHandlerId; protected: - // We assume the updater supplies sensible defaults if an empty object - // is supplied, this virtual function allows that to be changed. virtual void applyDefaults() { DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); JsonObject jsonObject = jsonDocument.as(); diff --git a/lib_standalone/HttpEndpoint.h b/lib_standalone/HttpEndpoint.h index c5ce9fefe..ae3d7c89c 100644 --- a/lib_standalone/HttpEndpoint.h +++ b/lib_standalone/HttpEndpoint.h @@ -22,12 +22,6 @@ class HttpGetEndpoint { AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) { - /* - server->on(servicePath.c_str(), - HTTP_GET, - securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1), - authenticationPredicate)); - */ } HttpGetEndpoint(JsonStateReader stateReader, @@ -36,9 +30,6 @@ class HttpGetEndpoint { const String& servicePath, size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) { - /* - server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1)); - */ } protected: @@ -47,12 +38,6 @@ class HttpGetEndpoint { size_t _bufferSize; void fetchSettings(AsyncWebServerRequest* request) { - // AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize); - // JsonObject jsonObject = response->getRoot().to(); - // _statefulService->read(jsonObject, _stateReader); - - // response->setLength(); - // request->send(response); } }; @@ -70,17 +55,7 @@ class HttpPostEndpoint { _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService), - /* - _updateHandler( - servicePath, - securityManager->wrapCallback( - std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2), - authenticationPredicate), - bufferSize), - */ _bufferSize(bufferSize) { - //_updateHandler.setMethod(HTTP_POST); - // server->addHandler(&_updateHandler); } HttpPostEndpoint(JsonStateReader stateReader, @@ -92,42 +67,26 @@ class HttpPostEndpoint { _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService), - /* - _updateHandler(servicePath, - std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2), - bufferSize), - */ _bufferSize(bufferSize) { - // _updateHandler.setMethod(HTTP_POST); - // server->addHandler(&_updateHandler); } protected: JsonStateReader _stateReader; JsonStateUpdater _stateUpdater; StatefulService* _statefulService; - //AsyncCallbackJsonWebHandler _updateHandler; size_t _bufferSize; void updateSettings(AsyncWebServerRequest* request, JsonVariant& json) { if (!json.is()) { - // request->send(400); return; } JsonObject jsonObject = json.as(); StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); if (outcome == StateUpdateResult::ERROR) { - // request->send(400); return; } if (outcome == StateUpdateResult::CHANGED) { - // request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); }); } - // AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize); - // jsonObject = response->getRoot().to(); - // _statefulService->read(jsonObject, _stateReader); - // response->setLength(); - // request->send(response); } }; diff --git a/lib_standalone/SecurityManager.h b/lib_standalone/SecurityManager.h index 1f1258e09..327206351 100644 --- a/lib_standalone/SecurityManager.h +++ b/lib_standalone/SecurityManager.h @@ -4,7 +4,6 @@ #include #include #include -// #include #include #include @@ -21,82 +20,64 @@ #define MAX_JWT_SIZE 128 class User { - public: - String username; - String password; - bool admin; + public: + String username; + String password; + bool admin; - public: - User(String username, String password, bool admin) : username(username), password(password), admin(admin) { - } + public: + User(String username, String password, bool admin) + : username(username) + , password(password) + , admin(admin) { + } }; class Authentication { - public: - User* user; - boolean authenticated; + public: + User * user; + boolean authenticated; - public: - Authentication(User& user) : user(new User(user)), authenticated(true) { - } - Authentication() : user(nullptr), authenticated(false) { - } - ~Authentication() { - delete (user); - } + public: + Authentication(User & user) + : user(new User(user)) + , authenticated(true) { + } + Authentication() + : user(nullptr) + , authenticated(false) { + } + ~Authentication() { + delete (user); + } }; -typedef std::function AuthenticationPredicate; +typedef std::function AuthenticationPredicate; class AuthenticationPredicates { - public: - static bool NONE_REQUIRED(Authentication& authentication) { - return true; - }; - static bool IS_AUTHENTICATED(Authentication& authentication) { - return authentication.authenticated; - }; - static bool IS_ADMIN(Authentication& authentication) { - return authentication.authenticated && authentication.user->admin; - }; + public: + static bool NONE_REQUIRED(Authentication & authentication) { + return true; + }; + static bool IS_AUTHENTICATED(Authentication & authentication) { + return authentication.authenticated; + }; + static bool IS_ADMIN(Authentication & authentication) { + return authentication.authenticated && authentication.user->admin; + }; }; class SecurityManager { - public: + public: #if FT_ENABLED(FT_SECURITY) - /* - * Authenticate, returning the user if found - */ - virtual Authentication authenticate(const String& username, const String& password) = 0; - - /* - * Generate a JWT for the user provided - */ - virtual String generateJWT(User* user) = 0; - + virtual Authentication authenticate(const String & username, const String & password) = 0; + virtual String generateJWT(User * user) = 0; #endif - /* - * Check the request header for the Authorization token - */ - virtual Authentication authenticateRequest(AsyncWebServerRequest* request) = 0; - - /** - * Filter a request with the provided predicate, only returning true if the predicate matches. - */ - virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0; - - /** - * Wrap the provided request to provide validation against an AuthenticationPredicate. - */ - virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, - AuthenticationPredicate predicate) = 0; - - /** - * Wrap the provided json request callback to provide validation against an AuthenticationPredicate. - */ - virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, - AuthenticationPredicate predicate) = 0; + virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0; + virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0; + virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0; + virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0; }; -#endif // end SecurityManager_h +#endif // end SecurityManager_h diff --git a/lib_standalone/SecuritySettingsService.cpp b/lib_standalone/SecuritySettingsService.cpp new file mode 100644 index 000000000..08ba401d8 --- /dev/null +++ b/lib_standalone/SecuritySettingsService.cpp @@ -0,0 +1,141 @@ +#include + +#if FT_ENABLED(FT_SECURITY) + +SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : + _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this), + _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE), + _jwtHandler(FACTORY_JWT_SECRET) { + addUpdateHandler([&](const String& originId) { configureJWTHandler(); }, false); +} + +void SecuritySettingsService::begin() { + _fsPersistence.readFromFS(); + configureJWTHandler(); +} + +Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) { + AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER); + if (authorizationHeader) { + String value = authorizationHeader->value(); + if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) { + value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN); + return authenticateJWT(value); + } + } else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) { + AsyncWebParameter* tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER); + String value = tokenParamater->value(); + return authenticateJWT(value); + } + return Authentication(); +} + +void SecuritySettingsService::configureJWTHandler() { + _jwtHandler.setSecret(_state.jwtSecret); +} + +Authentication SecuritySettingsService::authenticateJWT(String& jwt) { + DynamicJsonDocument payloadDocument(MAX_JWT_SIZE); + _jwtHandler.parseJWT(jwt, payloadDocument); + if (payloadDocument.is()) { + JsonObject parsedPayload = payloadDocument.as(); + String username = parsedPayload["username"]; + for (User _user : _state.users) { + if (_user.username == username && validatePayload(parsedPayload, &_user)) { + return Authentication(_user); + } + } + } + return Authentication(); +} + +Authentication SecuritySettingsService::authenticate(const String& username, const String& password) { + for (User _user : _state.users) { + if (_user.username == username && _user.password == password) { + return Authentication(_user); + } + } + return Authentication(); +} + +inline void populateJWTPayload(JsonObject& payload, User* user) { + payload["username"] = user->username; + payload["admin"] = user->admin; + payload["version"] = EMSESP_APP_VERSION; // proddy added +} + +boolean SecuritySettingsService::validatePayload(JsonObject& parsedPayload, User* user) { + DynamicJsonDocument jsonDocument(MAX_JWT_SIZE); + JsonObject payload = jsonDocument.to(); + populateJWTPayload(payload, user); + return payload == parsedPayload; +} + +String SecuritySettingsService::generateJWT(User* user) { + DynamicJsonDocument jsonDocument(MAX_JWT_SIZE); + JsonObject payload = jsonDocument.to(); + populateJWTPayload(payload, user); + return _jwtHandler.buildJWT(payload); +} + +ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { + return [this, predicate](AsyncWebServerRequest* request) { + Authentication authentication = authenticateRequest(request); + return predicate(authentication); + }; +} + +ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, + AuthenticationPredicate predicate) { + return [this, onRequest, predicate](AsyncWebServerRequest* request) { + Authentication authentication = authenticateRequest(request); + if (!predicate(authentication)) { + request->send(401); + return; + } + onRequest(request); + }; +} + +ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, + AuthenticationPredicate predicate) { + return [this, onRequest, predicate](AsyncWebServerRequest* request, JsonVariant& json) { + Authentication authentication = authenticateRequest(request); + if (!predicate(authentication)) { + request->send(401); + return; + } + onRequest(request, json); + }; +} + +#else + +User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true); + +SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : SecurityManager() { +} +SecuritySettingsService::~SecuritySettingsService() { +} + +ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { + return [this, predicate](AsyncWebServerRequest* request) { return true; }; +} + +// Return the admin user on all request - disabling security features +Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) { + return Authentication(ADMIN_USER); +} + +// Return the function unwrapped +ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, + AuthenticationPredicate predicate) { + return onRequest; +} + +ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, + AuthenticationPredicate predicate) { + return onRequest; +} + +#endif diff --git a/lib_standalone/SecuritySettingsService.h b/lib_standalone/SecuritySettingsService.h new file mode 100644 index 000000000..0034d08a4 --- /dev/null +++ b/lib_standalone/SecuritySettingsService.h @@ -0,0 +1,108 @@ +#ifndef SecuritySettingsService_h +#define SecuritySettingsService_h + +#include +#include +#include +#include + +#include "../../src/version.h" // added by proddy + +#ifndef FACTORY_ADMIN_USERNAME +#define FACTORY_ADMIN_USERNAME "admin" +#endif + +#ifndef FACTORY_ADMIN_PASSWORD +#define FACTORY_ADMIN_PASSWORD "admin" +#endif + +#ifndef FACTORY_GUEST_USERNAME +#define FACTORY_GUEST_USERNAME "guest" +#endif + +#ifndef FACTORY_GUEST_PASSWORD +#define FACTORY_GUEST_PASSWORD "guest" +#endif + +#define SECURITY_SETTINGS_FILE "/config/securitySettings.json" +#define SECURITY_SETTINGS_PATH "/rest/securitySettings" + +#if FT_ENABLED(FT_SECURITY) + +class SecuritySettings { + public: + String jwtSecret; + std::list users; + + static void read(SecuritySettings & settings, JsonObject & root) { + // secret + root["jwt_secret"] = settings.jwtSecret; + + // users + JsonArray users = root.createNestedArray("users"); + for (User user : settings.users) { + JsonObject userRoot = users.createNestedObject(); + userRoot["username"] = user.username; + userRoot["password"] = user.password; + userRoot["admin"] = user.admin; + } + } + + static StateUpdateResult update(JsonObject & root, SecuritySettings & settings) { + // secret + settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET; + + // users + settings.users.clear(); + if (root["users"].is()) { + for (JsonVariant user : root["users"].as()) { + settings.users.push_back(User(user["username"], user["password"], user["admin"])); + } + } else { + settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true)); + settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false)); + } + return StateUpdateResult::CHANGED; + } +}; + +class SecuritySettingsService : public StatefulService, public SecurityManager { + public: + SecuritySettingsService(AsyncWebServer * server, FS * fs); + + void begin(); + + // Functions to implement SecurityManager + Authentication authenticate(const String & username, const String & password); + Authentication authenticateRequest(AsyncWebServerRequest * request); + String generateJWT(User * user); + ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate); + ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); + ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate); + + private: + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; + ArduinoJsonJWT _jwtHandler; + + void configureJWTHandler(); + Authentication authenticateJWT(String & jwt); + boolean validatePayload(JsonObject & parsedPayload, User * user); +}; + +#else + +class SecuritySettingsService : public SecurityManager { + public: + SecuritySettingsService(AsyncWebServer * server, FS * fs); + ~SecuritySettingsService(); + + // minimal set of functions to support framework with security settings disabled + Authentication authenticateRequest(AsyncWebServerRequest * request); + ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate); + ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); + ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate); +}; + +#endif // end FT_ENABLED(FT_SECURITY) +#endif // end SecuritySettingsService_h diff --git a/lib_standalone/StatefulService.h b/lib_standalone/StatefulService.h index c6c38e988..41db7d3d5 100644 --- a/lib_standalone/StatefulService.h +++ b/lib_standalone/StatefulService.h @@ -12,133 +12,137 @@ #endif enum class StateUpdateResult { - CHANGED = 0, // The update changed the state and propagation should take place if required - UNCHANGED, // The state was unchanged, propagation should not take place - ERROR // There was a problem updating the state, propagation should not take place + CHANGED = 0, // The update changed the state and propagation should take place if required + UNCHANGED, // The state was unchanged, propagation should not take place + ERROR // There was a problem updating the state, propagation should not take place }; template -using JsonStateUpdater = std::function; +using JsonStateUpdater = std::function; template -using JsonStateReader = std::function; +using JsonStateReader = std::function; -typedef size_t update_handler_id_t; -typedef std::function StateUpdateCallback; +typedef size_t update_handler_id_t; +typedef std::function StateUpdateCallback; typedef struct StateUpdateHandlerInfo { - static update_handler_id_t currentUpdatedHandlerId; - update_handler_id_t _id; - StateUpdateCallback _cb; - bool _allowRemove; - StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove) : - _id(++currentUpdatedHandlerId), _cb(cb), _allowRemove(allowRemove){}; + static update_handler_id_t currentUpdatedHandlerId; + update_handler_id_t _id; + StateUpdateCallback _cb; + bool _allowRemove; + StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove) + : _id(++currentUpdatedHandlerId) + , _cb(cb) + , _allowRemove(allowRemove){}; } StateUpdateHandlerInfo_t; template class StatefulService { - public: - template + public: + template #ifdef ESP32 - StatefulService(Args&&... args) : - _state(std::forward(args)...), _accessMutex(xSemaphoreCreateRecursiveMutex()) { - } + StatefulService(Args &&... args) + : _state(std::forward(args)...) + , _accessMutex(xSemaphoreCreateRecursiveMutex()) { + } #else - StatefulService(Args&&... args) : _state(std::forward(args)...) { - } + StatefulService(Args &&... args) + : _state(std::forward(args)...) { + } #endif - update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) { - if (!cb) { - return 0; + update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) { + if (!cb) { + return 0; + } + StateUpdateHandlerInfo_t updateHandler(cb, allowRemove); + _updateHandlers.push_back(updateHandler); + return updateHandler._id; } - StateUpdateHandlerInfo_t updateHandler(cb, allowRemove); - _updateHandlers.push_back(updateHandler); - return updateHandler._id; - } - void removeUpdateHandler(update_handler_id_t id) { - for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) { - if ((*i)._allowRemove && (*i)._id == id) { - i = _updateHandlers.erase(i); - } else { - ++i; - } + void removeUpdateHandler(update_handler_id_t id) { + for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) { + if ((*i)._allowRemove && (*i)._id == id) { + i = _updateHandlers.erase(i); + } else { + ++i; + } + } } - } - StateUpdateResult update(std::function stateUpdater, const String& originId) { - beginTransaction(); - StateUpdateResult result = stateUpdater(_state); - endTransaction(); - if (result == StateUpdateResult::CHANGED) { - callUpdateHandlers(originId); + StateUpdateResult update(std::function stateUpdater, const String & originId) { + beginTransaction(); + StateUpdateResult result = stateUpdater(_state); + endTransaction(); + if (result == StateUpdateResult::CHANGED) { + callUpdateHandlers(originId); + } + return result; } - return result; - } - StateUpdateResult updateWithoutPropagation(std::function stateUpdater) { - beginTransaction(); - StateUpdateResult result = stateUpdater(_state); - endTransaction(); - return result; - } - - StateUpdateResult update(JsonObject& jsonObject, JsonStateUpdater stateUpdater, const String& originId) { - beginTransaction(); - StateUpdateResult result = stateUpdater(jsonObject, _state); - endTransaction(); - if (result == StateUpdateResult::CHANGED) { - callUpdateHandlers(originId); + StateUpdateResult updateWithoutPropagation(std::function stateUpdater) { + beginTransaction(); + StateUpdateResult result = stateUpdater(_state); + endTransaction(); + return result; } - return result; - } - StateUpdateResult updateWithoutPropagation(JsonObject& jsonObject, JsonStateUpdater stateUpdater) { - beginTransaction(); - StateUpdateResult result = stateUpdater(jsonObject, _state); - endTransaction(); - return result; - } - - void read(std::function stateReader) { - beginTransaction(); - stateReader(_state); - endTransaction(); - } - - void read(JsonObject& jsonObject, JsonStateReader stateReader) { - beginTransaction(); - stateReader(_state, jsonObject); - endTransaction(); - } - - void callUpdateHandlers(const String& originId) { - for (const StateUpdateHandlerInfo_t& updateHandler : _updateHandlers) { - updateHandler._cb(originId); + StateUpdateResult update(JsonObject & jsonObject, JsonStateUpdater stateUpdater, const String & originId) { + beginTransaction(); + StateUpdateResult result = stateUpdater(jsonObject, _state); + endTransaction(); + if (result == StateUpdateResult::CHANGED) { + callUpdateHandlers(originId); + } + return result; } - } - protected: - T _state; + StateUpdateResult updateWithoutPropagation(JsonObject & jsonObject, JsonStateUpdater stateUpdater) { + beginTransaction(); + StateUpdateResult result = stateUpdater(jsonObject, _state); + endTransaction(); + return result; + } - inline void beginTransaction() { + void read(std::function stateReader) { + beginTransaction(); + stateReader(_state); + endTransaction(); + } + + void read(JsonObject & jsonObject, JsonStateReader stateReader) { + beginTransaction(); + stateReader(_state, jsonObject); + endTransaction(); + } + + void callUpdateHandlers(const String & originId) { + for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) { + updateHandler._cb(originId); + } + } + + protected: + T _state; + + inline void beginTransaction() { #ifdef ESP32 - xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); + xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); #endif - } + } - inline void endTransaction() { + inline void endTransaction() { #ifdef ESP32 - xSemaphoreGiveRecursive(_accessMutex); + xSemaphoreGiveRecursive(_accessMutex); #endif - } + } - private: + private: #ifdef ESP32 - SemaphoreHandle_t _accessMutex; + SemaphoreHandle_t _accessMutex; #endif - std::list _updateHandlers; + std::list _updateHandlers; }; -#endif // end StatefulService_h +#endif // end StatefulService_h diff --git a/lib_standalone/WString.cpp b/lib_standalone/WString.cpp index 5fd02b086..ef3e571cc 100644 --- a/lib_standalone/WString.cpp +++ b/lib_standalone/WString.cpp @@ -1,863 +1,68 @@ -/* - WString.cpp - String library for Wiring & Arduino - ...mostly rewritten by Paul Stoffregen... - Copyright (c) 2009-10 Hernando Barragan. All rights reserved. - Copyright 2011, Paul Stoffregen, paul@pjrc.com - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ +#include #include "WString.h" -/*********************************************/ -/* Constructors */ -/*********************************************/ +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + * + * https://github.com/freebsd/freebsd/blob/master/sys/libkern/strlcpy.c + */ +size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize) { + const char * osrc = src; + size_t nleft = dsize; -char * itoa(signed short value, char * result, const unsigned int base) { - // check that the base if valid - if (base < 2 || base > 36) { - *result = '\0'; - return result; - } - - char * ptr = result, *ptr1 = result, tmp_char; - signed short tmp_value; - - do { - tmp_value = value; - value /= base; - *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)]; - } while (value); - - // Apply negative sign - if (tmp_value < 0) { - *ptr++ = '-'; - } - - *ptr-- = '\0'; - while (ptr1 < ptr) { - tmp_char = *ptr; - *ptr-- = *ptr1; - *ptr1++ = tmp_char; - } - - return result; -} - -char* ltoa(long value, char* result, int base) { - return itoa((int)value, result, base); -} - -char* ultoa(unsigned long value, char* result, int base) { - return itoa((unsigned int)value, result, base); -} - -char * dtostrf(double number, signed char width, unsigned char prec, char *s) { - bool negative = false; - - char* out = s; - - int fillme = width; // how many cells to fill for the integer part - if (prec > 0) { - fillme -= (prec+1); - } - - // Handle negative numbers - if (number < 0.0) { - negative = true; - fillme--; - number = -number; - } - - // Round correctly so that print(1.999, 2) prints as "2.00" - // I optimized out most of the divisions - double rounding = 2.0; - for (unsigned int i = 0; i < prec; ++i) - rounding *= 10.0; - rounding = 1.0 / rounding; - - number += rounding; - - // Figure out how big our number really is - double tenpow = 1.0; - int digitcount = 1; - double nextpow; - while (number >= (nextpow = (10.0 * tenpow))) { - tenpow = nextpow; - digitcount++; - } - - // minimal compensation for possible lack of precision (#7087 addition) - // number *= 1 + std::numeric_limits::epsilon(); - - number /= tenpow; - fillme -= digitcount; - - // Pad unused cells with spaces - while (fillme-- > 0) { - *out++ = ' '; - } - - // Handle negative sign - if (negative) *out++ = '-'; - - // Print the digits, and if necessary, the decimal point - digitcount += prec; - int8_t digit = 0; - while (digitcount-- > 0) { - digit = (int8_t)number; - if (digit > 9) digit = 9; // insurance - *out++ = (char)('0' | digit); - if ((digitcount == prec) && (prec > 0)) { - *out++ = '.'; + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; } - number -= digit; - number *= 10.0; } - // make sure the string is terminated - *out = 0; - return s; -} + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } -String::String(const char *cstr) -{ - init(); - if (cstr) copy(cstr, strlen(cstr)); -} - -String::String(const String &value) -{ - init(); - *this = value; -} - -String::String(const __FlashStringHelper *pstr) -{ - init(); - *this = pstr; -} - -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -String::String(String &&rval) -{ - init(); - move(rval); -} -String::String(StringSumHelper &&rval) -{ - init(); - move(rval); -} -#endif - -String::String(char c) -{ - init(); - char buf[2]; - buf[0] = c; - buf[1] = 0; - *this = buf; -} - -String::String(unsigned char value, unsigned char base) -{ - init(); - char buf[1 + 8 * sizeof(unsigned char)]; - itoa(value, buf, base); - *this = buf; -} - -String::String(int value, unsigned char base) -{ - init(); - char buf[2 + 8 * sizeof(int)]; - itoa(value, buf, base); - *this = buf; -} - -String::String(unsigned int value, unsigned char base) -{ - init(); - char buf[1 + 8 * sizeof(unsigned int)]; - itoa(value, buf, base); - *this = buf; -} - -String::String(long value, unsigned char base) -{ - init(); - char buf[2 + 8 * sizeof(long)]; - ltoa(value, buf, base); - *this = buf; -} - -String::String(unsigned long value, unsigned char base) -{ - init(); - char buf[1 + 8 * sizeof(unsigned long)]; - ultoa(value, buf, base); - *this = buf; -} - -String::String(float value, unsigned char decimalPlaces) -{ - init(); - char buf[33]; - *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); -} - -String::String(double value, unsigned char decimalPlaces) -{ - init(); - char buf[33]; - *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); -} - -String::~String() -{ - free(buffer); -} - -/*********************************************/ -/* Memory Management */ -/*********************************************/ - -inline void String::init(void) -{ - buffer = NULL; - capacity = 0; - len = 0; -} - -void String::invalidate(void) -{ - if (buffer) free(buffer); - buffer = NULL; - capacity = len = 0; -} - -unsigned char String::reserve(unsigned int size) -{ - if (buffer && capacity >= size) return 1; - if (changeBuffer(size)) { - if (len == 0) buffer[0] = 0; - return 1; - } - return 0; -} - -unsigned char String::changeBuffer(unsigned int maxStrLen) -{ - char *newbuffer = (char *)realloc(buffer, maxStrLen + 1); - if (newbuffer) { - buffer = newbuffer; - capacity = maxStrLen; - return 1; - } - return 0; -} - -/*********************************************/ -/* Copy and Move */ -/*********************************************/ - -String & String::copy(const char *cstr, unsigned int length) -{ - if (!reserve(length)) { - invalidate(); - return *this; - } - len = length; - strcpy(buffer, cstr); - return *this; + return (src - osrc - 1); /* count does not include NUL */ } /* -String & String::copy(const char *pstr, unsigned int length) -{ - if (!reserve(length)) { - invalidate(); - return *this; - } - len = length; - strcpy_P(buffer, pstr); - return *this; + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + * + * https://github.com/freebsd/freebsd/blob/master/sys/libkern/strlcat.c + */ +size_t strlcat(char * dst, const char * src, size_t siz) { + char * d = dst; + const char * s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return (dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return (dlen + (s - src)); /* count does not include NUL */ } -*/ - -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -void String::move(String &rhs) -{ - if (buffer) { - if (rhs && capacity >= rhs.len) { - strcpy(buffer, rhs.buffer); - len = rhs.len; - rhs.len = 0; - return; - } else { - free(buffer); - } - } - buffer = rhs.buffer; - capacity = rhs.capacity; - len = rhs.len; - rhs.buffer = NULL; - rhs.capacity = 0; - rhs.len = 0; -} -#endif - -String & String::operator = (const String &rhs) -{ - if (this == &rhs) return *this; - - if (rhs.buffer) copy(rhs.buffer, rhs.len); - else invalidate(); - - return *this; -} - -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -String & String::operator = (String &&rval) -{ - if (this != &rval) move(rval); - return *this; -} - -String & String::operator = (StringSumHelper &&rval) -{ - if (this != &rval) move(rval); - return *this; -} -#endif - -String & String::operator = (const char *cstr) -{ - if (cstr) copy(cstr, strlen(cstr)); - else invalidate(); - - return *this; -} - -String & String::operator = (const __FlashStringHelper *pstr) -{ - // if (pstr) copy(pstr, strlen_P((PGM_P)pstr)); - // else invalidate(); - - return *this; -} - -/*********************************************/ -/* concat */ -/*********************************************/ - -unsigned char String::concat(const String &s) -{ - return concat(s.buffer, s.len); -} - -unsigned char String::concat(const char *cstr, unsigned int length) -{ - unsigned int newlen = len + length; - if (!cstr) return 0; - if (length == 0) return 1; - if (!reserve(newlen)) return 0; - strcpy(buffer + len, cstr); - len = newlen; - return 1; -} - -unsigned char String::concat(const char *cstr) -{ - if (!cstr) return 0; - return concat(cstr, strlen(cstr)); -} - -unsigned char String::concat(char c) -{ - char buf[2]; - buf[0] = c; - buf[1] = 0; - return concat(buf, 1); -} - -unsigned char String::concat(unsigned char num) -{ - char buf[1 + 3 * sizeof(unsigned char)]; - itoa(num, buf, 10); - return concat(buf, strlen(buf)); -} - -unsigned char String::concat(int num) -{ - char buf[2 + 3 * sizeof(int)]; - itoa(num, buf, 10); - return concat(buf, strlen(buf)); -} - -unsigned char String::concat(unsigned int num) -{ - char buf[1 + 3 * sizeof(unsigned int)]; - itoa(num, buf, 10); - return concat(buf, strlen(buf)); -} - -unsigned char String::concat(long num) -{ - char buf[2 + 3 * sizeof(long)]; - ltoa(num, buf, 10); - return concat(buf, strlen(buf)); -} - -unsigned char String::concat(unsigned long num) -{ - char buf[1 + 3 * sizeof(unsigned long)]; - ultoa(num, buf, 10); - return concat(buf, strlen(buf)); -} - -unsigned char String::concat(float num) -{ - char buf[20]; - char* string = dtostrf(num, 4, 2, buf); - return concat(string, strlen(string)); -} - -unsigned char String::concat(double num) -{ - char buf[20]; - char* string = dtostrf(num, 4, 2, buf); - return concat(string, strlen(string)); -} - -unsigned char String::concat(const __FlashStringHelper * str) -{ - if (!str) return 0; - int length = strlen_P((const char *) str); - if (length == 0) return 1; - unsigned int newlen = len + length; - if (!reserve(newlen)) return 0; - strcpy_P(buffer + len, (const char *) str); - len = newlen; - return 1; -} - -/*********************************************/ -/* Concatenate */ -/*********************************************/ - -StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(rhs.buffer, rhs.len)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr) -{ - StringSumHelper &a = const_cast(lhs); - if (!cstr || !a.concat(cstr, strlen(cstr))) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, char c) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(c)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(num)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, int num) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(num)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(num)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, long num) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(num)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(num)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, float num) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(num)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, double num) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(num)) a.invalidate(); - return a; -} - -StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs) -{ - StringSumHelper &a = const_cast(lhs); - if (!a.concat(rhs)) a.invalidate(); - return a; -} - -/*********************************************/ -/* Comparison */ -/*********************************************/ - -int String::compareTo(const String &s) const -{ - if (!buffer || !s.buffer) { - if (s.buffer && s.len > 0) return 0 - *(unsigned char *)s.buffer; - if (buffer && len > 0) return *(unsigned char *)buffer; - return 0; - } - return strcmp(buffer, s.buffer); -} - -unsigned char String::isEmpty() const { - return (len == 0); -} - -unsigned char String::equals(const String &s2) const -{ - return (len == s2.len && compareTo(s2) == 0); -} - -unsigned char String::equals(const char *cstr) const -{ - if (len == 0) return (cstr == NULL || *cstr == 0); - if (cstr == NULL) return buffer[0] == 0; - return strcmp(buffer, cstr) == 0; -} - -unsigned char String::operator<(const String &rhs) const -{ - return compareTo(rhs) < 0; -} - -unsigned char String::operator>(const String &rhs) const -{ - return compareTo(rhs) > 0; -} - -unsigned char String::operator<=(const String &rhs) const -{ - return compareTo(rhs) <= 0; -} - -unsigned char String::operator>=(const String &rhs) const -{ - return compareTo(rhs) >= 0; -} - -unsigned char String::equalsIgnoreCase( const String &s2 ) const -{ - if (this == &s2) return 1; - if (len != s2.len) return 0; - if (len == 0) return 1; - const char *p1 = buffer; - const char *p2 = s2.buffer; - while (*p1) { - if (tolower(*p1++) != tolower(*p2++)) return 0; - } - return 1; -} - -unsigned char String::startsWith( const String &s2 ) const -{ - if (len < s2.len) return 0; - return startsWith(s2, 0); -} - -unsigned char String::startsWith( const String &s2, unsigned int offset ) const -{ - if (offset > len - s2.len || !buffer || !s2.buffer) return 0; - return strncmp( &buffer[offset], s2.buffer, s2.len ) == 0; -} - -unsigned char String::endsWith( const String &s2 ) const -{ - if ( len < s2.len || !buffer || !s2.buffer) return 0; - return strcmp(&buffer[len - s2.len], s2.buffer) == 0; -} - -/*********************************************/ -/* Character Access */ -/*********************************************/ - -char String::charAt(unsigned int loc) const -{ - return operator[](loc); -} - -void String::setCharAt(unsigned int loc, char c) -{ - if (loc < len) buffer[loc] = c; -} - -char & String::operator[](unsigned int index) -{ - static char dummy_writable_char; - if (index >= len || !buffer) { - dummy_writable_char = 0; - return dummy_writable_char; - } - return buffer[index]; -} - -char String::operator[]( unsigned int index ) const -{ - if (index >= len || !buffer) return 0; - return buffer[index]; -} - -void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const -{ - if (!bufsize || !buf) return; - if (index >= len) { - buf[0] = 0; - return; - } - unsigned int n = bufsize - 1; - if (n > len - index) n = len - index; - strncpy((char *)buf, buffer + index, n); - buf[n] = 0; -} - -/*********************************************/ -/* Search */ -/*********************************************/ - -int String::indexOf(char c) const -{ - return indexOf(c, 0); -} - -int String::indexOf( char ch, unsigned int fromIndex ) const -{ - if (fromIndex >= len) return -1; - const char* temp = strchr(buffer + fromIndex, ch); - if (temp == NULL) return -1; - return temp - buffer; -} - -int String::indexOf(const String &s2) const -{ - return indexOf(s2, 0); -} - -int String::indexOf(const String &s2, unsigned int fromIndex) const -{ - if (fromIndex >= len) return -1; - const char *found = strstr(buffer + fromIndex, s2.buffer); - if (found == NULL) return -1; - return found - buffer; -} - -int String::lastIndexOf( char theChar ) const -{ - return lastIndexOf(theChar, len - 1); -} - -int String::lastIndexOf(char ch, unsigned int fromIndex) const -{ - if (fromIndex >= len) return -1; - char tempchar = buffer[fromIndex + 1]; - buffer[fromIndex + 1] = '\0'; - char* temp = strrchr( buffer, ch ); - buffer[fromIndex + 1] = tempchar; - if (temp == NULL) return -1; - return temp - buffer; -} - -int String::lastIndexOf(const String &s2) const -{ - return lastIndexOf(s2, len - s2.len); -} - -int String::lastIndexOf(const String &s2, unsigned int fromIndex) const -{ - if (s2.len == 0 || len == 0 || s2.len > len) return -1; - if (fromIndex >= len) fromIndex = len - 1; - int found = -1; - for (char *p = buffer; p <= buffer + fromIndex; p++) { - p = strstr(p, s2.buffer); - if (!p) break; - if ((unsigned int)(p - buffer) <= fromIndex) found = p - buffer; - } - return found; -} - -String String::substring(unsigned int left, unsigned int right) const -{ - if (left > right) { - unsigned int temp = right; - right = left; - left = temp; - } - String out; - if (left >= len) return out; - if (right > len) right = len; - char temp = buffer[right]; // save the replaced character - buffer[right] = '\0'; - out = buffer + left; // pointer arithmetic - buffer[right] = temp; //restore character - return out; -} - -/*********************************************/ -/* Modification */ -/*********************************************/ - -void String::replace(char find, char replace) -{ - if (!buffer) return; - for (char *p = buffer; *p; p++) { - if (*p == find) *p = replace; - } -} - -void String::replace(const String& find, const String& replace) -{ - if (len == 0 || find.len == 0) return; - int diff = replace.len - find.len; - char *readFrom = buffer; - char *foundAt; - if (diff == 0) { - while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { - memcpy(foundAt, replace.buffer, replace.len); - readFrom = foundAt + replace.len; - } - } else if (diff < 0) { - char *writeTo = buffer; - while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { - unsigned int n = foundAt - readFrom; - memcpy(writeTo, readFrom, n); - writeTo += n; - memcpy(writeTo, replace.buffer, replace.len); - writeTo += replace.len; - readFrom = foundAt + find.len; - len += diff; - } - strcpy(writeTo, readFrom); - } else { - unsigned int size = len; // compute size needed for result - while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { - readFrom = foundAt + find.len; - size += diff; - } - if (size == len) return; - if (size > capacity && !changeBuffer(size)) return; - int index = len - 1; - while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) { - readFrom = buffer + index + find.len; - memmove(readFrom + diff, readFrom, len - (readFrom - buffer)); - len += diff; - buffer[len] = 0; - memcpy(buffer + index, replace.buffer, replace.len); - index--; - } - } -} - -void String::remove(unsigned int index){ - // Pass the biggest integer as the count. The remove method - // below will take care of truncating it at the end of the - // string. - remove(index, (unsigned int)-1); -} - -void String::remove(unsigned int index, unsigned int count){ - if (index >= len) { return; } - if (count <= 0) { return; } - if (count > len - index) { count = len - index; } - char *writeTo = buffer + index; - len = len - count; - strncpy(writeTo, buffer + index + count,len - index); - buffer[len] = 0; -} - -void String::toLowerCase(void) -{ - if (!buffer) return; - for (char *p = buffer; *p; p++) { - *p = tolower(*p); - } -} - -void String::toUpperCase(void) -{ - if (!buffer) return; - for (char *p = buffer; *p; p++) { - *p = toupper(*p); - } -} - -void String::trim(void) -{ - if (!buffer || len == 0) return; - char *begin = buffer; - while (isspace(*begin)) begin++; - char *end = buffer + len - 1; - while (isspace(*end) && end >= begin) end--; - len = end + 1 - begin; - if (begin > buffer) memcpy(buffer, begin, len); - buffer[len] = 0; -} - -/*********************************************/ -/* Parsing / Conversion */ -/*********************************************/ - -long String::toInt(void) const -{ - if (buffer) return atol(buffer); - return 0; -} - -float String::toFloat(void) const -{ - return float(toDouble()); -} - -double String::toDouble(void) const -{ - if (buffer) return atof(buffer); - return 0; -} \ No newline at end of file diff --git a/lib_standalone/WString.h b/lib_standalone/WString.h index 1a7c9d223..c7b481bf2 100644 --- a/lib_standalone/WString.h +++ b/lib_standalone/WString.h @@ -1,236 +1,69 @@ -/* - WString.h - String library for Wiring & Arduino - ...mostly rewritten by Paul Stoffregen... - Copyright (c) 2009-10 Hernando Barragan. All right reserved. - Copyright 2011, Paul Stoffregen, paul@pjrc.com - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +#ifndef WSTRING_H +#define WSTRING_H - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. +#include - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ +// Reproduces Arduino's String class +class String { + public: + String & operator+=(const char * rhs) { + _str += rhs; + return *this; + } -#ifndef String_class_h -#define String_class_h + size_t length() const { + return _str.size(); + } + + String(const char * str = "") + : _str(str) { + } + + const char * c_str() const { + return _str.c_str(); + } + + bool operator==(const char * s) const { + return _str == s; + } + + friend std::ostream & operator<<(std::ostream & lhs, const ::String & rhs) { + lhs << rhs._str; + return lhs; + } + + /// + bool isEmpty() { + return _str.empty(); + } + + // long toInt() const { + // return std::stol(_str); + // } + + bool equals(const char * s) { + return _str == s; + } + + + private: + std::string _str; +}; + +class StringSumHelper; + +inline bool operator==(const std::string & lhs, const ::String & rhs) { + return lhs == rhs.c_str(); +} + + +size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize); +size_t strlcat(char * dst, const char * src, size_t siz); #define strlen_P strlen #define strncpy_P strncpy #define strcmp_P strcmp #define strcpy_P strcpy -// #include "pgmspace.h" -// #include "noniso.h" - -#include -#include -#include - -// When compiling programs with this class, the following gcc parameters -// dramatically increase performance and memory (RAM) efficiency, typically -// with little or no increase in code size. -// -felide-constructors -// -std=c++0x - -class __FlashStringHelper; -// #define F(string_literal) (reinterpret_cast(PSTR(string_literal))) - -// An inherited class for holding the result of a concatenation. These -// result objects are assumed to be writable by subsequent concatenations. -class StringSumHelper; - -// The string class -class String -{ - // use a function pointer to allow for "if (s)" without the - // complications of an operator bool(). for more information, see: - // http://www.artima.com/cppsource/safebool.html - typedef void (String::*StringIfHelperType)() const; - void StringIfHelper() const {} - -public: - // constructors - // creates a copy of the initial value. - // if the initial value is null or invalid, or if memory allocation - // fails, the string will be marked as invalid (i.e. "if (s)" will - // be false). - String(const char *cstr = ""); - String(const String &str); - String(const __FlashStringHelper *str); - #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) - String(String &&rval); - String(StringSumHelper &&rval); - #endif - explicit String(char c); - explicit String(unsigned char, unsigned char base=10); - explicit String(int, unsigned char base=10); - explicit String(unsigned int, unsigned char base=10); - explicit String(long, unsigned char base=10); - explicit String(unsigned long, unsigned char base=10); - explicit String(float, unsigned char decimalPlaces=2); - explicit String(double, unsigned char decimalPlaces=2); - ~String(void); - - // memory management - // return true on success, false on failure (in which case, the string - // is left unchanged). reserve(0), if successful, will validate an - // invalid string (i.e., "if (s)" will be true afterwards) - unsigned char reserve(unsigned int size); - inline unsigned int length(void) const {return len;} - - // creates a copy of the assigned value. if the value is null or - // invalid, or if the memory allocation fails, the string will be - // marked as invalid ("if (s)" will be false). - String & operator = (const String &rhs); - String & operator = (const char *cstr); - String & operator = (const __FlashStringHelper *str); - #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) - String & operator = (String &&rval); - String & operator = (StringSumHelper &&rval); - #endif - - // concatenate (works w/ built-in types) - - // returns true on success, false on failure (in which case, the string - // is left unchanged). if the argument is null or invalid, the - // concatenation is considered unsucessful. - unsigned char concat(const String &str); - unsigned char concat(const char *cstr); - unsigned char concat(char c); - unsigned char concat(unsigned char c); - unsigned char concat(int num); - unsigned char concat(unsigned int num); - unsigned char concat(long num); - unsigned char concat(unsigned long num); - unsigned char concat(float num); - unsigned char concat(double num); - unsigned char concat(const __FlashStringHelper * str); - - // if there's not enough memory for the concatenated value, the string - // will be left unchanged (but this isn't signalled in any way) - String & operator += (const String &rhs) {concat(rhs); return (*this);} - String & operator += (const char *cstr) {concat(cstr); return (*this);} - String & operator += (char c) {concat(c); return (*this);} - String & operator += (unsigned char num) {concat(num); return (*this);} - String & operator += (int num) {concat(num); return (*this);} - String & operator += (unsigned int num) {concat(num); return (*this);} - String & operator += (long num) {concat(num); return (*this);} - String & operator += (unsigned long num) {concat(num); return (*this);} - String & operator += (float num) {concat(num); return (*this);} - String & operator += (double num) {concat(num); return (*this);} - String & operator += (const __FlashStringHelper *str){concat(str); return (*this);} - - friend StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs); - friend StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr); - friend StringSumHelper & operator + (const StringSumHelper &lhs, char c); - friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num); - friend StringSumHelper & operator + (const StringSumHelper &lhs, int num); - friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num); - friend StringSumHelper & operator + (const StringSumHelper &lhs, long num); - friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num); - friend StringSumHelper & operator + (const StringSumHelper &lhs, float num); - friend StringSumHelper & operator + (const StringSumHelper &lhs, double num); - friend StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs); - - // comparison (only works w/ Strings and "strings") - operator StringIfHelperType() const { return buffer ? &String::StringIfHelper : 0; } - int compareTo(const String &s) const; - unsigned char equals(const String &s) const; - unsigned char equals(const char *cstr) const; - unsigned char operator == (const String &rhs) const {return equals(rhs);} - unsigned char operator == (const char *cstr) const {return equals(cstr);} - unsigned char operator != (const String &rhs) const {return !equals(rhs);} - unsigned char operator != (const char *cstr) const {return !equals(cstr);} - unsigned char operator < (const String &rhs) const; - unsigned char operator > (const String &rhs) const; - unsigned char operator <= (const String &rhs) const; - unsigned char operator >= (const String &rhs) const; - unsigned char equalsIgnoreCase(const String &s) const; - unsigned char startsWith( const String &prefix) const; - unsigned char startsWith(const String &prefix, unsigned int offset) const; - unsigned char endsWith(const String &suffix) const; - - // character acccess - char charAt(unsigned int index) const; - void setCharAt(unsigned int index, char c); - char operator [] (unsigned int index) const; - char& operator [] (unsigned int index); - void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index=0) const; - void toCharArray(char *buf, unsigned int bufsize, unsigned int index=0) const - { getBytes((unsigned char *)buf, bufsize, index); } - const char* c_str() const { return buffer; } - char* begin() { return buffer; } - char* end() { return buffer + length(); } - const char* begin() const { return c_str(); } - const char* end() const { return c_str() + length(); } - - // search - int indexOf( char ch ) const; - int indexOf( char ch, unsigned int fromIndex ) const; - int indexOf( const String &str ) const; - int indexOf( const String &str, unsigned int fromIndex ) const; - int lastIndexOf( char ch ) const; - int lastIndexOf( char ch, unsigned int fromIndex ) const; - int lastIndexOf( const String &str ) const; - int lastIndexOf( const String &str, unsigned int fromIndex ) const; - String substring( unsigned int beginIndex ) const { return substring(beginIndex, len); }; - String substring( unsigned int beginIndex, unsigned int endIndex ) const; - - // modification - void replace(char find, char replace); - void replace(const String& find, const String& replace); - void remove(unsigned int index); - void remove(unsigned int index, unsigned int count); - void toLowerCase(void); - void toUpperCase(void); - void trim(void); - - // parsing/conversion - long toInt(void) const; - float toFloat(void) const; - double toDouble(void) const; - - unsigned char isEmpty() const; - -protected: - char *buffer; // the actual char array - unsigned int capacity; // the array length minus one (for the '\0') - unsigned int len; // the String length (not counting the '\0') -protected: - void init(void); - void invalidate(void); - unsigned char changeBuffer(unsigned int maxStrLen); - unsigned char concat(const char *cstr, unsigned int length); - - // copy and move - String & copy(const char *cstr, unsigned int length); - String & copy(const __FlashStringHelper *pstr, unsigned int length); - #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) - void move(String &rhs); - #endif -}; - -class StringSumHelper : public String -{ -public: - StringSumHelper(const String &s) : String(s) {} - StringSumHelper(const char *p) : String(p) {} - StringSumHelper(char c) : String(c) {} - StringSumHelper(unsigned char num) : String(num) {} - StringSumHelper(int num) : String(num) {} - StringSumHelper(unsigned int num) : String(num) {} - StringSumHelper(long num) : String(num) {} - StringSumHelper(unsigned long num) : String(num) {} - StringSumHelper(float num) : String(num) {} - StringSumHelper(double num) : String(num) {} -}; - -#endif // String_class_h \ No newline at end of file +#endif diff --git a/makefile b/makefile index 9000ae4c9..06928a441 100644 --- a/makefile +++ b/makefile @@ -65,9 +65,9 @@ CPPFLAGS += -g3 CPPFLAGS += -Os CFLAGS += $(CPPFLAGS) -CFLAGS += -Wall -CFLAGS += -Wno-unused -Wno-restrict -CFLAGS += -Wextra +# CFLAGS += -Wall +# CFLAGS += -Wno-unused -Wno-restrict +# CFLAGS += -Wextra CXXFLAGS += $(CFLAGS) -MMD diff --git a/media/EMS-ESP_logo.png:Zone.Identifier b/media/EMS-ESP_logo.png:Zone.Identifier deleted file mode 100644 index 2dac2bc63..000000000 --- a/media/EMS-ESP_logo.png:Zone.Identifier +++ /dev/null @@ -1,3 +0,0 @@ -[ZoneTransfer] -LastWriterPackageFamilyName=Microsoft.MSPaint_8wekyb3d8bbwe -ZoneId=3 diff --git a/media/EMS-ESP_logo_dark.png:Zone.Identifier b/media/EMS-ESP_logo_dark.png:Zone.Identifier deleted file mode 100644 index 2dac2bc63..000000000 --- a/media/EMS-ESP_logo_dark.png:Zone.Identifier +++ /dev/null @@ -1,3 +0,0 @@ -[ZoneTransfer] -LastWriterPackageFamilyName=Microsoft.MSPaint_8wekyb3d8bbwe -ZoneId=3 diff --git a/media/web_devices.PNG b/media/web_devices.PNG index 684968967..4a9796a7b 100644 Binary files a/media/web_devices.PNG and b/media/web_devices.PNG differ diff --git a/platformio.ini b/platformio.ini index acfbf5622..9df1b635a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,13 +1,12 @@ ; PlatformIO Project Configuration File for EMS-ESP [platformio] -default_envs = esp8266 -; default_envs = esp32 +; default_envs = esp8266 +default_envs = esp32 # override any settings with your own local ones in pio_local.ini extra_configs = factory_settings.ini - features.ini pio_local.ini [common] @@ -20,7 +19,11 @@ debug_flags = ; default platformio compile flags are: -fno-rtti -std=c++11 -Os -mlongcalls -mtext-section-literals -falign-functions=4 -ffunction-sections -fdata-sections -fno-exceptions -Wall build_flags = ${factory_settings.build_flags} - ${features.build_flags} + -D FT_PROJECT=1 + -D FT_SECURITY=1 + -D FT_MQTT=1 + -D FT_OTA=1 + -D FT_UPLOAD_FIRMWARE=1 -D ONEWIRE_CRC16=0 -D NO_GLOBAL_ARDUINOOTA -D ARDUINOJSON_ENABLE_STD_STRING=1 @@ -53,8 +56,8 @@ check_flags = ; USB upload ; upload_protocol = esptool -; upload_port = COM6 -; upload_port = /dev/cu.wchusbserial1420 +; upload_port = COM4 +; upload_port = /dev/cu.wchusbserial1410 ; OTA upload upload_protocol = espota @@ -73,14 +76,15 @@ board_build.f_cpu = 160000000L ; 160MHz ; eagle.flash.4m1m.ld = 1019 KB sketch, 1000 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 2052 KB OTA & buffer ; eagle.flash.4m2m.ld = 1019 KB sketch, 2024 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 1028 KB OTA & buffer ; board_build.ldscript = eagle.flash.4m2m.ld -build_flags = ${common.build_flags} ${common.debug_flags} +build_flags = ${common.build_flags} ${common.debug_flags} -DFT_NTP=0 lib_ignore = AsyncTCP [env:esp32] board = esp32dev build_type = release -platform = espressif32 +; platform = espressif32 +platform = https://github.com/platformio/platform-espressif32.git board_build.partitions = min_spiffs.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/ lib_deps = ${common.libs_core} -build_flags = ${common.build_flags} ${common.debug_flags} +build_flags = ${common.build_flags} ${common.debug_flags} -DFT_NTP=1 diff --git a/src/EMSESPDevicesService.cpp b/src/EMSESPDevicesService.cpp index 1ce990286..8fccbdc32 100644 --- a/src/EMSESPDevicesService.cpp +++ b/src/EMSESPDevicesService.cpp @@ -1,35 +1,93 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "EMSESPDevicesService.h" #include "emsesp.h" #include "mqtt.h" namespace emsesp { -EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager) { +EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager) + : _device_dataHandler(DEVICE_DATA_SERVICE_PATH, + securityManager->wrapCallback(std::bind(&EMSESPDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) { server->on(EMSESP_DEVICES_SERVICE_PATH, HTTP_GET, - securityManager->wrapRequest(std::bind(&EMSESPDevicesService::emsespDevicesService, this, std::placeholders::_1), - AuthenticationPredicates::IS_AUTHENTICATED)); + securityManager->wrapRequest(std::bind(&EMSESPDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + + server->on(SCAN_DEVICES_SERVICE_PATH, + HTTP_GET, + securityManager->wrapRequest(std::bind(&EMSESPDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + + _device_dataHandler.setMethod(HTTP_POST); + _device_dataHandler.setMaxContentLength(256); + server->addHandler(&_device_dataHandler); } -void EMSESPDevicesService::emsespDevicesService(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_STATUS_SIZE); +void EMSESPDevicesService::scan_devices(AsyncWebServerRequest * request) { + EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER); + request->send(200); +} + +void EMSESPDevicesService::all_devices(AsyncWebServerRequest * request) { + AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_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(); + JsonObject obj = devices.createNestedObject(); + obj["id"] = emsdevice->unique_id(); + obj["type"] = emsdevice->device_type_name(); + obj["brand"] = emsdevice->brand_to_string(); + obj["name"] = emsdevice->name(); + obj["deviceid"] = emsdevice->device_id(); + obj["productid"] = emsdevice->product_id(); + obj["version"] = emsdevice->version(); } } + + JsonArray sensors = root.createNestedArray("sensors"); + if (!EMSESP::sensor_devices().empty()) { + for (const auto & sensor : EMSESP::sensor_devices()) { + JsonObject obj = sensors.createNestedObject(); + obj["id"] = sensor.to_string(); + obj["temp"] = sensor.temperature_c; + } + } + response->setLength(); request->send(response); } +void EMSESPDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) { + if (json.is()) { + uint8_t id = json["id"]; // get id from selected table row + + AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE); +#ifndef EMSESP_STANDALONE + EMSESP::device_info(id, (JsonObject &)response->getRoot()); +#endif + response->setLength(); + request->send(response); + } else { + AsyncWebServerResponse * response = request->beginResponse(200); + request->send(response); + } +} + } // namespace emsesp \ No newline at end of file diff --git a/src/EMSESPDevicesService.h b/src/EMSESPDevicesService.h index 02f7187f1..b542e82fc 100644 --- a/src/EMSESPDevicesService.h +++ b/src/EMSESPDevicesService.h @@ -1,3 +1,21 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef EMSESPDevicesService_h #define EMSESPDevicesService_h @@ -6,23 +24,27 @@ #include #include -// #include -// #include -// #include +// #define MAX_EMSESP_STATUS_SIZE 1024 +#define MAX_EMSESP_DEVICE_SIZE 1280 -#include "version.h" - -#define MAX_EMSESP_STATUS_SIZE 1024 -#define EMSESP_DEVICES_SERVICE_PATH "/rest/emsespDevices" +#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices" +#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices" +#define DEVICE_DATA_SERVICE_PATH "/rest/deviceData" namespace emsesp { +using namespace std::placeholders; // for `_1` + class EMSESPDevicesService { public: EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager); private: - void emsespDevicesService(AsyncWebServerRequest * request); + void all_devices(AsyncWebServerRequest * request); + void scan_devices(AsyncWebServerRequest * request); + void device_data(AsyncWebServerRequest * request, JsonVariant & json); + + AsyncCallbackJsonWebHandler _device_dataHandler; }; } // namespace emsesp diff --git a/src/EMSESPScanDevicesService.cpp b/src/EMSESPScanDevicesService.cpp deleted file mode 100644 index 0abb917be..000000000 --- a/src/EMSESPScanDevicesService.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#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 deleted file mode 100644 index 0b3a873e4..000000000 --- a/src/EMSESPScanDevicesService.h +++ /dev/null @@ -1,21 +0,0 @@ -#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/EMSESPSettingsService.cpp b/src/EMSESPSettingsService.cpp index 0adac9027..9cf40cf9e 100644 --- a/src/EMSESPSettingsService.cpp +++ b/src/EMSESPSettingsService.cpp @@ -1,3 +1,21 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "EMSESPSettingsService.h" #include "emsesp.h" @@ -6,62 +24,46 @@ namespace emsesp { EMSESPSettingsService::EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(EMSESPSettings::read, EMSESPSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager) , _fsPersistence(EMSESPSettings::read, EMSESPSettings::update, this, fs, EMSESP_SETTINGS_FILE) { + addUpdateHandler([&](const String & originId) { onUpdate(); }, false); } void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) { - root["tx_mode"] = settings.tx_mode; - root["ems_bus_id"] = settings.ems_bus_id; - + root["tx_mode"] = settings.tx_mode; + root["ems_bus_id"] = settings.ems_bus_id; root["syslog_level"] = settings.syslog_level; root["syslog_mark_interval"] = settings.syslog_mark_interval; root["syslog_host"] = settings.syslog_host; - - root["master_thermostat"] = settings.master_thermostat; - root["shower_timer"] = settings.shower_timer; - root["shower_alert"] = settings.shower_alert; + root["master_thermostat"] = settings.master_thermostat; + root["shower_timer"] = settings.shower_timer; + root["shower_alert"] = settings.shower_alert; } StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) { - EMSESPSettings newSettings = {}; - newSettings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE; - newSettings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID; + settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE; + settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID; + settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL; + settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL; + settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST; + settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT; + settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER; + settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT; - newSettings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL; - newSettings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL; - newSettings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST; + return StateUpdateResult::CHANGED; +} - newSettings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT; - newSettings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER; - newSettings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT; - - bool changed = false; - - if (newSettings.tx_mode != settings.tx_mode) { - EMSESP::reset_tx(newSettings.tx_mode); // reset counters - changed = true; - } - - if ((newSettings.shower_timer != settings.shower_timer) || (newSettings.shower_alert != settings.shower_alert)) { - EMSESP::shower_.start(); - changed = true; - } - - if ((newSettings.syslog_level != settings.syslog_level) || (newSettings.syslog_mark_interval != settings.syslog_mark_interval) - || !newSettings.syslog_host.equals(settings.syslog_host)) { - EMSESP::system_.syslog_init(); - changed = true; - } - - if (changed) { - settings = newSettings; - return StateUpdateResult::CHANGED; - } - - return StateUpdateResult::UNCHANGED; +// this is called after the settings have been persisted to the filesystem +void EMSESPSettingsService::onUpdate() { + EMSESP::shower_.start(); + EMSESP::system_.syslog_init(); + EMSESP::reset_tx(); } void EMSESPSettingsService::begin() { _fsPersistence.readFromFS(); } +void EMSESPSettingsService::save() { + _fsPersistence.writeToFS(); +} + } // namespace emsesp \ No newline at end of file diff --git a/src/EMSESPSettingsService.h b/src/EMSESPSettingsService.h index 174138576..4bbd22f4c 100644 --- a/src/EMSESPSettingsService.h +++ b/src/EMSESPSettingsService.h @@ -1,3 +1,21 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef EMSESPSettingsConfig_h #define EMSESPSettingsConfig_h @@ -7,7 +25,7 @@ #define EMSESP_SETTINGS_FILE "/config/emsespSettings.json" #define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings" -#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0 +#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0 #define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key #define EMSESP_DEFAULT_SYSLOG_LEVEL -1 // OFF @@ -44,10 +62,13 @@ class EMSESPSettingsService : public StatefulService { EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); void begin(); + void save(); private: HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; + + void onUpdate(); }; } // namespace emsesp diff --git a/src/EMSESPStatusService.cpp b/src/EMSESPStatusService.cpp index ba3745a01..c0f6252b9 100644 --- a/src/EMSESPStatusService.cpp +++ b/src/EMSESPStatusService.cpp @@ -1,3 +1,21 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "EMSESPStatusService.h" #include "emsesp.h" #include "mqtt.h" @@ -12,38 +30,30 @@ EMSESPStatusService::EMSESPStatusService(AsyncWebServer * server, SecurityManage securityManager->wrapRequest(std::bind(&EMSESPStatusService::emsespStatusService, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)); -// trigger on wifi connects +// trigger on wifi connects/disconnects #ifdef ESP32 - WiFi.onEvent(onStationModeConnected, WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED); WiFi.onEvent(onStationModeDisconnected, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); #elif defined(ESP8266) - _onStationModeConnectedHandler = WiFi.onStationModeConnected(onStationModeConnected); _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(onStationModeDisconnected); _onStationModeGotIPHandler = WiFi.onStationModeGotIP(onStationModeGotIP); #endif } #ifdef ESP32 -void EMSESPStatusService::onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info) { - EMSESP::logger().debug(F("Wifi Connected")); -} void EMSESPStatusService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { EMSESP::logger().debug(F("WiFi Disconnected. Reason code=%d"), info.disconnected.reason); } void EMSESPStatusService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { - EMSESP::logger().debug(F("WiFi connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname()); + EMSESP::logger().debug(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname()); EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection } #elif defined(ESP8266) -void EMSESPStatusService::onStationModeConnected(const WiFiEventStationModeConnected & event) { - EMSESP::logger().debug(F("Wifi connected with SSID %s"), event.ssid.c_str()); -} void EMSESPStatusService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) { EMSESP::logger().debug(F("WiFi Disconnected. Reason code=%d"), event.reason); } void EMSESPStatusService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) { - EMSESP::logger().debug(F("WiFi connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str()); + EMSESP::logger().debug(F("WiFi Connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str()); EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection } #endif diff --git a/src/EMSESPStatusService.h b/src/EMSESPStatusService.h index c2abfddb2..8ee99071a 100644 --- a/src/EMSESPStatusService.h +++ b/src/EMSESPStatusService.h @@ -1,3 +1,21 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef EMSESPStatusService_h #define EMSESPStatusService_h @@ -20,14 +38,11 @@ class EMSESPStatusService { void emsespStatusService(AsyncWebServerRequest * request); #ifdef ESP32 - static void onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info); static void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info); #elif defined(ESP8266) - WiFiEventHandler _onStationModeConnectedHandler; WiFiEventHandler _onStationModeDisconnectedHandler; WiFiEventHandler _onStationModeGotIPHandler; - static void onStationModeConnected(const WiFiEventStationModeConnected & event); static void onStationModeDisconnected(const WiFiEventStationModeDisconnected & event); static void onStationModeGotIP(const WiFiEventStationModeGotIP & event); #endif diff --git a/src/console.cpp b/src/console.cpp index fe7020ff1..c594e5ba2 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -90,6 +90,7 @@ void EMSESPShell::display_banner() { // turn off watch emsesp::EMSESP::watch_id(WATCH_ID_NONE); + emsesp::EMSESP::watch(EMSESP::WATCH_OFF); } // pre-loads all the console commands into the MAIN context @@ -107,11 +108,9 @@ void EMSESPShell::add_console_commands() { commands->add_command(ShellContext::MAIN, CommandFlags::USER, - flash_string_vector{F_(refresh)}, + flash_string_vector{F_(fetch)}, [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { shell.printfln(F("Requesting data from EMS devices")); - console_commands_loaded_ = false; - add_console_commands(); EMSESP::fetch_device_values(); }); @@ -177,15 +176,14 @@ void EMSESPShell::add_console_commands() { flash_string_vector{F_(n_mandatory)}, [](Shell & shell, const std::vector & arguments) { uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10); - + // save the tx_mode EMSESP::emsespSettingsService.update( [&](EMSESPSettings & settings) { settings.tx_mode = tx_mode; - shell.printfln(F_(tx_mode_fmt), tx_mode); return StateUpdateResult::CHANGED; }, "local"); - EMSESP::reset_tx(tx_mode); // reset counters and set tx_mode + EMSESP::reset_tx(); // reset counters and set tx_mode }); commands->add_command(ShellContext::MAIN, @@ -516,6 +514,9 @@ void Console::start() { shell = std::make_shared(serial_console_, true); shell->maximum_log_messages(100); // default is 50 shell->start(); +#endif + +#if defined(EMSESP_DEBUG) shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all #endif @@ -524,8 +525,6 @@ void Console::start() { shell->add_flags(CommandFlags::ADMIN); #endif - emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // turn watch off in case it was still set in the last session - // start the telnet service // default idle is 10 minutes, default write timeout is 0 (automatic) // note, this must be started after the network/wifi for ESP32 otherwise it'll crash @@ -533,6 +532,9 @@ void Console::start() { telnet_.start(); telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second #endif + + // turn watch off in case it was still set in the last session + emsesp::EMSESP::watch(EMSESP::WATCH_OFF); } // handles telnet sync and logging to console diff --git a/src/console.h b/src/console.h index 095bed9ba..e7458abf0 100644 --- a/src/console.h +++ b/src/console.h @@ -73,7 +73,7 @@ MAKE_PSTR_WORD(read) MAKE_PSTR_WORD(version) MAKE_PSTR_WORD(values) MAKE_PSTR_WORD(system) -MAKE_PSTR_WORD(refresh) +MAKE_PSTR_WORD(fetch) MAKE_PSTR_WORD(restart) MAKE_PSTR_WORD(format) MAKE_PSTR_WORD(raw) @@ -191,8 +191,8 @@ class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPSh class Console { public: - static void loop(); - void start(); + void loop(); + void start(); uuid::log::Level log_level(); diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 44b72d7ba..64adca5d2 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -28,6 +28,8 @@ MAKE_PSTR_WORD(comfort) MAKE_PSTR_WORD(eco) MAKE_PSTR_WORD(intelligent) MAKE_PSTR_WORD(hot) +MAKE_PSTR_WORD(maxpower) +MAKE_PSTR_WORD(minpower) MAKE_PSTR(comfort_mandatory, "") @@ -59,7 +61,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_telegram_type(0x33, F("UBAParameterWW"), true, std::bind(&Boiler::process_UBAParameterWW, this, _1)); register_telegram_type(0x14, F("UBATotalUptime"), false, std::bind(&Boiler::process_UBATotalUptime, this, _1)); register_telegram_type(0x35, F("UBAFlags"), false, std::bind(&Boiler::process_UBAFlags, this, _1)); - register_telegram_type(0x15, F("UBAMaintenanceSettings"), false, std::bind(&Boiler::process_UBAMaintenanceSettings, this, _1)); + register_telegram_type(0x15, F("UBAMaintenanceData"), false, std::bind(&Boiler::process_UBAMaintenanceData, this, _1)); register_telegram_type(0x16, F("UBAParameters"), true, std::bind(&Boiler::process_UBAParameters, this, _1)); register_telegram_type(0x1A, F("UBASetPoints"), false, std::bind(&Boiler::process_UBASetPoints, this, _1)); register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, std::bind(&Boiler::process_UBAOutdoorTemp, this, _1)); @@ -103,18 +105,50 @@ void Boiler::boiler_cmd(const char * message) { uint8_t t = doc["wwtemp"]; set_warmwater_temp(t); } + if (nullptr != doc["boilhyston"]) { + int8_t t = doc["boilhyston"]; + set_hyst_on(t); + } + if (nullptr != doc["boilhystoff"]) { + uint8_t t = doc["boilhystoff"]; + set_hyst_off(t); + } + if (nullptr != doc["burnperiod"]) { + uint8_t t = doc["burnperiod"]; + set_burn_period(t); + } + if (nullptr != doc["burnminpower"]) { + uint8_t p = doc["burnminpower"]; + set_min_power(p); + } + if (nullptr != doc["burnmaxpower"]) { + uint8_t p = doc["burnmaxpower"]; + set_max_power(p); + } + if (nullptr != doc["pumpdelay"]) { + uint8_t t = doc["pumpdelay"]; + set_pump_delay(t); + } + + if (nullptr != doc["comfort"]) { + const char * data = doc["comfort"]; + if (strcmp((char *)data, "hot") == 0) { + set_warmwater_mode(1); + } else if (strcmp((char *)data, "eco") == 0) { + set_warmwater_mode(2); + } else if (strcmp((char *)data, "intelligent") == 0) { + set_warmwater_mode(3); + } + } const char * command = doc["cmd"]; - if (command == nullptr) { + if (command == nullptr || doc["data"] == nullptr) { return; } // boiler ww comfort setting if (strcmp(command, "comfort") == 0) { const char * data = doc["data"]; - if (data == nullptr) { - return; - } if (strcmp((char *)data, "hot") == 0) { set_warmwater_mode(1); } else if (strcmp((char *)data, "eco") == 0) { @@ -128,9 +162,45 @@ void Boiler::boiler_cmd(const char * message) { // boiler flowtemp setting if (strcmp(command, "flowtemp") == 0) { uint8_t t = doc["data"]; - if (t) { - set_flow_temp(t); - } + set_flow_temp(t); + return; + } + if (strcmp(command, "wwtemp") == 0) { + uint8_t t = doc["data"]; + set_warmwater_temp(t); + return; + } + // boiler max power setting + if (strcmp(command, "burnmaxpower") == 0) { + uint8_t p = doc["data"]; + set_max_power(p); + return; + } + + // boiler min power setting + if (strcmp(command, "burnminpower") == 0) { + uint8_t p = doc["data"]; + set_min_power(p); + return; + } + if (strcmp(command, "boilhyston") == 0) { + int8_t t = doc["data"]; + set_hyst_on(t); + return; + } + if (strcmp(command, "boilhystoff") == 0) { + uint8_t t = doc["data"]; + set_hyst_off(t); + return; + } + if (strcmp(command, "burnperiod") == 0) { + uint8_t t = doc["data"]; + set_burn_period(t); + return; + } + if (strcmp(command, "pumpdelay") == 0) { + uint8_t t = doc["data"]; + set_pump_delay(t); return; } } @@ -166,9 +236,27 @@ void Boiler::boiler_cmd_wwtemp(const char * message) { } } +void Boiler::device_info(JsonArray & root) { + JsonObject dataElement; + + dataElement = root.createNestedObject(); + dataElement["name"] = F("Hot tap water"); + dataElement["value"] = tap_water_active_ ? F("running") : F("off"); + + dataElement = root.createNestedObject(); + dataElement["name"] = F("Central heating"); + dataElement["value"] = heating_active_ ? F("active") : F("off"); + + render_value_json(root, "", F("Selected flow temperature"), selFlowTemp_, F_(degrees)); + render_value_json(root, "", F("Current flow temperature"), curFlowTemp_, F_(degrees), 10); + render_value_json(root, "", F("Warm Water selected temperature"), wWSelTemp_, F_(degrees)); + render_value_json(root, "", F("Warm Water set temperature"), wWSetTmp_, F_(degrees)); + render_value_json(root, "", F("Warm Water current temperature (intern)"), wWCurTmp_, F_(degrees), 10); +} + // publish values via MQTT void Boiler::publish_values() { - const size_t capacity = JSON_OBJECT_SIZE(47); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/ + const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/ DynamicJsonDocument doc(capacity); char s[10]; // for formatting strings @@ -280,7 +368,7 @@ void Boiler::publish_values() { doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10; } if (Helpers::hasValue(heatPmp_, VALUE_BOOL)) { - doc["heatPmp"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL); + doc["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL); } if (Helpers::hasValue(fanWork_, VALUE_BOOL)) { doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL); @@ -292,13 +380,37 @@ void Boiler::publish_values() { doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL); } if (Helpers::hasValue(heating_temp_)) { - doc["heating_temp"] = heating_temp_; + doc["heatingTemp"] = heating_temp_; } if (Helpers::hasValue(pump_mod_max_)) { - doc["pump_mod_max"] = pump_mod_max_; + doc["pumpModMax"] = pump_mod_max_; } if (Helpers::hasValue(pump_mod_min_)) { - doc["pump_mod_min"] = pump_mod_min_; + doc["pumpModMin"] = pump_mod_min_; + } + if (Helpers::hasValue(pumpDelay_)) { + doc["pumpDelay"] = pumpDelay_; + } + if (Helpers::hasValue(burnPeriod_)) { + doc["burnMinPeriod"] = burnPeriod_; + } + if (Helpers::hasValue(burnPowermin_)) { + doc["burnMinPower"] = burnPowermin_; + } + if (Helpers::hasValue(burnPowermax_)) { + doc["burnMaxPower"] = burnPowermax_; + } + if (Helpers::hasValue(boilTemp_on_)) { + doc["boilHystOn"] = boilTemp_on_; + } + if (Helpers::hasValue(boilTemp_off_)) { + doc["boilHystOff"] = boilTemp_off_; + } + if (Helpers::hasValue(setFlowTemp_)) { + doc["setFlowTemp"] = setFlowTemp_; + } + if (Helpers::hasValue(setWWPumpPow_)) { + doc["wWSetPumpPower"] = setWWPumpPow_; } if (Helpers::hasValue(wWStarts_)) { doc["wWStarts"] = wWStarts_; @@ -318,7 +430,6 @@ void Boiler::publish_values() { if (Helpers::hasValue(heatWorkMin_)) { doc["heatWorkMin"] = heatWorkMin_; } - if (Helpers::hasValue(serviceCode_)) { doc["serviceCode"] = serviceCodeChar_; doc["serviceCodeNumber"] = serviceCode_; @@ -416,6 +527,17 @@ void Boiler::show_values(uuid::console::Shell & shell) { print_value(shell, 2, F("Heating temperature setting on the boiler"), heating_temp_, F_(degrees)); print_value(shell, 2, F("Boiler circuit pump modulation max power"), pump_mod_max_, F_(percent)); print_value(shell, 2, F("Boiler circuit pump modulation min power"), pump_mod_min_, F_(percent)); + print_value(shell, 2, F("Boiler circuit pump delay time"), pumpDelay_, F("min")); + print_value(shell, 2, F("Boiler temp hysteresis on"), boilTemp_on_, F_(degrees)); + print_value(shell, 2, F("Boiler temp hysteresis off"), boilTemp_off_, F_(degrees)); + print_value(shell, 2, F("Boiler burner min period"), burnPeriod_, F("min")); + print_value(shell, 2, F("Boiler burner min power"), burnPowermin_, F_(percent)); + print_value(shell, 2, F("Boiler burner max power"), burnPowermax_, F_(percent)); + + // UBASetPoint - these may differ from the above + print_value(shell, 2, F("Set Flow temperature"), setFlowTemp_, F_(degrees)); + print_value(shell, 2, F("Boiler burner set power"), setBurnPow_, F_(percent)); + print_value(shell, 2, F("Warm Water pump set power"), setWWPumpPow_, F_(percent)); // UBAMonitorSlow if (Helpers::hasValue(extTemp_)) { @@ -528,6 +650,12 @@ void Boiler::process_UBATotalUptime(std::shared_ptr telegram) { */ void Boiler::process_UBAParameters(std::shared_ptr telegram) { telegram->read_value(heating_temp_, 1); + telegram->read_value(burnPowermax_,2); + telegram->read_value(burnPowermin_,3); + telegram->read_value(boilTemp_off_,4); + telegram->read_value(boilTemp_on_,5); + telegram->read_value(burnPeriod_,6); + telegram->read_value(pumpDelay_,8); telegram->read_value(pump_mod_max_, 9); telegram->read_value(pump_mod_min_, 10); } @@ -652,16 +780,16 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr telegram) { telegram->read_value(extTemp_, 0); } +// UBASetPoint 0x1A +void Boiler::process_UBASetPoints(std::shared_ptr telegram) { + telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat + telegram->read_value(setBurnPow_, 1); // max output power in % + telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power? +} + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" -// UBASetPoint 0x1A -// not yet implemented -void Boiler::process_UBASetPoints(std::shared_ptr telegram) { - // uint8_t setpoint = telegram->message_data[0]; // boiler flow temp - // uint8_t ww_power = telegram->message_data[2]; // power in % -} - // 0x35 // not yet implemented void Boiler::process_UBAFlags(std::shared_ptr telegram) { @@ -670,11 +798,7 @@ void Boiler::process_UBAFlags(std::shared_ptr telegram) { // 0x1C // not yet implemented void Boiler::process_UBAMaintenanceStatus(std::shared_ptr telegram) { -} - -// 0x15 -// not yet implemented -void Boiler::process_UBAMaintenanceSettings(std::shared_ptr telegram) { + // first byte: Maintenance due (0 = no, 3 = yes, due to operating hours, 8 = yes, due to date) } // 0x10, 0x11, 0x12 @@ -685,12 +809,20 @@ void Boiler::process_UBAErrorMessage(std::shared_ptr telegram) { #pragma GCC diagnostic pop +// 0x15 +void Boiler::process_UBAMaintenanceData(std::shared_ptr telegram) { + // first byte: Maintenance messages (0 = none, 1 = by operating hours, 2 = by date) + // I see a value of 3 in the 1st byte when the boiler is booted, so probably a flag + if (telegram->message_data[0] == 3) { + LOG_WARNING(F("Boiler has booted.")); + } +} + // Set the warm water temperature 0x33 void Boiler::set_warmwater_temp(const uint8_t temperature) { LOG_INFO(F("Setting boiler warm water temperature to %d C"), temperature); write_command(EMS_TYPE_UBAParameterWW, 2, temperature); - // for i9000, see #397 - write_command(EMS_TYPE_UBAFlags, 3, temperature); + write_command(EMS_TYPE_UBAFlags, 3, temperature); // for i9000, see #397 } // flow temp @@ -699,6 +831,42 @@ void Boiler::set_flow_temp(const uint8_t temperature) { write_command(EMS_TYPE_UBASetPoints, 0, temperature); } +// set min boiler output +void Boiler::set_min_power(const uint8_t power) { + LOG_INFO(F("Setting boiler min power to "), power); + write_command(EMS_TYPE_UBAParameters, 3, power); +} + +// set max temp +void Boiler::set_max_power(const uint8_t power) { + LOG_INFO(F("Setting boiler max power to %d C"), power); + write_command(EMS_TYPE_UBAParameters, 2, power); +} + +// set oiler on hysteresis +void Boiler::set_hyst_on(const uint8_t temp) { + LOG_INFO(F("Setting boiler hysteresis on to %d C"), temp); + write_command(EMS_TYPE_UBAParameters, 5, temp); +} + +// set boiler off hysteresis +void Boiler::set_hyst_off(const uint8_t temp) { + LOG_INFO(F("Setting boiler hysteresis off to %d C"), temp); + write_command(EMS_TYPE_UBAParameters, 4, temp); +} + +// set min burner period +void Boiler::set_burn_period(const uint8_t t) { + LOG_INFO(F("Setting burner min. period to %d min"), t); + write_command(EMS_TYPE_UBAParameters, 6, t); +} + +// set pump delay +void Boiler::set_pump_delay(const uint8_t t) { + LOG_INFO(F("Setting boiler pump delay to %d min"), t); + write_command(EMS_TYPE_UBAParameters, 8, t); +} + // 1=hot, 2=eco, 3=intelligent // note some boilers do not have this setting, than it's done by thermostat // on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) @@ -803,6 +971,22 @@ void Boiler::console_commands(Shell & shell, unsigned int context) { set_flow_temp(Helpers::atoint(arguments.front().c_str())); }); + EMSESPShell::commands->add_command(ShellContext::BOILER, + CommandFlags::ADMIN, + flash_string_vector{F_(maxpower)}, + flash_string_vector{F_(n_mandatory)}, + [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { + set_max_power(Helpers::atoint(arguments.front().c_str())); + }); + + EMSESPShell::commands->add_command(ShellContext::BOILER, + CommandFlags::ADMIN, + flash_string_vector{F_(minpower)}, + flash_string_vector{F_(n_mandatory)}, + [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { + set_min_power(Helpers::atoint(arguments.front().c_str())); + }); + EMSESPShell::commands->add_command( ShellContext::BOILER, CommandFlags::ADMIN, diff --git a/src/devices/boiler.h b/src/devices/boiler.h index 41f7e8160..fc77d7e28 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -40,6 +40,7 @@ class Boiler : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); @@ -54,6 +55,7 @@ class Boiler : public EMSdevice { static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D; static constexpr uint8_t EMS_TYPE_UBAFlags = 0x35; static constexpr uint8_t EMS_TYPE_UBASetPoints = 0x1A; + static constexpr uint8_t EMS_TYPE_UBAParameters = 0x16; static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344 @@ -119,11 +121,22 @@ class Boiler : public EMSdevice { uint8_t heating_temp_ = EMS_VALUE_UINT_NOTSET; // Heating temperature setting on the boiler uint8_t pump_mod_max_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation max. power % uint8_t pump_mod_min_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation min. power + uint8_t burnPowermin_ = EMS_VALUE_UINT_NOTSET; + uint8_t burnPowermax_ = EMS_VALUE_UINT_NOTSET; + int8_t boilTemp_off_ = EMS_VALUE_INT_NOTSET; + int8_t boilTemp_on_ = EMS_VALUE_UINT_NOTSET; + uint8_t burnPeriod_ = EMS_VALUE_UINT_NOTSET; + uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET; + // UBASetPoint + uint8_t setFlowTemp_ = EMS_VALUE_UINT_NOTSET; // boiler setpoint temp + uint8_t setBurnPow_ = EMS_VALUE_UINT_NOTSET; // max output power in % + uint8_t setWWPumpPow_ = EMS_VALUE_UINT_NOTSET; // ww pump speed/power? + + // other internal calculated params uint8_t tap_water_active_ = EMS_VALUE_BOOL_NOTSET; // Hot tap water is on/off uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off - - uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps) + uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps) void process_UBAParameterWW(std::shared_ptr telegram); void process_UBAMonitorFast(std::shared_ptr telegram); @@ -140,7 +153,7 @@ class Boiler : public EMSdevice { void process_UBAFlags(std::shared_ptr telegram); void process_MC10Status(std::shared_ptr telegram); void process_UBAMaintenanceStatus(std::shared_ptr telegram); - void process_UBAMaintenanceSettings(std::shared_ptr telegram); + void process_UBAMaintenanceData(std::shared_ptr telegram); void process_UBAErrorMessage(std::shared_ptr telegram); void process_UBADHWStatus(std::shared_ptr telegram); @@ -154,6 +167,13 @@ class Boiler : public EMSdevice { void set_tapwarmwater_activated(const bool activated); void set_warmwater_onetime(const bool activated); void set_warmwater_circulation(const bool activated); + void set_min_power(const uint8_t power); + void set_max_power(const uint8_t power); + void set_hyst_on(const uint8_t temp); + void set_hyst_off(const uint8_t temp); + void set_burn_period(const uint8_t t); + void set_pump_delay(const uint8_t t); + // mqtt callbacks void boiler_cmd(const char * message); diff --git a/src/devices/connect.cpp b/src/devices/connect.cpp index 07fb346c7..d8da0d612 100644 --- a/src/devices/connect.cpp +++ b/src/devices/connect.cpp @@ -34,6 +34,9 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con // register_mqtt_topic("topic", std::bind(&Connect::cmd, this, _1)); } +void Connect::device_info(JsonArray & root) { +} + void Connect::add_context_menu() { } diff --git a/src/devices/connect.h b/src/devices/connect.h index 37bf8a7e9..51956ef5a 100644 --- a/src/devices/connect.h +++ b/src/devices/connect.h @@ -37,6 +37,7 @@ class Connect : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); diff --git a/src/devices/controller.cpp b/src/devices/controller.cpp index c7d5727e9..a6796ce8b 100644 --- a/src/devices/controller.cpp +++ b/src/devices/controller.cpp @@ -39,6 +39,9 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i void Controller::add_context_menu() { } +void Controller::device_info(JsonArray & root) { +} + // display all values into the shell console void Controller::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // always call this to show header diff --git a/src/devices/controller.h b/src/devices/controller.h index 36866a8f7..d905b3864 100644 --- a/src/devices/controller.h +++ b/src/devices/controller.h @@ -37,6 +37,7 @@ class Controller : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); diff --git a/src/devices/gateway.cpp b/src/devices/gateway.cpp index 101791f1d..aa8bec0bf 100644 --- a/src/devices/gateway.cpp +++ b/src/devices/gateway.cpp @@ -39,6 +39,9 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con void Gateway::add_context_menu() { } +void Gateway::device_info(JsonArray & root) { +} + // display all values into the shell console void Gateway::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // always call this to show header diff --git a/src/devices/gateway.h b/src/devices/gateway.h index e27eff894..d38dbb32e 100644 --- a/src/devices/gateway.h +++ b/src/devices/gateway.h @@ -37,6 +37,7 @@ class Gateway : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); diff --git a/src/devices/heatpump.cpp b/src/devices/heatpump.cpp index fec97a61d..14b8e74ea 100644 --- a/src/devices/heatpump.cpp +++ b/src/devices/heatpump.cpp @@ -50,6 +50,9 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c void Heatpump::add_context_menu() { } +void Heatpump::device_info(JsonArray & root) { +} + // display all values into the shell console void Heatpump::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // always call this to show header diff --git a/src/devices/heatpump.h b/src/devices/heatpump.h index dda5acd3c..a23921698 100644 --- a/src/devices/heatpump.h +++ b/src/devices/heatpump.h @@ -37,6 +37,7 @@ class Heatpump : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); diff --git a/src/devices/mixing.cpp b/src/devices/mixing.cpp index 52938aff7..7fc8e4424 100644 --- a/src/devices/mixing.cpp +++ b/src/devices/mixing.cpp @@ -57,6 +57,24 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const void Mixing::add_context_menu() { } +// output json to web UI +void Mixing::device_info(JsonArray & root) { + if (type_ == Type::NONE) { + return; // don't have any values yet + } + + if (type_ == Type::WWC) { + render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr); + } else { + render_value_json(root, "", F("Heating Circuit"), hc_, nullptr); + } + render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10); + render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees)); + render_value_json(root, "", F("Current pump modulation"), pumpMod_, F_(percent)); + render_value_json(root, "", F("Current valve status"), status_, nullptr); + +} + // check to see if values have been updated bool Mixing::updated_values() { return false; diff --git a/src/devices/mixing.h b/src/devices/mixing.h index ed1e8ce39..d1c6b09b9 100644 --- a/src/devices/mixing.h +++ b/src/devices/mixing.h @@ -37,6 +37,7 @@ class Mixing : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index feb2987c0..30f8974a7 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -58,6 +58,32 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s void Solar::add_context_menu() { } +// print to web +void Solar::device_info(JsonArray & root) { + render_value_json(root, "", F("Collector temperature (TS1)"), collectorTemp_, F_(degrees), 10); + render_value_json(root, "", F("Bottom temperature (TS2)"), bottomTemp_, F_(degrees), 10); + render_value_json(root, "", F("Bottom temperature (TS5)"), bottomTemp2_, F_(degrees), 10); + render_value_json(root, "", F("Pump modulation"), pumpModulation_, F_(percent)); + render_value_json(root, "", F("Valve (VS2) status"), valveStatus_, nullptr, EMS_VALUE_BOOL); + render_value_json(root, "", F("Pump (PS1) active"), pump_, nullptr, EMS_VALUE_BOOL); + + if (Helpers::hasValue(pumpWorkMin_)) { + JsonObject dataElement; + dataElement = root.createNestedObject(); + dataElement["name"] = F("Pump working time"); + std::string time_str(60, '\0'); + snprintf_P(&time_str[0], time_str.capacity() + 1, PSTR("%d days %d hours %d minutes"), pumpWorkMin_ / 1440, (pumpWorkMin_ % 1440) / 60, pumpWorkMin_ % 60); + dataElement["value"] = time_str; + } + + render_value_json(root, "", F("Tank Heated"), tankHeated_, nullptr, EMS_VALUE_BOOL); + render_value_json(root, "", F("Collector shutdown"), collectorOnOff_, nullptr, EMS_VALUE_BOOL); + + render_value_json(root, "", F("Energy last hour"), energyLastHour_, F_(wh), 10); + render_value_json(root, "", F("Energy today"), energyToday_, F_(wh)); + render_value_json(root, "", F("Energy total"), energyTotal_, F_(kwh), 10); +} + // display all values into the shell console void Solar::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // always call this to show header @@ -74,7 +100,7 @@ void Solar::show_values(uuid::console::Shell & shell) { } print_value(shell, 2, F("Tank Heated"), tankHeated_, nullptr, EMS_VALUE_BOOL); - print_value(shell, 2, F("Collector"), collectorOnOff_, nullptr, EMS_VALUE_BOOL); + print_value(shell, 2, F("Collector shutdown"), collectorOnOff_, nullptr, EMS_VALUE_BOOL); print_value(shell, 2, F("Energy last hour"), energyLastHour_, F_(wh), 10); print_value(shell, 2, F("Energy today"), energyToday_, F_(wh)); @@ -203,8 +229,8 @@ void Solar::process_SM100Status(std::shared_ptr telegram) { pumpModulation_ = 15; // set to minimum } - telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422 - telegram->read_bitvalue(collectorOnOff_, 3, 0); + telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422 + telegram->read_bitvalue(collectorOnOff_, 3, 0); // collector shutdown } /* @@ -240,11 +266,14 @@ void Solar::process_ISM1StatusMessage(std::shared_ptr telegram) if (Wh != 0xFFFF) { energyLastHour_ = Wh * 10; // set to *10 } - telegram->read_bitvalue(collectorOnOff_, 9, 0); // collector on/off + 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(collectorOnOff_, 9, 0); // collector shutdown on/off telegram->read_bitvalue(tankHeated_, 9, 2); // tank full } /* + * Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values * e.g. 90 30 FF 06 00 01 50 */ void Solar::process_ISM1Set(std::shared_ptr telegram) { diff --git a/src/devices/solar.h b/src/devices/solar.h index 5a1be5e5b..1fb9864a1 100644 --- a/src/devices/solar.h +++ b/src/devices/solar.h @@ -37,6 +37,7 @@ class Solar : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); @@ -57,7 +58,7 @@ class Solar : public EMSdevice { uint32_t energyTotal_ = EMS_VALUE_ULONG_NOTSET; uint32_t pumpWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total solar pump operating time uint8_t tankHeated_ = EMS_VALUE_BOOL_NOTSET; - uint8_t collectorOnOff_ = EMS_VALUE_BOOL_NOTSET; + uint8_t collectorOnOff_ = EMS_VALUE_BOOL_NOTSET; // Collector shutdown on/off uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET; diff --git a/src/devices/switch.cpp b/src/devices/switch.cpp index f86858fff..c7a160260 100644 --- a/src/devices/switch.cpp +++ b/src/devices/switch.cpp @@ -39,6 +39,9 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const void Switch::add_context_menu() { } +void Switch::device_info(JsonArray & root) { +} + // display all values into the shell console void Switch::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // always call this to show header diff --git a/src/devices/switch.h b/src/devices/switch.h index cf74014eb..44d9844d6 100644 --- a/src/devices/switch.h +++ b/src/devices/switch.h @@ -37,6 +37,7 @@ class Switch : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 9c52e0760..3468abe06 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -141,8 +141,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i // if we're on auto mode, register this thermostat if it has a device id of 0x10 or 0x17 // or if its the master thermostat we defined // see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161 - if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && ((device_id == 0x10) || (device_id == 0x17))) - || (master_thermostat == device_id)) { + if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) { EMSESP::actual_master_thermostat(device_id); LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X (as master)"), device_id); init_mqtt(); @@ -169,6 +168,59 @@ void Thermostat::init_mqtt() { register_mqtt_topic("thermostat_cmd_mode", std::bind(&Thermostat::thermostat_cmd_mode, this, _1)); } +// prepare data for Web UI +void Thermostat::device_info(JsonArray & root) { + uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits + + for (const auto & hc : heating_circuits_) { + if (!Helpers::hasValue(hc->setpoint_roomTemp)) { + break; // skip this HC + } + + // different thermostat types store their temperature values differently + uint8_t format_setpoint, format_curr; + switch (flags) { + case EMS_DEVICE_FLAG_EASY: + format_setpoint = 100; // *100 + format_curr = 100; // *100 + break; + case EMS_DEVICE_FLAG_JUNKERS: + format_setpoint = 10; // *10 + format_curr = 10; // *10 + break; + default: // RC30, RC35 etc... + format_setpoint = 2; // *2 + format_curr = 10; // *10 + break; + } + + // create prefix with heating circuit number + std::string hc_str(5, '\0'); + snprintf_P(&hc_str[0], hc_str.capacity() + 1, PSTR("hc%d: "), hc->hc_num()); + + render_value_json(root, hc_str, F("Current room temperature"), hc->curr_roomTemp, F_(degrees), format_curr); + render_value_json(root, hc_str, F("Setpoint room temperature"), hc->setpoint_roomTemp, F_(degrees), format_setpoint); + if (Helpers::hasValue(hc->mode)) { + JsonObject dataElement; + dataElement = root.createNestedObject(); + std::string mode_str(15, '\0'); + snprintf_P(&mode_str[0], mode_str.capacity() + 1, PSTR("%sMode"), hc_str.c_str()); + dataElement["name"] = mode_str; + std::string modetype_str(20, '\0'); + if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) { + snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - summer"), mode_tostring(hc->get_mode(flags)).c_str()); + } else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) { + snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - holiday"), mode_tostring(hc->get_mode(flags)).c_str()); + } else if (Helpers::hasValue(hc->mode_type)) { + snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - %s"), mode_tostring(hc->get_mode(flags)).c_str(), mode_tostring(hc->get_mode_type(flags)).c_str()); + } else { + snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, mode_tostring(hc->get_mode(flags)).c_str()); + } + dataElement["value"] = modetype_str; + } + } +} + // only add the menu for the master thermostat void Thermostat::add_context_menu() { if (device_id() != EMSESP::actual_master_thermostat()) { @@ -258,7 +310,7 @@ void Thermostat::thermostat_cmd(const char * message) { set_holiday(holiday.c_str(), hc_num); } } - + // commands without heatingcircuit if (nullptr != doc["wwmode"]) { std::string mode = doc["wwmode"]; set_ww_mode(mode); @@ -363,10 +415,10 @@ void Thermostat::thermostat_cmd(const char * message) { // check for commands like {"hc":2,"cmd":"temp","data":21} const char * command = doc["cmd"]; - if (command == nullptr) { + if (command == nullptr || doc["data"] == nullptr) { return; } - + // ok, we have command and data if (strcmp(command, "temp") == 0) { float f = doc["data"]; if (f) { @@ -374,8 +426,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - - // thermostat mode changes if (strcmp(command, "mode") == 0) { std::string mode = doc["data"]; if (mode.empty()) { @@ -384,7 +434,6 @@ void Thermostat::thermostat_cmd(const char * message) { set_mode(mode, hc_num); return; } - if (strcmp(command, "nighttemp") == 0) { float f = doc["data"]; if (f) { @@ -392,7 +441,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - if (strcmp(command, "daytemp") == 0) { float f = doc["data"]; if (f) { @@ -400,7 +448,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - if (strcmp(command, "holidaytemp") == 0) { float f = doc["data"]; if (f) { @@ -408,7 +455,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - if (strcmp(command, "ecotemp") == 0) { float f = doc["data"]; if (f) { @@ -416,7 +462,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - if (strcmp(command, "heattemp") == 0) { float f = doc["data"]; if (f) { @@ -424,7 +469,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - if (strcmp(command, "nofrosttemp") == 0) { float f = doc["data"]; if (f) { @@ -432,7 +476,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - if (strcmp(command, "summertemp") == 0) { float f = doc["data"]; if (f) { @@ -440,7 +483,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - if (strcmp(command, "designtemp") == 0) { float f = doc["data"]; if (f) { @@ -448,7 +490,6 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } - if (strcmp(command, "offsettemp") == 0) { float f = doc["data"]; if (f) { @@ -456,6 +497,40 @@ void Thermostat::thermostat_cmd(const char * message) { } return; } + if (strcmp(command, "remotetemp") == 0) { + float f = doc["data"]; + if (f > 100 || f < 0) { + Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET); + } else { + Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10)); + } + return; + } + if (strcmp(command, "control") == 0) { + uint8_t ctrl = doc["data"]; + set_control(ctrl, hc_num); + return; + } + if (strcmp(command, "pause") == 0) { + uint8_t p = doc["data"]; + set_pause(p, hc_num); + return; + } + if (strcmp(command, "party") == 0) { + uint8_t p = doc["data"]; + set_party(p, hc_num); + return; + } + if (strcmp(command, "holiday") == 0) { + std::string holiday = doc["data"]; + set_holiday(holiday.c_str(), hc_num); + return; + } + if (strcmp(command, "date") == 0) { + std::string date = doc["data"]; + set_datetime(date.c_str()); + return; + } } void Thermostat::thermostat_cmd_temp(const char * message) { @@ -552,7 +627,8 @@ void Thermostat::publish_values() { } // send this specific data using the thermostat_data topic - if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::HA)) { + // if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::HA)) { + if (mqtt_format_ != MQTT_format::NESTED) { Mqtt::publish("thermostat_data", doc); rootThermostat = doc.to(); // clear object } @@ -566,7 +642,8 @@ void Thermostat::publish_values() { has_data = true; // if the MQTT format is 'nested' or 'ha' then create the parent object hc - if (mqtt_format_ != MQTT_format::SINGLE) { + // if (mqtt_format_ != MQTT_format::SINGLE) { + if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) { char hc_name[10]; // hc{1-4} strlcpy(hc_name, "hc", 10); char s[3]; @@ -655,7 +732,8 @@ void Thermostat::publish_values() { // if format is single, send immediately and clear object for next hc // the topic will have the hc number appended - if (mqtt_format_ == MQTT_format::SINGLE) { + // if (mqtt_format_ == MQTT_format::SINGLE) { + if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) { char topic[30]; char s[3]; strlcpy(topic, "thermostat_data", 30); @@ -674,10 +752,9 @@ void Thermostat::publish_values() { } // if we're using nested json, send all in one go under one topic called thermostat_data + // if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) { if (mqtt_format_ == MQTT_format::NESTED) { Mqtt::publish("thermostat_data", doc); - } else if (mqtt_format_ == MQTT_format::CUSTOM) { - Mqtt::publish("thermostat_data", doc); } } @@ -987,7 +1064,7 @@ void Thermostat::show_values(uuid::console::Shell & shell) { shell.printfln(F(" Display: time")); } else if (ibaMainDisplay_ == 7) { shell.printfln(F(" Display: date")); - } else if (ibaMainDisplay_ == 9) { + } else if (ibaMainDisplay_ == 8) { shell.printfln(F(" Display: smoke temperature")); } } @@ -1024,8 +1101,6 @@ void Thermostat::show_values(uuid::console::Shell & shell) { } } - // std::sort(heating_circuits_.begin(), heating_circuits_.end()); // sort based on hc number. This has moved to the heating_circuit() function - for (const auto & hc : heating_circuits_) { if (!Helpers::hasValue(hc->setpoint_roomTemp)) { break; // skip this HC @@ -1168,7 +1243,7 @@ void Thermostat::process_EasyMonitor(std::shared_ptr telegram) { void Thermostat::process_IBASettings(std::shared_ptr telegram) { // 22 - display line on RC35 telegram->read_value(ibaMainDisplay_, - 0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp + 0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy @@ -1310,11 +1385,16 @@ void Thermostat::process_RCTime(std::shared_ptr telegram) { if (flags() == EMS_DEVICE_FLAG_EASY) { return; // not supported } - + if (telegram->message_length < 7) { + return; + } + if (telegram->message_data[7] & 0x0C) { // date and time not valid + set_datetime("NTP"); // set from NTP + return; + } if (datetime_.empty()) { datetime_.resize(25, '\0'); } - // render time to HH:MM:SS DD/MM/YYYY // had to create separate buffers because of how printf works char buf1[6]; @@ -1469,18 +1549,38 @@ void Thermostat::set_party(const uint8_t hrs, const uint8_t hc_num) { } } -// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst +// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst or "NTP" for setting to internet-time // dw - day of week (0..6), dst- summertime (0/1) void Thermostat::set_datetime(const char * dt) { uint8_t data[9]; - data[0] = (dt[16] - '0') * 100 + (dt[17] - '0') * 10 + (dt[18] - '0'); // year - data[1] = (dt[12] - '0') * 10 + (dt[13] - '0'); // month - data[2] = (dt[0] - '0') * 10 + (dt[1] - '0'); // hour - data[3] = (dt[9] - '0') * 10 + (dt[10] - '0'); // day - data[4] = (dt[3] - '0') * 10 + (dt[4] - '0'); // min - data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec - data[6] = (dt[20] - '0'); // day of week - data[7] = (dt[22] - '0'); // summerime + if (strcmp(dt,"NTP") == 0) { + time_t now = time(nullptr); + tm * tm_ = localtime(&now); + if (tm_->tm_year < 110) { // no NTP time + LOG_WARNING(F("No NTP time. Cannot set RCtime")); + return; + } + data[0] = tm_->tm_year - 100; // Bosch counts from 2000 + data[1] = tm_->tm_mon; + data[2] = tm_->tm_hour; + data[3] = tm_->tm_mday; + data[4] = tm_->tm_min; + data[5] = tm_->tm_sec; + data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su + data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock + char time_string[25]; + strftime(time_string, 25, "%FT%T%z", tm_); + LOG_INFO(F("Date and time: %s"), time_string); + } else { + data[0] = (dt[16] - '0') * 100 + (dt[17] - '0') * 10 + (dt[18] - '0'); // year + data[1] = (dt[12] - '0') * 10 + (dt[13] - '0'); // month + data[2] = (dt[0] - '0') * 10 + (dt[1] - '0'); // hour + data[3] = (dt[9] - '0') * 10 + (dt[10] - '0'); // day + data[4] = (dt[3] - '0') * 10 + (dt[4] - '0'); // min + data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec + data[6] = (dt[20] - '0'); // day of week + data[7] = (dt[22] - '0') + 2; // DST and flag + } if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35 || (flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { LOG_INFO(F("Setting date and time")); write_command(6, 0, data, 8, 0); diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index 8f2ea0c76..e0db67f99 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -31,6 +31,7 @@ #include "mqtt.h" #include +#include namespace emsesp { @@ -94,6 +95,7 @@ class Thermostat : public EMSdevice { virtual void show_values(uuid::console::Shell & shell); virtual void publish_values(); + virtual void device_info(JsonArray & root); virtual bool updated_values(); virtual void add_context_menu(); diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 72fe9da06..2185b17df 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -127,7 +127,7 @@ uint8_t EMSdevice::decode_brand(uint8_t value) { } } -// print human friendly description of the EMS device +// returns string of a human friendly description of the EMS device std::string EMSdevice::to_string() const { std::string str(160, '\0'); @@ -153,6 +153,17 @@ std::string EMSdevice::to_string() const { return str; } +// returns out brand + device name +std::string EMSdevice::to_string_short() const { + std::string str(160, '\0'); + if (brand_ == Brand::NO_BRAND) { + snprintf_P(&str[0], str.capacity() + 1, PSTR("%s: %s"), device_type_name().c_str(), name_.c_str()); + } else { + snprintf_P(&str[0], str.capacity() + 1, PSTR("%s: %s %s"), device_type_name().c_str(), brand_to_string().c_str(), name_.c_str()); + } + return str; +} + // prints the header for the section void EMSdevice::show_values(uuid::console::Shell & shell) { shell.printfln(F("%s: %s"), device_type_name().c_str(), to_string().c_str()); @@ -198,7 +209,7 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) { Mqtt::show_topic_handlers(shell, this->device_id_); } -void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_function_p f) { +void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) { LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_); Mqtt::subscribe(this->device_id_, topic, f); } diff --git a/src/emsdevice.h b/src/emsdevice.h index 8b691bcee..2abc3bc42 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -102,12 +102,22 @@ class EMSdevice { return name_; } + inline uint8_t unique_id() const { + return unique_id_; + } + + inline void unique_id(uint8_t unique_id) { + unique_id_ = unique_id; + } + std::string brand_to_string() const; static uint8_t decode_brand(uint8_t value); std::string to_string() const; - void show_telegram_handlers(uuid::console::Shell & shell); - void show_mqtt_handlers(uuid::console::Shell & shell); + std::string to_string_short() const; + + void show_telegram_handlers(uuid::console::Shell & shell); + void show_mqtt_handlers(uuid::console::Shell & shell); using process_function_p = std::function)>; void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p cb); @@ -119,13 +129,14 @@ class EMSdevice { void read_command(const uint16_t type_id); - void register_mqtt_topic(const std::string & topic, mqtt_function_p f); + void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f); // virtual functions overrules by derived classes virtual void show_values(uuid::console::Shell & shell) = 0; virtual void publish_values() = 0; virtual bool updated_values() = 0; virtual void add_context_menu() = 0; + virtual void device_info(JsonArray & root) = 0; std::string telegram_type_name(std::shared_ptr telegram); @@ -165,6 +176,37 @@ class EMSdevice { } } + // takes a value from an ems device and creates a nested json (name, value) + // which can be passed to the web UI + template + static void render_value_json(JsonArray & json, + const std::string & prefix, + const __FlashStringHelper * name, + Value & value, + const __FlashStringHelper * suffix, + const uint8_t format = 0) { + char buffer[15]; + if (Helpers::render_value(buffer, value, format) == nullptr) { + return; + } + + JsonObject dataElement = json.createNestedObject(); + + // copy flash into std::strings to ensure arduinojson can reference them without a copy + + if (suffix != nullptr) { + std::string text(20, '\0'); + snprintf_P(&text[0], text.capacity() + 1, PSTR("%s%s"), buffer, uuid::read_flash_string(suffix).c_str()); + dataElement["value"] = text; + } else { + dataElement["value"] = buffer; + } + + std::string text2(100, '\0'); + snprintf_P(&text2[0], text2.capacity() + 1, PSTR("%s%s"), prefix.c_str(), uuid::read_flash_string(name).c_str()); + dataElement["name"] = text2; + } + static void print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const __FlashStringHelper * value); static void print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value); @@ -229,6 +271,7 @@ class EMSdevice { static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS_2 = (1 << 6); // 6th bit set if older models private: + uint8_t unique_id_; uint8_t device_type_ = DeviceType::UNKNOWN; uint8_t device_id_ = 0; uint8_t product_id_ = 0; diff --git a/src/emsesp.cpp b/src/emsesp.cpp index a927dc573..e26fb9e26 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -39,9 +39,8 @@ ESP8266React EMSESP::esp8266React(&webServer, &dummyFS); EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager()); #endif -EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager()); -EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager()); -EMSESPScanDevicesService EMSESP::emsespScanDevicesService = EMSESPScanDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager()); +EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager()); +EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&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 @@ -63,6 +62,7 @@ uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; / uint8_t EMSESP::watch_ = 0; // trace off bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower() uint32_t EMSESP::last_fetch_ = 0; +uint8_t EMSESP::unique_id_count_ = 0; // for a specific EMS device go and request data values // or if device_id is 0 it will fetch from all our known and active devices @@ -110,13 +110,17 @@ 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); +void EMSESP::reset_tx() { + // get the tx_mode + uint8_t tx_mode; + EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode = settings.tx_mode; }); + + EMSuart::stop(); + EMSuart::start(tx_mode); + txservice_.start(); + + // force a fetch for all new values, unless Tx is set to off + if (tx_mode != 0) { EMSESP::fetch_device_values(); } } @@ -151,7 +155,22 @@ uint8_t EMSESP::bus_status() { // show the EMS bus status plus both Rx and Tx queues void EMSESP::show_ems(uuid::console::Shell & shell) { // EMS bus information - if (rxservice_.bus_connected()) { + switch (bus_status()) { + case BUS_STATUS_OFFLINE: + shell.printfln(F("EMS Bus is disconnected.")); + break; + case BUS_STATUS_TX_ERRORS: + shell.printfln(F("EMS Bus is connected, but Tx is not stable.")); + break; + case BUS_STATUS_CONNECTED: + default: + shell.printfln(F("EMS Bus is connected.")); + break; + } + + shell.println(); + + if (bus_status() != BUS_STATUS_OFFLINE) { uint8_t success_rate = 0; if (rxservice_.telegram_error_count()) { success_rate = ((float)rxservice_.telegram_error_count() / (float)rxservice_.telegram_count()) * 100; @@ -165,8 +184,6 @@ void EMSESP::show_ems(uuid::console::Shell & shell) { shell.printfln(F(" #write requests sent: %d"), txservice_.telegram_write_count()); shell.printfln(F(" #corrupted telegrams: %d (%d%%)"), rxservice_.telegram_error_count(), success_rate); shell.printfln(F(" #tx fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_fail_count()); - } else { - shell.printfln(F("EMS Bus is disconnected.")); } shell.println(); @@ -231,7 +248,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) { } } -// show Dallas sensors +// show Dallas temperature sensors void EMSESP::show_sensor_values(uuid::console::Shell & shell) { if (sensor_devices().empty()) { return; @@ -240,7 +257,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) { char valuestr[8] = {0}; // for formatting temp shell.printfln(F("External temperature sensors:")); for (const auto & device : sensor_devices()) { - shell.printfln(F(" Sensor ID %s: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c_, 2)); + shell.printfln(F(" ID: %s, Temperature: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c, 2)); } shell.println(); } @@ -474,6 +491,20 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { return found; } +// calls the device handler's function to populate a json doc with device info +void EMSESP::device_info(const uint8_t unique_id, JsonObject & root) { + for (const auto & emsdevice : emsdevices) { + if (emsdevice) { + if (emsdevice->unique_id() == unique_id) { + root["deviceName"] = emsdevice->to_string_short(); // can;t use c_str() because of scope + JsonArray data = root.createNestedArray("deviceData"); + emsdevice->device_info(data); + return; + } + } + } +} + // return true if we have this device already registered bool EMSESP::device_exists(const uint8_t device_id) { for (const auto & emsdevice : emsdevices) { @@ -513,6 +544,9 @@ void EMSESP::show_devices(uuid::console::Shell & shell) { // shell.printf(F("[factory ID: %d] "), device_class.first); for (const auto & emsdevice : emsdevices) { if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { +#if defined(EMSESP_DEBUG) + shell.printf(F("[id=%d] "), emsdevice->unique_id()); +#endif shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str()); if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) { shell.printf(F(" ** master device **")); @@ -584,6 +618,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: } else { emsdevices.push_back( EMSFactory::add(device_p->device_type, device_id, device_p->product_id, version, uuid::read_flash_string(device_p->name), device_p->flags, brand)); + emsdevices.back()->unique_id(++unique_id_count_); LOG_DEBUG(F("Adding new device with device ID 0x%02X with product ID %d and version %s"), device_id, product_id, version.c_str()); // go and fetch its data, fetch_device_values(device_id); @@ -629,14 +664,14 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { } // are we waiting for a response from a recent Tx Read or Write? - uint8_t op = EMSbus::tx_waiting(); - if (op != Telegram::Operation::NONE) { + uint8_t tx_state = EMSbus::tx_state(); + if (tx_state != Telegram::Operation::NONE) { bool tx_successful = false; - EMSbus::tx_waiting(Telegram::Operation::NONE); // reset Tx wait state + EMSbus::tx_state(Telegram::Operation::NONE); // reset Tx wait state // txservice_.print_last_tx(); // if we're waiting on a Write operation, we want a single byte 1 or 4 - if ((op == Telegram::Operation::TX_WRITE) && (length == 1)) { + if ((tx_state == Telegram::Operation::TX_WRITE) && (length == 1)) { if (first_value == TxService::TX_WRITE_SUCCESS) { LOG_DEBUG(F("Last Tx write successful")); txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok @@ -649,7 +684,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { txservice_.send_poll(); // close the bus txservice_.reset_retry_count(); } - } else if (op == Telegram::Operation::TX_READ) { + } else if (tx_state == Telegram::Operation::TX_READ) { // got a telegram with data in it. See if the src/dest matches that from the last one we sent and continue to process it uint8_t src = data[0]; uint8_t dest = data[1]; @@ -664,13 +699,15 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { // if Tx wasn't successful, retry or just give up if (!tx_successful) { - txservice_.retry_tx(op, data, length); + txservice_.retry_tx(tx_state, data, length); return; } } // check for poll if (length == 1) { + EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active + #ifdef EMSESP_DEBUG char s[4]; if (first_value & 0x80) { @@ -684,7 +721,6 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { // check for poll to us, if so send top message from Tx queue immediately and quit // if ht3 poll must be ems_bus_id else if Buderus poll must be (ems_bus_id | 0x80) if ((first_value ^ 0x80 ^ rxservice_.ems_mask()) == txservice_.ems_bus_id()) { - EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active txservice_.send(); } // send remote room temperature if active @@ -741,15 +777,17 @@ void EMSESP::loop() { // if we're doing an OTA upload, skip MQTT and EMS if (system_.upload_status()) { +#if defined(ESP32) + delay(10); // slow down OTA update to avoid getting killed by task watchdog (task_wdt) +#endif return; } system_.loop(); // does LED and checks system health, and syslog service - mqtt_.loop(); // starts mqtt, and sends out anything in the queue rxservice_.loop(); // process what ever is in the rx queue - txservice_.loop(); // check that the Tx is all ok shower_.loop(); // check for shower on/off sensors_.loop(); // this will also send out via MQTT + mqtt_.loop(); // sends out anything in the queue via MQTT console_.loop(); // telnet/serial console // force a query on the EMS devices to fetch latest data at a set interval (1 min) diff --git a/src/emsesp.h b/src/emsesp.h index 9829c002a..e22633f88 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -37,7 +37,6 @@ #include "EMSESPStatusService.h" #include "EMSESPDevicesService.h" #include "EMSESPSettingsService.h" -#include "EMSESPScanDevicesService.h" #include "emsdevice.h" #include "emsfactory.h" @@ -82,6 +81,8 @@ class EMSESP { static void send_raw_telegram(const char * data); static bool device_exists(const uint8_t device_id); + static void device_info(const uint8_t unique_id, JsonObject & root); + static uint8_t count_devices(const uint8_t device_type); static uint8_t actual_master_thermostat(); @@ -95,7 +96,7 @@ class EMSESP { static void add_context_menus(); - static void reset_tx(uint8_t const tx_mode); + static void reset_tx(); static void incoming_telegram(uint8_t * data, const uint8_t length); @@ -146,11 +147,10 @@ class EMSESP { static TxService txservice_; // web controllers - static ESP8266React esp8266React; - static EMSESPSettingsService emsespSettingsService; - static EMSESPStatusService emsespStatusService; - static EMSESPDevicesService emsespDevicesService; - static EMSESPScanDevicesService emsespScanDevicesService; + static ESP8266React esp8266React; + static EMSESPSettingsService emsespSettingsService; + static EMSESPStatusService emsespStatusService; + static EMSESPDevicesService emsespDevicesService; static uuid::log::Logger logger() { return logger_; @@ -182,6 +182,8 @@ class EMSESP { static uint16_t watch_id_; static uint8_t watch_; static bool tap_water_active_; + + static uint8_t unique_id_count_; }; } // namespace emsesp diff --git a/src/emsfactory.h b/src/emsfactory.h index ede08d8fe..50b2d0afe 100644 --- a/src/emsfactory.h +++ b/src/emsfactory.h @@ -89,6 +89,7 @@ class ConcreteEMSFactory : EMSFactory { ConcreteEMSFactory(const uint8_t device_type) { EMSFactory::registerFactory(device_type, this); } + auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand) const -> EMSdevice * { return new DerivedClass(device_type, device_id, product_id, version, name, flags, brand); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index a616ec4c8..a38bc4ec5 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -35,7 +35,7 @@ std::string Mqtt::hostname_; uint8_t Mqtt::mqtt_qos_; uint16_t Mqtt::publish_time_; -std::vector Mqtt::mqtt_functions_; +std::vector Mqtt::mqtt_subfunctions_; uint16_t Mqtt::mqtt_publish_fails_ = 0; size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES; uint16_t Mqtt::mqtt_message_id_ = 0; @@ -51,62 +51,56 @@ Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptrtopic.c_str()) == 0)) { exists = true; } } } if (!exists) { - mqtt_functions_.emplace_back(device_id, std::move(full_topic), cb); // register a call back function for a specific telegram type + mqtt_subfunctions_.emplace_back(device_id, std::move(message->topic), cb); // register a call back function for a specific telegram type } - - queue_subscribe_message(topic); // add subscription to queue } // subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device -void Mqtt::subscribe(const std::string & topic, mqtt_function_p cb) { +void Mqtt::subscribe(const std::string & topic, mqtt_subfunction_p cb) { subscribe(0, topic, cb); // no device_id needed, if generic to EMS-ESP } // resubscribe to all MQTT topics again void Mqtt::resubscribe() { - if (mqtt_functions_.empty()) { + if (mqtt_subfunctions_.empty()) { return; } - for (const auto & mqtt_function : mqtt_functions_) { - queue_subscribe_message(mqtt_function.topic_); + for (const auto & mqtt_subfunction : mqtt_subfunctions_) { + queue_message(Operation::SUBSCRIBE, mqtt_subfunction.topic_, "", false, true); // no payload, no topic prefixing } } @@ -120,6 +114,7 @@ void Mqtt::loop() { } uint32_t currentMillis = uuid::get_uptime(); + // create publish messages for each of the EMS device values, adding to queue if (publish_time_ && (currentMillis - last_publish_ > publish_time_)) { last_publish_ = currentMillis; @@ -146,8 +141,8 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) { // show subscriptions shell.printfln(F("MQTT subscriptions:")); - for (const auto & mqtt_function : mqtt_functions_) { - shell.printfln(F(" %s"), mqtt_function.topic_.c_str()); + for (const auto & mqtt_subfunction : mqtt_subfunctions_) { + shell.printfln(F(" %s"), mqtt_subfunction.topic_.c_str()); } shell.println(); @@ -206,19 +201,11 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) { strlcpy(message, payload, len + 1); LOG_DEBUG(F("[DEBUG] Received %s => %s (length %d)"), topic, message, len); - /* - // strip out everything until the last / - char * topic_magnitude = strrchr(topic, '/'); - if (topic_magnitude != nullptr) { - topic = topic_magnitude + 1; - } - */ - // see if we have this topic in our subscription list, then call its callback handler // note: this will pick the first topic that matches, so for multiple devices of the same type it's gonna fail. Not sure if this is going to be an issue? - for (const auto & mf : mqtt_functions_) { + for (const auto & mf : mqtt_subfunctions_) { if (strcmp(topic, mf.topic_.c_str()) == 0) { - (mf.mqtt_function_)(message); + (mf.mqtt_subfunction_)(message); return; } } @@ -229,15 +216,17 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) { // print all the topics related to a specific device_id void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id) { - if (std::count_if(mqtt_functions_.cbegin(), mqtt_functions_.cend(), [=](MQTTFunction const & mqtt_function) { return device_id == mqtt_function.device_id_; }) + if (std::count_if(mqtt_subfunctions_.cbegin(), + mqtt_subfunctions_.cend(), + [=](MQTTSubFunction const & mqtt_subfunction) { return device_id == mqtt_subfunction.device_id_; }) == 0) { return; } shell.print(F(" Subscribed MQTT topics: ")); - for (const auto & mqtt_function : mqtt_functions_) { - if (mqtt_function.device_id_ == device_id) { - shell.printf(F("%s "), mqtt_function.topic_.c_str()); + for (const auto & mqtt_subfunction : mqtt_subfunctions_) { + if (mqtt_subfunction.device_id_ == device_id) { + shell.printf(F("%s "), mqtt_subfunction.topic_.c_str()); } } shell.println(); @@ -268,24 +257,7 @@ void Mqtt::on_publish(uint16_t packetId) { mqtt_messages_.pop_front(); // always remove from queue, regardless if there was a successful ACK } -// builds up a topic by prefixing the hostname -// unless it's hardcoded like "homeassistant" -char * Mqtt::make_topic(char * result, const std::string & topic) { - // check for homesassistant - if (strncmp(topic.c_str(), "homeassistant/", 13) == 0) { - strlcpy(result, topic.c_str(), MQTT_TOPIC_MAX_SIZE); - return result; - } - - strlcpy(result, hostname_.c_str(), MQTT_TOPIC_MAX_SIZE); - strlcat(result, "/", MQTT_TOPIC_MAX_SIZE); - strlcat(result, topic.c_str(), MQTT_TOPIC_MAX_SIZE); - - return result; -} - void Mqtt::start() { - mqttClient_ = EMSESP::esp8266React.getMqttClient(); // get the hostname, which we'll use to prefix to all topics @@ -298,15 +270,36 @@ void Mqtt::start() { }); mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); }); - mqttClient_->setWill(make_topic(will_topic_, "status"), 1, true, "offline"); // with qos 1, retain true + + mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) { + if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { + LOG_INFO(F("MQTT disconnected: TCP")); + } + if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) { + LOG_INFO(F("MQTT disconnected: Identifier Rejected")); + } + if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) { + LOG_INFO(F("MQTT disconnected: Server unavailable")); + } + if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) { + LOG_INFO(F("MQTT disconnected: Malformed credentials")); + } + if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { + LOG_INFO(F("MQTT disconnected: Not authorized")); + } + }); + + // create will_topic with the hostname prefixed. It has to be static because asyncmqttclient destroys the reference + static char will_topic[MQTT_TOPIC_MAX_SIZE]; + strlcpy(will_topic, hostname_.c_str(), MQTT_TOPIC_MAX_SIZE); + strlcat(will_topic, "/", MQTT_TOPIC_MAX_SIZE); + strlcat(will_topic, "status", MQTT_TOPIC_MAX_SIZE); + mqttClient_->setWill(will_topic, 1, true, "offline"); // with qos 1, retain true + mqttClient_->onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { on_message(topic, payload, len); mqttClient_->onPublish([this](uint16_t packetId) { on_publish(packetId); }); }); - - // add the system MQTT subscriptions - Mqtt::subscribe("cmd", System::mqtt_commands); - // Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, std::placeholders::_1)); } void Mqtt::set_publish_time(uint16_t publish_time) { @@ -334,47 +327,50 @@ void Mqtt::on_connect() { resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics + // add the system MQTT subscriptions, only if its a fresh start with no previous subscriptions + if (mqtt_subfunctions_.empty()) { + Mqtt::subscribe("cmd", System::mqtt_commands); + } + LOG_INFO(F("MQTT connected")); } -// add MQTT message to queue, payload is a string -void Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) { - // can't have bogus topics, but empty payloads are ok +// add sub or pub task to the queue. When the message is created, the topic will have +// automatically the hostname prefixed. +std::shared_ptr +Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix) { if (topic.empty()) { - return; + return nullptr; } - // prefix the hostname to the topic - char full_topic[MQTT_TOPIC_MAX_SIZE]; - make_topic(full_topic, topic); - - auto message = std::make_shared(Operation::PUBLISH, full_topic, payload, retain); + // take the topic and prefix the hostname, unless its for HA + std::shared_ptr message; + if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0) || no_prefix) { + // leave topic as it is + message = std::make_shared(operation, topic, payload, retain); + } else { + // prefix the hostname + std::string full_topic = Mqtt::hostname_ + "/" + topic; + message = std::make_shared(operation, full_topic, payload, retain); + } // if the queue is full, make room but removing the last one if (mqtt_messages_.size() >= maximum_mqtt_messages_) { mqtt_messages_.pop_front(); } - mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message)); + + return mqtt_messages_.back().content_; // this is because the message has been moved +} + +// add MQTT message to queue, payload is a string +std::shared_ptr Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) { + return queue_message(Operation::PUBLISH, topic, payload, retain); } // add MQTT subscribe message to queue -void Mqtt::queue_subscribe_message(const std::string & topic) { - if (topic.empty()) { - return; - } - - auto message = std::make_shared(Operation::SUBSCRIBE, topic, "", false); -#ifdef DEBUG - LOG_DEBUG(F("Adding a subscription for %s"), topic.c_str()); -#endif - - // if the queue is full, make room but removing the last one - if (mqtt_messages_.size() >= maximum_mqtt_messages_) { - mqtt_messages_.pop_front(); - } - - mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message)); +std::shared_ptr Mqtt::queue_subscribe_message(const std::string & topic) { + return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload } // MQTT Publish, using a specific retain flag @@ -383,9 +379,8 @@ void Mqtt::publish(const std::string & topic, const std::string & payload, bool } void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) { - // convert json to string std::string payload_text; - serializeJson(payload, payload_text); + serializeJson(payload, payload_text); // convert json to string queue_publish_message(topic, payload_text, retain); } @@ -413,6 +408,26 @@ void Mqtt::process_queue() { return; } + // show queue - Debug only + /* + Serial.printf("MQTT queue:\n\r"); + for (const auto & message : mqtt_messages_) { + auto content = message.content_; + if (content->operation == Operation::PUBLISH) { + // Publish messages + Serial.printf(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)\n\r", + message.id_, + content->topic.c_str(), + content->payload.c_str(), + message.packet_id_, + message.retry_count_); + } else { + // Subscribe messages + Serial.printf(" [%02d] (Sub) topic=%s\n\r", message.id_, content->topic.c_str()); + } + } + */ + // fetch first from queue and create the full topic name auto mqtt_message = mqtt_messages_.front(); auto message = mqtt_message.content_; diff --git a/src/mqtt.h b/src/mqtt.h index 992286d12..b0ec6b3e8 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -43,11 +43,11 @@ using uuid::console::Shell; namespace emsesp { -using mqtt_function_p = std::function; +using mqtt_subfunction_p = std::function; using namespace std::placeholders; // for `_1` struct MqttMessage { - MqttMessage(uint8_t operation, const std::string & topic, const std::string & payload, bool retain); + MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain); ~MqttMessage() = default; const uint8_t operation; @@ -68,8 +68,8 @@ class Mqtt { static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100; - static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb); - static void subscribe(const std::string & topic, mqtt_function_p cb); + static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb); + static void subscribe(const std::string & topic, mqtt_subfunction_p cb); static void resubscribe(); static void publish(const std::string & topic, const std::string & payload, bool retain = false); @@ -100,6 +100,8 @@ class Mqtt { mqtt_publish_fails_ = 0; } + static std::string hostname_; + private: static uuid::log::Logger logger_; @@ -125,36 +127,36 @@ class Mqtt { static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing - static void queue_publish_message(const std::string & topic, const std::string & payload, const bool retain); - static void queue_subscribe_message(const std::string & topic); + static std::shared_ptr queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix = false); + static std::shared_ptr queue_publish_message(const std::string & topic, const std::string & payload, const bool retain); + static std::shared_ptr queue_subscribe_message(const std::string & topic); void on_publish(uint16_t packetId); void on_message(char * topic, char * payload, size_t len); - static char * make_topic(char * result, const std::string & topic); void process_queue(); void process_all_queue(); static uint16_t mqtt_publish_fails_; - class MQTTFunction { + // function handlers for MQTT subscriptions + class MQTTSubFunction { public: - MQTTFunction(uint8_t device_id, const std::string && topic, mqtt_function_p mqtt_function); - ~MQTTFunction() = default; + MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction); + ~MQTTSubFunction() = default; - uint8_t device_id_; // which device ID owns this - std::string topic_; - mqtt_function_p mqtt_function_; + const uint8_t device_id_; // which device ID owns this + const std::string topic_; + mqtt_subfunction_p mqtt_subfunction_; }; - static std::vector mqtt_functions_; // list of mqtt subscribe callbacks for all devices + static std::vector mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices uint32_t last_mqtt_poll_ = 0; uint32_t last_publish_ = 0; // settings, copied over - static std::string hostname_; - static uint8_t mqtt_qos_; - static uint16_t publish_time_; + static uint8_t mqtt_qos_; + static uint16_t publish_time_; }; } // namespace emsesp diff --git a/src/roomcontrol.cpp b/src/roomcontrol.cpp index 96e801dbb..5db25621c 100644 --- a/src/roomcontrol.cpp +++ b/src/roomcontrol.cpp @@ -65,6 +65,10 @@ void Roomctrl::check(const uint8_t addr, const uint8_t * data) { if (hc_ > 3) { return; } + // no reply if the temperature is not set + if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) { + return; + } // reply to writes with write nack byte if (addr & 0x80) { // it's a write to us nack_write(); // we don't accept writes. diff --git a/src/sensors.cpp b/src/sensors.cpp index c09b71b05..26a0a1223 100644 --- a/src/sensors.cpp +++ b/src/sensors.cpp @@ -23,6 +23,12 @@ MAKE_PSTR(logger_name, "sensors") +#ifdef ESP32 +#define YIELD +#else +#define YIELD yield() +#endif + namespace emsesp { uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON}; @@ -63,10 +69,9 @@ void Sensors::loop() { if (time_now - last_activity_ >= READ_INTERVAL_MS) { // LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug if (bus_.reset()) { - yield(); + YIELD; bus_.skip(); bus_.write(CMD_CONVERT_TEMP); - state_ = State::READING; } else { // no sensors found @@ -80,20 +85,15 @@ void Sensors::loop() { // LOG_DEBUG(F("Scanning for sensors")); // uncomment for debug bus_.reset_search(); found_.clear(); - - state_ = State::SCANNING; - last_activity_ = time_now; + state_ = State::SCANNING; } else if (time_now - last_activity_ > READ_TIMEOUT_MS) { LOG_ERROR(F("Sensor read timeout")); - - state_ = State::IDLE; - last_activity_ = time_now; + state_ = State::IDLE; } } else if (state_ == State::SCANNING) { if (time_now - last_activity_ > SCAN_TIMEOUT_MS) { LOG_ERROR(F("Sensor scan timeout")); - state_ = State::IDLE; - last_activity_ = time_now; + state_ = State::IDLE; } else { uint8_t addr[ADDR_LEN] = {0}; @@ -107,7 +107,7 @@ void Sensors::loop() { case TYPE_DS1822: case TYPE_DS1825: found_.emplace_back(addr); - found_.back().temperature_c_ = get_temperature_c(addr); + found_.back().temperature_c = get_temperature_c(addr); /* // comment out for debugging @@ -127,11 +127,15 @@ void Sensors::loop() { } } else { bus_.depower(); - devices_ = std::move(found_); + if ((found_.size() >= devices_.size()) || (retrycnt_ > 5)) { + devices_ = std::move(found_); + retrycnt_ = 0; + } else { + retrycnt_++; + } found_.clear(); // LOG_DEBUG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug - state_ = State::IDLE; - last_activity_ = time_now; + state_ = State::IDLE; } } } @@ -155,17 +159,17 @@ float Sensors::get_temperature_c(const uint8_t addr[]) { LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str()); return NAN; } - yield(); + YIELD; uint8_t scratchpad[SCRATCHPAD_LEN] = {0}; bus_.select(addr); bus_.write(CMD_READ_SCRATCHPAD); bus_.read_bytes(scratchpad, SCRATCHPAD_LEN); - yield(); + YIELD; if (!bus_.reset()) { LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str()); return NAN; } - yield(); + YIELD; if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) { LOG_WARNING(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"), scratchpad[0], @@ -202,7 +206,8 @@ float Sensors::get_temperature_c(const uint8_t addr[]) { break; } - return (float)raw_value / 16; + uint32_t raw = (raw_value *625) / 100; // round to 0.01 + return (float)raw / 100; #else return NAN; #endif @@ -253,7 +258,7 @@ void Sensors::publish_values() { StaticJsonDocument<100> doc; for (const auto & device : devices_) { char s[5]; - doc["temp"] = Helpers::render_value(s, device.temperature_c_, 2); + doc["temp"] = Helpers::render_value(s, device.temperature_c, 2); char topic[60]; // sensors{1-n} strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F strlcat(topic, device.to_string().c_str(), 60); @@ -279,7 +284,7 @@ void Sensors::publish_values() { for (const auto & device : devices_) { if (mqtt_format_ == MQTT_format::CUSTOM) { char s[5]; - doc[device.to_string()] = Helpers::render_value(s, device.temperature_c_, 2); + doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 2); } else { char sensorID[10]; // sensor{1-n} strlcpy(sensorID, "sensor", 10); @@ -287,7 +292,7 @@ void Sensors::publish_values() { strlcat(sensorID, Helpers::itoa(s, i++), 10); JsonObject dataSensor = doc.createNestedObject(sensorID); dataSensor["id"] = device.to_string(); - dataSensor["temp"] = Helpers::render_value(s, device.temperature_c_, 2); + dataSensor["temp"] = Helpers::render_value(s, device.temperature_c, 2); } } diff --git a/src/sensors.h b/src/sensors.h index 4601224b0..7ec37d6d3 100644 --- a/src/sensors.h +++ b/src/sensors.h @@ -46,7 +46,7 @@ class Sensors { uint64_t id() const; std::string to_string() const; - float temperature_c_ = NAN; + float temperature_c = NAN; private: const uint64_t id_; @@ -90,7 +90,7 @@ class Sensors { static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds static constexpr uint32_t READ_TIMEOUT_MS = 2000; // 2 seconds - static constexpr uint32_t SCAN_TIMEOUT_MS = 30000; // 30 seconds + static constexpr uint32_t SCAN_TIMEOUT_MS = 3000; // 3 seconds static constexpr uint8_t CMD_CONVERT_TEMP = 0x44; static constexpr uint8_t CMD_READ_SCRATCHPAD = 0xBE; @@ -111,6 +111,8 @@ class Sensors { std::vector devices_; uint8_t mqtt_format_; + uint8_t retrycnt_ = 0; + }; } // namespace emsesp diff --git a/src/system.cpp b/src/system.cpp index b21db535f..9bac30722 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -24,8 +24,10 @@ MAKE_PSTR_WORD(passwd) MAKE_PSTR_WORD(hostname) MAKE_PSTR_WORD(wifi) +MAKE_PSTR_WORD(reconnect) MAKE_PSTR_WORD(ssid) MAKE_PSTR_WORD(heartbeat) +MAKE_PSTR_WORD(users) MAKE_PSTR(host_fmt, "Host = %s") MAKE_PSTR(hostname_fmt, "WiFi Hostname = %s") @@ -141,7 +143,6 @@ void System::mqtt_commands(const char * message) { // restart EMS-ESP void System::restart() { LOG_NOTICE("Restarting system..."); - Shell::loop_all(); delay(1000); // wait a second #if defined(ESP8266) @@ -151,10 +152,19 @@ void System::restart() { #endif } +// saves all settings +void System::wifi_reconnect() { + LOG_NOTICE("The wifi will reconnect..."); + Shell::loop_all(); + delay(1000); // wait a second + EMSESP::emsespSettingsService.save(); // local settings + EMSESP::esp8266React.getWiFiSettingsService()->callUpdateHandlers("local"); // in case we've changed ssid or password +} + // format fs // format the FS. Wipes everything. void System::format(uuid::console::Shell & shell) { - auto msg = F("Formatting file system. This will also reset all settings to their defaults"); + auto msg = F("Formatting file system. This will reset all settings to their defaults"); shell.logger().warning(msg); shell.flush(); @@ -189,19 +199,17 @@ void System::syslog_init() { }); #ifndef EMSESP_STANDALONE - syslog_.start(); // syslog service + syslog_.start(); // syslog service re-start // configure syslog IPAddress addr; - if (!addr.fromString(syslog_host_.c_str())) { addr = (uint32_t)0; } - - EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { syslog_.hostname(wifiSettings.hostname.c_str()); }); syslog_.log_level((uuid::log::Level)syslog_level_); syslog_.mark_interval(syslog_mark_interval_); syslog_.destination(addr); + EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { syslog_.hostname(wifiSettings.hostname.c_str()); }); #endif } @@ -220,32 +228,32 @@ void System::start() { #endif } - // fetch settings - EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode_ = settings.tx_mode; }); + // fetch system heartbeat EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat_ = settings.system_heartbeat; }); - syslog_init(); // init SysLog + // print boot message + EMSESP::esp8266React.getWiFiSettingsService()->read( + [&](WiFiSettings & wifiSettings) { LOG_INFO(F("System %s booted (EMS-ESP version %s)"), wifiSettings.hostname.c_str(), EMSESP_APP_VERSION); }); -#if defined(ESP32) - LOG_INFO(F("System booted (EMS-ESP version %s ESP32)"), EMSESP_APP_VERSION); -#else - LOG_INFO(F("System booted (EMS-ESP version %s)"), EMSESP_APP_VERSION); -#endif + syslog_init(); // init SysLog if (LED_GPIO) { pinMode(LED_GPIO, OUTPUT); // LED pin, 0 means disabled } + EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode_ = settings.tx_mode; }); #ifndef EMSESP_FORCE_SERIAL - if (tx_mode_) { - EMSuart::start(tx_mode_); // start UART, if tx_mode is not 0 - } + EMSuart::start(tx_mode_); // start UART #endif } // returns true if OTA is uploading bool System::upload_status() { +#if defined(EMSESP_STANDALONE) + return false; +#else return upload_status_ || Update.isRunning(); +#endif } void System::upload_status(bool in_progress) { @@ -372,10 +380,23 @@ int8_t System::wifi_quality() { return 2 * (dBm + 100); } -void System::show_system(uuid::console::Shell & shell) { - shell.print(F("Uptime: ")); - shell.print(uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3)); +// print users to console +void System::show_users(uuid::console::Shell & shell) { + shell.printfln(F("Users:")); + +#ifndef EMSESP_STANDALONE + EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) { + for (User user : securitySettings.users) { + shell.printfln(F(" username: %s, password: %s, is_admin: %s"), user.username.c_str(), user.password.c_str(), user.admin ? F("yes") : F("no")); + } + }); +#endif + shell.println(); +} + +void System::show_system(uuid::console::Shell & shell) { + shell.printfln(F("Uptime: %s"), uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3).c_str()); #if defined(ESP8266) shell.printfln(F("Chip ID: 0x%08x"), ESP.getChipId()); @@ -389,7 +410,7 @@ void System::show_system(uuid::console::Shell & shell) { shell.printfln(F("Sketch size: %u bytes (%u bytes free)"), ESP.getSketchSize(), ESP.getFreeSketchSpace()); shell.printfln(F("Reset reason: %s"), ESP.getResetReason().c_str()); shell.printfln(F("Reset info: %s"), ESP.getResetInfo().c_str()); - + shell.println(); shell.printfln(F("Free heap: %lu bytes"), (unsigned long)ESP.getFreeHeap()); shell.printfln(F("Free mem: %d %%"), free_mem()); shell.printfln(F("Maximum free block size: %lu bytes"), (unsigned long)ESP.getMaxFreeBlockSize()); @@ -408,21 +429,19 @@ void System::show_system(uuid::console::Shell & shell) { #ifndef EMSESP_STANDALONE switch (WiFi.status()) { case WL_IDLE_STATUS: - shell.printfln(F("WiFi: idle")); + shell.printfln(F("WiFi: Idle")); break; case WL_NO_SSID_AVAIL: - shell.printfln(F("WiFi: network not found")); + shell.printfln(F("WiFi: Network not found")); break; case WL_SCAN_COMPLETED: - shell.printfln(F("WiFi: network scan complete")); + shell.printfln(F("WiFi: Network scan complete")); break; case WL_CONNECTED: { - shell.printfln(F("WiFi: connected")); - shell.println(); - + shell.printfln(F("WiFi: Connected")); shell.printfln(F("SSID: %s"), WiFi.SSID().c_str()); shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str()); shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality()); @@ -432,27 +451,26 @@ void System::show_system(uuid::console::Shell & shell) { #elif defined(ESP32) shell.printfln(F("Hostname: %s"), WiFi.getHostname()); #endif - shell.println(); shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str()); shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(WiFi.gatewayIP()).c_str()); shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(WiFi.dnsIP()).c_str()); } break; case WL_CONNECT_FAILED: - shell.printfln(F("WiFi: connection failed")); + shell.printfln(F("WiFi: Connection failed")); break; case WL_CONNECTION_LOST: - shell.printfln(F("WiFi: connection lost")); + shell.printfln(F("WiFi: Connection lost")); break; case WL_DISCONNECTED: - shell.printfln(F("WiFi: disconnected")); + shell.printfln(F("WiFi: Disconnected")); break; case WL_NO_SHIELD: default: - shell.printfln(F("WiFi: unknown")); + shell.printfln(F("WiFi: Unknown")); break; } @@ -479,6 +497,13 @@ void System::console_commands(Shell & shell, unsigned int context) { restart(); }); + EMSESPShell::commands->add_command(ShellContext::SYSTEM, + CommandFlags::ADMIN, + flash_string_vector{F_(wifi), F_(reconnect)}, + [](Shell & shell __attribute__((unused)), const std::vector & arguments __attribute__((unused))) { + wifi_reconnect(); + }); + EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(format)}, @@ -535,8 +560,9 @@ void System::console_commands(Shell & shell, unsigned int context) { flash_string_vector{F_(set), F_(wifi), F_(hostname)}, flash_string_vector{F_(name_mandatory)}, [](Shell & shell __attribute__((unused)), const std::vector & arguments) { - shell.println("Note, connection will be reset..."); - Console::loop(); + shell.println("The wifi connection will be reset..."); + Shell::loop_all(); + delay(1000); // wait a second EMSESP::esp8266React.getWiFiSettingsService()->update( [&](WiFiSettings & wifiSettings) { wifiSettings.hostname = arguments.front().c_str(); @@ -550,14 +576,11 @@ void System::console_commands(Shell & shell, unsigned int context) { flash_string_vector{F_(set), F_(wifi), F_(ssid)}, flash_string_vector{F_(name_mandatory)}, [](Shell & shell, const std::vector & arguments) { - shell.println("Note, connection will be reset..."); - Console::loop(); - EMSESP::esp8266React.getWiFiSettingsService()->update( - [&](WiFiSettings & wifiSettings) { - wifiSettings.ssid = arguments.front().c_str(); - return StateUpdateResult::CHANGED; - }, - "local"); + EMSESP::esp8266React.getWiFiSettingsService()->updateWithoutPropagation([&](WiFiSettings & wifiSettings) { + wifiSettings.ssid = arguments.front().c_str(); + return StateUpdateResult::CHANGED; + }); + shell.println("Use `wifi reconnect` to apply the new settings"); }); EMSESPShell::commands->add_command(ShellContext::SYSTEM, @@ -570,13 +593,12 @@ void System::console_commands(Shell & shell, unsigned int context) { [password1](Shell & shell, bool completed, const std::string & password2) { if (completed) { if (password1 == password2) { - EMSESP::esp8266React.getWiFiSettingsService()->update( + EMSESP::esp8266React.getWiFiSettingsService()->updateWithoutPropagation( [&](WiFiSettings & wifiSettings) { wifiSettings.password = password2.c_str(); return StateUpdateResult::CHANGED; - }, - "local"); - shell.println(F("WiFi password updated")); + }); + shell.println("Use `wifi reconnect` to apply the new settings"); } else { shell.println(F("Passwords do not match")); } @@ -612,6 +634,11 @@ void System::console_commands(Shell & shell, unsigned int context) { flash_string_vector{F_(show), F_(mqtt)}, [](Shell & shell, const std::vector & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); }); + EMSESPShell::commands->add_command(ShellContext::SYSTEM, + CommandFlags::ADMIN, + flash_string_vector{F_(show), F_(users)}, + [](Shell & shell, const std::vector & arguments __attribute__((unused))) { System::show_users(shell); }); + // enter the context Console::enter_custom_context(shell, context); @@ -621,8 +648,7 @@ void System::console_commands(Shell & shell, unsigned int context) { void System::check_upgrade() { // check for v1.9. It uses SPIFFS and only on the ESP8266 #if defined(ESP8266) - - Serial.begin(115200); // TODO remove + Serial.begin(115200); // TODO remove, just for debugging #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" diff --git a/src/system.h b/src/system.h index 3b3cd0d52..e46965017 100644 --- a/src/system.h +++ b/src/system.h @@ -50,8 +50,8 @@ class System { static void mqtt_commands(const char * message); static uint8_t free_mem(); - static void upload_status(bool in_progress); - static bool upload_status(); + static void upload_status(bool in_progress); + static bool upload_status(); void syslog_init(); @@ -97,6 +97,8 @@ class System { void system_check(); static void show_system(uuid::console::Shell & shell); + static void show_users(uuid::console::Shell & shell); + static void wifi_reconnect(); static int8_t wifi_quality(); bool system_healthy_ = false; diff --git a/src/telegram.cpp b/src/telegram.cpp index 22ebe694b..97e2a41ce 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -42,8 +42,8 @@ uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time bool EMSbus::bus_connected_ = false; // start assuming the bus hasn't been connected uint8_t EMSbus::ems_mask_ = EMS_MASK_UNSET; // unset so its triggered when booting, the its 0x00=buderus, 0x80=junker/ht3 uint8_t EMSbus::ems_bus_id_ = EMSESP_DEFAULT_EMS_BUS_ID; -uint8_t EMSbus::tx_waiting_ = Telegram::Operation::NONE; -bool EMSbus::tx_active_ = false; +uint8_t EMSbus::tx_mode_ = EMSESP_DEFAULT_TX_MODE; +uint8_t EMSbus::tx_state_ = Telegram::Operation::NONE; uuid::log::Logger EMSbus::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; @@ -82,9 +82,10 @@ Telegram::Telegram(const uint8_t operation, // returns telegram's message data bytes in hex std::string Telegram::to_string() const { - if (message_length == 0) { + if (this->message_length == 0) { return read_flash_string(F("")); } + uint8_t data[EMS_MAX_TELEGRAM_LENGTH]; uint8_t length = 0; data[0] = this->src ^ RxService::ems_mask(); @@ -100,8 +101,7 @@ std::string Telegram::to_string() const { data[2] = this->type_id; length = 5; } - } - if (this->operation == Telegram::Operation::TX_WRITE) { + } else if (this->operation == Telegram::Operation::TX_WRITE) { data[1] = this->dest; if (this->type_id > 0xFF) { data[2] = 0xFF; @@ -115,7 +115,12 @@ std::string Telegram::to_string() const { for (uint8_t i = 0; i < this->message_length; i++) { data[length++] = this->message_data[i]; } + } else { + for (uint8_t i = 0; i < this->message_length; i++) { + data[length++] = this->message_data[i]; + } } + return Helpers::data_to_hex(data, length); } @@ -160,17 +165,18 @@ void RxService::add(uint8_t * data, uint8_t length) { // validate the CRC uint8_t crc = calculate_crc(data, length - 1); - - if ((data[length - 1] != crc) && (EMSESP::watch() != EMSESP::Watch::WATCH_OFF)) { - LOG_ERROR(F("Rx: %s %s(CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET); + if (data[length - 1] != crc) { increment_telegram_error_count(); + if (EMSESP::watch() != EMSESP::Watch::WATCH_OFF) { + LOG_ERROR(F("Rx: %s %s(CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET); + } return; } // since it's a valid telegram, work out the ems mask // we check the 1st byte, which assumed is the src ID and see if the MSB (8th bit) is set // this is used to identify if the protocol should be Junkers/HT3 or Buderus - // this only happens once with the first rx telegram is processed + // this only happens once with the first valid rx telegram is processed if (ems_mask() == EMS_MASK_UNSET) { ems_mask(data[0]); } @@ -251,48 +257,51 @@ void TxService::flush_tx_queue() { // start and initialize Tx void TxService::start() { - // grab the bus ID - EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { ems_bus_id(settings.ems_bus_id); }); + // grab the bus ID and tx_mode + EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { + ems_bus_id(settings.ems_bus_id); + tx_mode(settings.tx_mode); + }); + + // reset counters + telegram_read_count(0); + telegram_write_count(0); + telegram_fail_count(0); // send first Tx request to bus master (boiler) for its registered devices // this will be added to the queue and sent during the first tx loop() read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER); } -// Tx loop -// here we check if the Tx is not full and report an error -void TxService::loop() { -#ifndef EMSESP_STANDALONE - if ((uuid::get_uptime() - last_tx_check_) > TX_LOOP_WAIT) { - last_tx_check_ = uuid::get_uptime(); - if (!tx_active() && (EMSbus::bus_connected())) { - LOG_ERROR(F("Tx is not active. Please check settings and the circuit connection.")); - } - } -#endif -} - // sends a 1 byte poll which is our own device ID void TxService::send_poll() { //LOG_DEBUG(F("Ack %02X"),ems_bus_id() ^ ems_mask()); - EMSuart::send_poll(ems_bus_id() ^ ems_mask()); + if (tx_mode()) { + EMSuart::send_poll(ems_bus_id() ^ ems_mask()); + } } // Process the next telegram on the Tx queue // This is sent when we receieve a poll request void TxService::send() { // don't process if we don't have a connection to the EMS bus - // or we're in read-only mode if (!bus_connected()) { return; } // if there's nothing in the queue to transmit, send back a poll and quit + // unless tx_mode is 0 if (tx_telegrams_.empty()) { send_poll(); return; } + // if we're in read-only mode (tx_mode 0) forget the Tx call + if (tx_mode() == 0) { + tx_telegrams_.pop_front(); + return; + } + // send next telegram in the queue (which is actually a list!) send_telegram(tx_telegrams_.front()); @@ -380,12 +389,12 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) { if (status == EMS_TX_STATUS_ERR) { LOG_ERROR(F("Failed to transmit Tx via UART.")); - increment_telegram_fail_count(); // another Tx fail - tx_waiting(Telegram::Operation::NONE); // nothing send, tx not in wait state + increment_telegram_fail_count(); // another Tx fail + tx_state(Telegram::Operation::NONE); // nothing send, tx not in wait state return; } - tx_waiting(telegram->operation); // tx now in a wait state + tx_state(telegram->operation); // tx now in a wait state } // send an array of bytes as a telegram @@ -399,7 +408,7 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) { } telegram_raw[length] = calculate_crc(telegram_raw, length); // apppend CRC - tx_waiting(Telegram::Operation::NONE); // no post validation needed + tx_state(Telegram::Operation::NONE); // no post validation needed // send the telegram to the UART Tx uint16_t status = EMSuart::transmit(telegram_raw, length); diff --git a/src/telegram.h b/src/telegram.h index c7a696612..14f762a12 100644 --- a/src/telegram.h +++ b/src/telegram.h @@ -126,11 +126,38 @@ class EMSbus { public: static uuid::log::Logger logger_; - static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet - static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3 - static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus + static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet + static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3 + static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus + static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning - static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning + static bool is_ht3() { + return (ems_mask_ == EMS_MASK_HT3); + } + + static uint8_t ems_mask() { + return ems_mask_; + } + + static void ems_mask(uint8_t ems_mask) { + ems_mask_ = ems_mask & 0x80; // only keep the MSB (8th bit) + } + + static uint8_t tx_mode() { + return tx_mode_; + } + + static void tx_mode(uint8_t tx_mode) { + tx_mode_ = tx_mode; + } + + static uint8_t ems_bus_id() { + return ems_bus_id_; + } + + static void ems_bus_id(uint8_t ems_bus_id) { + ems_bus_id_ = ems_bus_id; + } static bool bus_connected() { #ifndef EMSESP_STANDALONE @@ -143,53 +170,17 @@ class EMSbus { #endif } - static bool is_ht3() { - return (ems_mask_ == EMS_MASK_HT3); - } - - static uint8_t protocol() { - return ems_mask_; - } - - static uint8_t ems_mask() { - return ems_mask_; - } - - static void ems_mask(uint8_t ems_mask) { - ems_mask_ = ems_mask & 0x80; // only keep the MSB (8th bit) - } - - static uint8_t ems_bus_id() { - return ems_bus_id_; - } - - static void ems_bus_id(uint8_t ems_bus_id) { - ems_bus_id_ = ems_bus_id; - } - // sets the flag for EMS bus connected static void last_bus_activity(uint32_t timestamp) { last_bus_activity_ = timestamp; bus_connected_ = true; } - static bool tx_active() { - return tx_active_; + static uint8_t tx_state() { + return tx_state_; } - static void tx_active(bool tx_active) { - tx_active_ = tx_active; - } - - static uint8_t tx_waiting() { - return tx_waiting_; - } - static void tx_waiting(uint8_t tx_waiting) { - tx_waiting_ = tx_waiting; - - // if NONE, then it's been reset which means we have an active Tx - if ((tx_waiting == Telegram::Operation::NONE) && !(tx_active_)) { - tx_active_ = true; - } + static void tx_state(uint8_t tx_state) { + tx_state_ = tx_state; } static uint8_t calculate_crc(const uint8_t * data, const uint8_t length); @@ -201,8 +192,8 @@ class EMSbus { static bool bus_connected_; // start assuming the bus hasn't been connected static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80 static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings - static uint8_t tx_waiting_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE) - static bool tx_active_; // is true is we have a working Tx connection + static uint8_t tx_mode_; // local copy of the tx mode + static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE) }; class RxService : public EMSbus { @@ -269,7 +260,6 @@ class TxService : public EMSbus { ~TxService() = default; void start(); - void loop(); void send(); void add(const uint8_t operation, @@ -364,8 +354,7 @@ class TxService : public EMSbus { private: uint8_t tx_telegram_id_ = 0; // queue counter - static constexpr uint32_t TX_LOOP_WAIT = 10000; // when to check if Tx is up and running (10 sec) - uint32_t last_tx_check_ = 0; + uint32_t last_tx_check_ = 0; std::deque tx_telegrams_; diff --git a/src/test/test.cpp b/src/test/test.cpp index 03a1de80c..440245cad 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -17,6 +17,7 @@ * along with this program. If not, see . */ +#if defined(EMSESP_STANDALONE) #include "test.h" @@ -692,3 +693,5 @@ void Test::dummy_mqtt_commands(const char * message) { #pragma GCC diagnostic pop } // namespace emsesp + +#endif diff --git a/src/test/test.h b/src/test/test.h index 300ec8796..c45fe42d4 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +#if defined(EMSESP_STANDALONE) + #ifndef EMSESP_TEST_H #define EMSESP_TEST_H @@ -51,3 +53,5 @@ class Test { } // namespace emsesp #endif + +#endif diff --git a/src/uart/emsuart_esp32.cpp b/src/uart/emsuart_esp32.cpp index b827d2328..db9381136 100644 --- a/src/uart/emsuart_esp32.cpp +++ b/src/uart/emsuart_esp32.cpp @@ -34,8 +34,8 @@ static hw_timer_t * timer = NULL; bool drop_next_rx = true; uint8_t tx_mode_ = 0xFF; uint8_t emsTxBuf[EMS_MAXBUFFERSIZE]; -uint8_t emsTxBufIdx; -uint8_t emsTxBufLen; +uint8_t emsTxBufIdx = 0; +uint8_t emsTxBufLen = 0; uint32_t emsTxWait; /* @@ -88,12 +88,15 @@ void IRAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() { portENTER_CRITICAL(&mux); if (emsTxBufIdx < emsTxBufLen) { EMS_UART.fifo.rw_byte = emsTxBuf[emsTxBufIdx]; + if (emsTxBufIdx == 1) { + timerAlarmWrite(timer, emsTxWait, true); + } } else if (emsTxBufIdx == emsTxBufLen) { EMS_UART.conf0.txd_inv = 1; - timerAlarmWrite(timer, EMSUART_TX_WAIT_BRK, true); + timerAlarmWrite(timer, EMSUART_TX_BRK_TIMER, true); } else if (emsTxBufIdx == emsTxBufLen + 1) { - // delayMicroseconds(EMSUART_TX_WAIT_BRK); EMS_UART.conf0.txd_inv = 0; + emsTxBufLen = 0; timerAlarmDisable(timer); } emsTxBufIdx++; @@ -122,28 +125,31 @@ void EMSuart::start(const uint8_t tx_mode) { uart_set_pin(EMSUART_UART, EMSUART_TXPIN, EMSUART_RXPIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); EMS_UART.int_ena.val = 0; // disable all intr. EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags - EMS_UART.idle_conf.tx_brk_num = 11; // breaklength 11 bit + EMS_UART.idle_conf.tx_brk_num = 10; // breaklength 10 bit EMS_UART.idle_conf.rx_idle_thrhd = 256; drop_next_rx = true; buf_handle = xRingbufferCreate(128, RINGBUF_TYPE_NOSPLIT); uart_isr_register(EMSUART_UART, emsuart_rx_intr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, configMAX_PRIORITIES - 1, NULL); - timer = timerBegin(1, 80, true); // timer prescale to 1 µs, countup + timer = timerBegin(0, 80, true); // timer prescale to 1 us, countup timerAttachInterrupt(timer, &emsuart_tx_timer_intr_handler, true); // Timer with edge interrupt restart(); } /* - * Stop, disables interrupt + * Stop, disable interrupt */ void EMSuart::stop() { - EMS_UART.int_ena.val = 0; // disable all intr. - // timerAlarmDisable(timer); + EMS_UART.int_ena.val = 0; // disable all intr. + EMS_UART.conf0.txd_inv = 0; // stop break + if (emsTxBufLen > 0) { + timerAlarmDisable(timer); + } }; /* - * Restart Interrupt + * Restart uart and make mode dependent configs. */ void EMSuart::restart() { if (EMS_UART.int_raw.brk_det) { // we received a break in the meantime @@ -153,7 +159,11 @@ void EMSuart::restart() { EMS_UART.int_ena.brk_det = 1; // activate only break emsTxBufIdx = 0; emsTxBufLen = 0; - emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10); + if (tx_mode_ > 100) { + emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ - 90); + } else { + emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10); + } if(tx_mode_ == EMS_TXMODE_NEW) { EMS_UART.conf0.txd_brk = 1; } else { @@ -161,44 +171,11 @@ void EMSuart::restart() { } } -/* - * Sends a 11-bit break by inverting the tx-port - */ -void EMSuart::tx_brk() { - EMS_UART.conf0.txd_inv = 1; - delayMicroseconds(EMSUART_TX_WAIT_BRK); - EMS_UART.conf0.txd_inv = 0; -} - /* * Sends a 1-byte poll, ending with a */ void EMSuart::send_poll(const uint8_t data) { - if (tx_mode_ > 5) { // timer controlled modes - emsTxBuf[0] = data; - emsTxBufIdx = 0; - emsTxBufLen = 1; - timerAlarmWrite(timer, emsTxWait, true); // start timer with autoreload - timerAlarmEnable(timer); // first interrupt comes immediately - } else if (tx_mode_ == EMS_TXMODE_DEFAULT) { - volatile uint8_t _usrxc = EMS_UART.status.rxfifo_cnt; - uint16_t timeoutcnt = EMSUART_TX_TIMEOUT; - EMS_UART.fifo.rw_byte = data; - while ((EMS_UART.status.rxfifo_cnt == _usrxc) && (--timeoutcnt > 0)) { - delayMicroseconds(EMSUART_TX_BUSY_WAIT); - } - tx_brk(); - } else if (tx_mode_ == EMS_TXMODE_EMSPLUS) { - EMS_UART.fifo.rw_byte = data; - delayMicroseconds(EMSUART_TX_WAIT_PLUS); - tx_brk(); - } else if (tx_mode_ == EMS_TXMODE_HT3) { - EMS_UART.fifo.rw_byte = data; - delayMicroseconds(EMSUART_TX_WAIT_HT3); - tx_brk(); - } else { - EMS_UART.fifo.rw_byte = data; - } + transmit(&data, 1); } /* @@ -217,7 +194,11 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) { } emsTxBufIdx = 0; emsTxBufLen = len; - timerAlarmWrite(timer, emsTxWait, true); // start with autoreload + if (tx_mode_ > 100 && len > 1) { + timerAlarmWrite(timer, EMSUART_TX_WAIT_REPLY, true); + } else { + timerAlarmWrite(timer, emsTxWait, true); // start with autoreload + } timerAlarmEnable(timer); return EMS_TX_STATUS_OK; } @@ -234,7 +215,9 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) { EMS_UART.fifo.rw_byte = buf[i]; delayMicroseconds(EMSUART_TX_WAIT_PLUS); } - tx_brk(); + EMS_UART.conf0.txd_inv = 1; // send + delayMicroseconds(EMSUART_TX_BRK_PLUS); + EMS_UART.conf0.txd_inv = 0; return EMS_TX_STATUS_OK; } @@ -243,11 +226,13 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) { EMS_UART.fifo.rw_byte = buf[i]; delayMicroseconds(EMSUART_TX_WAIT_HT3); } - tx_brk(); + EMS_UART.conf0.txd_inv = 1; // send + delayMicroseconds(EMSUART_TX_BRK_HT3); + EMS_UART.conf0.txd_inv = 0; return EMS_TX_STATUS_OK; } - // mode 1 + // mode 1: wait for echo after each byte // flush fifos -- not supported in ESP32 uart #2! // EMS_UART.conf0.rxfifo_rst = 1; // EMS_UART.conf0.txfifo_rst = 1; @@ -259,7 +244,9 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) { delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles... } } - tx_brk(); + EMS_UART.conf0.txd_inv = 1; + delayMicroseconds(EMSUART_TX_BRK_EMS); + EMS_UART.conf0.txd_inv = 0; return EMS_TX_STATUS_OK; } diff --git a/src/uart/emsuart_esp32.h b/src/uart/emsuart_esp32.h index 043a0ae4c..df29566b4 100644 --- a/src/uart/emsuart_esp32.h +++ b/src/uart/emsuart_esp32.h @@ -46,19 +46,25 @@ #define EMS_TXMODE_NEW 4 // for michael's testing // LEGACY -#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud -#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144 +#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud + +// Timer controlled modes +#define EMSUART_TX_BRK_TIMER (EMSUART_TX_BIT_TIME * 10 + 28) // 10.25 bit times +#define EMSUART_TX_WAIT_REPLY 100000 // delay 100ms after first byte // EMS 1.0 #define EMSUART_TX_BUSY_WAIT (EMSUART_TX_BIT_TIME / 8) // 13 -#define EMSUART_TX_TIMEOUT (32 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT) // 256 +#define EMSUART_TX_TIMEOUT (20 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT) +#define EMSUART_TX_BRK_EMS (EMSUART_TX_BIT_TIME * 10) // HT3/Junkers - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) plus 7 bit delay. The -8 is for lag compensation. // since we use a faster processor the lag is negligible #define EMSUART_TX_WAIT_HT3 (EMSUART_TX_BIT_TIME * 17) // 1768 +#define EMSUART_TX_BRK_HT3 (EMSUART_TX_BIT_TIME * 11) // EMS+ - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) and delay of another Bytetime. #define EMSUART_TX_WAIT_PLUS (EMSUART_TX_BIT_TIME * 20) // 2080 +#define EMSUART_TX_BRK_PLUS (EMSUART_TX_BIT_TIME * 11) // customize the GPIO pins for RX and TX here @@ -90,10 +96,9 @@ class EMSuart { static void emsuart_recvTask(void * para); static void IRAM_ATTR emsuart_rx_intr_handler(void * para); static void IRAM_ATTR emsuart_tx_timer_intr_handler(); - static void tx_brk(); }; } // namespace emsesp #endif -#endif +#endif \ No newline at end of file diff --git a/src/uart/emsuart_esp8266.cpp b/src/uart/emsuart_esp8266.cpp index dfc0f758f..3816fed86 100644 --- a/src/uart/emsuart_esp8266.cpp +++ b/src/uart/emsuart_esp8266.cpp @@ -95,10 +95,10 @@ void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() { timer1_write(emsTxWait); } else if (emsTxBufIdx == emsTxBufLen) { USC0(EMSUART_UART) |= (1 << UCBRK); // set - timer1_write(EMSUART_TX_WAIT_BRK * 5); + timer1_write(EMSUART_TX_BRK_TIMER); } else { USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset - sending_ = false; + sending_ = false; } } @@ -114,7 +114,6 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_flush_fifos() { * init UART0 driver */ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) { - emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode + 10); // bittimes wait between bytes if (tx_mode_ != 0xFF) { // it's a restart no need to configure uart tx_mode_ = tx_mode; restart(); @@ -129,7 +128,6 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) { } pEMSRxBuf = paEMSRxBuf[0]; // reset EMS Rx Buffer - ETS_UART_INTR_DISABLE(); ETS_UART_INTR_ATTACH(nullptr, nullptr); // pin settings @@ -149,22 +147,14 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) { // UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127) // see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf // - // change: we set UCFFT to 1 to get an immediate indicator about incoming traffic. - // Otherwise, we're only noticed by UCTOT or RxBRK! // change: don't care, we do not use these interrupts - USC1(EMSUART_UART) = 0; // reset config first + USC1(EMSUART_UART) = 0; // reset config // USC1(EMSUART_UART) = (0x7F << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts // set interrupts for triggers USIC(EMSUART_UART) = 0xFFFF; // clear all interupts USIE(EMSUART_UART) = 0; // disable all interrupts - // enable rx break, fifo full and timeout. - // but not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes - // change: we don't care about Rx Timeout - it may lead to wrong readouts - // change:we don't care about Fifo full and read only on break-detect - USIE(EMSUART_UART) = (1 << UIBD) | (0 << UIFF) | (0 << UITO); - // set up interrupt callbacks for Rx system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen); @@ -175,13 +165,12 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) { system_uart_swap(); ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr); - // ETS_UART_INTR_ENABLE(); drop_next_rx = true; // for sending with large delay in EMS+ mode we use a timer interrupt - timer1_attachInterrupt(emsuart_tx_timer_intr_handler); // Add ISR Function - timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); // 5 MHz timer - USIE(EMSUART_UART) = (1 << UIBD); + timer1_attachInterrupt(emsuart_tx_timer_intr_handler); + + restart(); } /* @@ -190,7 +179,9 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) { */ void ICACHE_FLASH_ATTR EMSuart::stop() { USIE(EMSUART_UART) = 0; - // timer1_disable(); + USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear BRK bit + timer1_disable(); + sending_ = false; } /* @@ -198,67 +189,25 @@ void ICACHE_FLASH_ATTR EMSuart::stop() { */ void ICACHE_FLASH_ATTR EMSuart::restart() { if (USIR(EMSUART_UART) & ((1 << UIBD))) { - USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt + USIC(EMSUART_UART) = (1 << UIBD); // INT clear the detect interrupt drop_next_rx = true; } + if (tx_mode_ > 100) { + emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode_ - 90); + } else { + emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode_ + 10); // bittimes wait to next bytes + } emsTxBufIdx = 0; emsTxBufLen = 0; - // timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); - USIE(EMSUART_UART) = (1 << UIBD); -} - -/* - * Send a BRK signal - * Which is a 11-bit set of zero's (11 cycles) - */ -void ICACHE_FLASH_ATTR EMSuart::tx_brk() { - // make sure Tx FIFO is empty - while (((USS(EMSUART_UART) >> USTXC) & 0xFF)) { - } - USC0(EMSUART_UART) |= (1 << UCBRK); // set bit - // also for EMS+ there is no need to wait longer, we are finished and can free the bus. - delayMicroseconds(EMSUART_TX_WAIT_BRK); // 1144 - USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear BRK bit + timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); + USIE(EMSUART_UART) = (1 << UIBD); // enable interrupt } /* * Sends a 1-byte poll, ending with a - * It's a bit dirty. there is no special wait logic per tx_mode type, fifo flushes or error checking */ -void EMSuart::send_poll(uint8_t data) { - // reset tx-brk, just in case it is accidentally set - USC0(EMSUART_UART) &= ~(1 << UCBRK); - sending_ = true; - - if (tx_mode_ >= 5) { // timer controlled modes - emsTxBuf[0] = data; - emsTxBufIdx = 0; - emsTxBufLen = 1; - timer1_write(emsTxWait); - } else if (tx_mode_ == EMS_TXMODE_NEW) { // hardware controlled modes - USF(EMSUART_UART) = data; - USC0(EMSUART_UART) |= (1 << UCBRK); - } else if (tx_mode_ == EMS_TXMODE_HT3) { - USF(EMSUART_UART) = data; - delayMicroseconds(EMSUART_TX_WAIT_HT3); - tx_brk(); // send - sending_ = false; - } else if (tx_mode_ == EMS_TXMODE_EMSPLUS) { - USF(EMSUART_UART) = data; - delayMicroseconds(EMSUART_TX_WAIT_PLUS); - tx_brk(); // send - sending_ = false; - } else { - // tx_mode 1 - volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF; - USF(EMSUART_UART) = data; - uint16_t timeoutcnt = EMSUART_TX_TIMEOUT; - while ((((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) && (--timeoutcnt > 0)) { - delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles... - } - tx_brk(); // send - sending_ = false; - } +void ICACHE_FLASH_ATTR EMSuart::send_poll(uint8_t data) { + transmit(&data, 1); } /* @@ -270,19 +219,21 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { if (len == 0 || len >= EMS_MAXBUFFERSIZE) { return EMS_TX_STATUS_ERR; // nothing or to much to send } - // reset tx-brk, just in case it is accidentally set - USC0(EMSUART_UART) &= ~(1 << UCBRK); - sending_ = true; // timer controlled modes with extra delay if (tx_mode_ >= 5) { + sending_ = true; for (uint8_t i = 0; i < len; i++) { emsTxBuf[i] = buf[i]; } USF(EMSUART_UART) = buf[0]; // send first byte emsTxBufIdx = 0; emsTxBufLen = len; - timer1_write(emsTxWait); + if (tx_mode_ > 100 && len > 1) { + timer1_write(EMSUART_TX_WAIT_REPLY); // large delay after first byte + } else { + timer1_write(emsTxWait); + } return EMS_TX_STATUS_OK; } @@ -291,7 +242,7 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { for (uint8_t i = 0; i < len; i++) { USF(EMSUART_UART) = buf[i]; } - USC0(EMSUART_UART) |= (1 << UCBRK); // send at the end + USC0(EMSUART_UART) |= (1 << UCBRK); // send at the end, clear by interrupt return EMS_TX_STATUS_OK; } @@ -301,8 +252,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { USF(EMSUART_UART) = buf[i]; delayMicroseconds(EMSUART_TX_WAIT_PLUS); // 2070 } - tx_brk(); // send - sending_ = false; + USC0(EMSUART_UART) |= (1 << UCBRK); // set break + delayMicroseconds(EMSUART_TX_BRK_PLUS); + USC0(EMSUART_UART) &= ~(1 << UCBRK); return EMS_TX_STATUS_OK; } @@ -316,8 +268,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { // wait until bits are sent on wire delayMicroseconds(EMSUART_TX_WAIT_HT3); } - tx_brk(); // send - sending_ = false; + USC0(EMSUART_UART) |= (1 << UCBRK); // set break bit + delayMicroseconds(EMSUART_TX_BRK_HT3); + USC0(EMSUART_UART) &= ~(1 << UCBRK); return EMS_TX_STATUS_OK; } @@ -346,9 +299,7 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { * */ - // disable rx interrupt // clear Rx status register, resetting the Rx FIFO and flush it - // ETS_UART_INTR_DISABLE(); emsuart_flush_fifos(); // send the bytes along the serial line @@ -361,21 +312,12 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles... } } - - // we got the whole telegram in the Rx buffer - // on Rx-BRK (bus collision), we simply enable Rx and leave it - // otherwise we send the final Tx-BRK - // worst case, we'll see an additional Rx-BRK... - // neither bus collision nor timeout - send terminating BRK signal - if (!(USIS(EMSUART_UART) & (1 << UIBD))) { - // no bus collision - send terminating BRK signal - tx_brk(); - } - // ETS_UART_INTR_ENABLE(); // open up the FIFO again to start receiving - sending_ = false; + USC0(EMSUART_UART) |= (1 << UCBRK); // snd break + delayMicroseconds(EMSUART_TX_BRK_EMS); + USC0(EMSUART_UART) &= ~(1 << UCBRK); return EMS_TX_STATUS_OK; // send the Tx ok status back } } // namespace emsesp -#endif +#endif \ No newline at end of file diff --git a/src/uart/emsuart_esp8266.h b/src/uart/emsuart_esp8266.h index 29f0c0eb1..be4d1d9fa 100644 --- a/src/uart/emsuart_esp8266.h +++ b/src/uart/emsuart_esp8266.h @@ -40,20 +40,26 @@ #define EMS_TXMODE_NEW 4 // for michael's testing // LEGACY -#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud -#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144 +#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud + +// TIMER modes +#define EMSUART_TX_BRK_TIMER (EMSUART_TX_BIT_TIME * 52) // > 10 bittimes for timer modes +#define EMSUART_TX_WAIT_REPLY 500000 // delay 100ms after first byte // EMS 1.0 #define EMSUART_TX_BUSY_WAIT (EMSUART_TX_BIT_TIME / 8) // 13 -// #define EMSUART_TX_TIMEOUT (22 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT) // 176 -#define EMSUART_TX_TIMEOUT (32 * 8) // 256 for tx_mode 1 - see https://github.com/proddy/EMS-ESP/issues/398#issuecomment-645886277 +// #define EMSUART_TX_TIMEOUT (32 * 8) // 256 for tx_mode 1 - see https://github.com/proddy/EMS-ESP/issues/398#issuecomment-645886277 +#define EMSUART_TX_TIMEOUT (220 * 8) // 1760 as in v1.9 (180 ms) +#define EMSUART_TX_BRK_EMS (EMSUART_TX_BIT_TIME * 10) // HT3/Junkers - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) plus 7 bit delay. The -8 is for lag compensation. // since we use a faster processor the lag is negligible #define EMSUART_TX_WAIT_HT3 (EMSUART_TX_BIT_TIME * 17) // 1768 +#define EMSUART_TX_BRK_HT3 (EMSUART_TX_BIT_TIME * 11) // EMS+ - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) and delay of another Bytetime. #define EMSUART_TX_WAIT_PLUS (EMSUART_TX_BIT_TIME * 20) // 2080 +#define EMSUART_TX_BRK_PLUS (EMSUART_TX_BIT_TIME * 11) namespace emsesp { @@ -83,7 +89,6 @@ class EMSuart { static void ICACHE_RAM_ATTR emsuart_rx_intr_handler(void * para); static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events); static void ICACHE_FLASH_ATTR emsuart_flush_fifos(); - static void ICACHE_FLASH_ATTR tx_brk(); static void ICACHE_RAM_ATTR emsuart_tx_timer_intr_handler(); static bool sending_; }; @@ -91,4 +96,4 @@ class EMSuart { } // namespace emsesp #endif -#endif +#endif \ No newline at end of file diff --git a/src/version.h b/src/version.h index 65f946843..0d74e7d44 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.0.0b7" +#define EMSESP_APP_VERSION "2.0.0b11"