diff --git a/.gitignore b/.gitignore index 23e97e758..d97ce9d8e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,4 @@ emsesp /interface/build node_modules /interface/.eslintcache - +test.sh \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f4e330167..84360649b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [3.2.0] August 6 2021 + +## Added + +- support for IPv6 (web/api/mqtt, not syslog yet) [#83](https://github.com/emsesp/EMS-ESP32/issues/83) +- System Log in Web UI will show current time if the NTP Service is enabled [#82](https://github.com/emsesp/EMS-ESP32/issues/82) +- Network settings for Tx-power, WiFi-bandwidth, WiFi-sleepmode [#83](https://github.com/emsesp/EMS-ESP32/issues/83) +- optional low CPU clockrate (160 MHz) [#83](https://github.com/emsesp/EMS-ESP32/issues/83) +- select format for enumerated values in web +- settings for water hysteresis on/off +- dallas sensor name editable. `sensorname` console-command, replace sensorid with a unique name [#84](https://github.com/emsesp/EMS-ESP32/issues/84) +- 'restart' system command. Can be invoked via API with authentication. [#87](https://github.com/emsesp/EMS-ESP32/issues/87) +- add Download button in Web UI for log + +## Fixed + +- set mode allow numbers +- Junkers thermostat shows mode as selected by set_mode +- HA thermostat mode if bool-format: numbers is selected +- Web UI System Log sometimes skipped a few log messages when watching real-time +- fix wwactivated [#89](https://github.com/emsesp/EMS-ESP32/issues/89) +- don't show commands (like reset) as Device values in the Web or Console + +## Changed + +- removed Rx echo failures counting as incomplete telegrams. Bad telegrams show as Warning and not Errors. [#80](https://github.com/emsesp/EMS-ESP32/issues/80) +- add upload_sec to `api/system/info` and removed # from some names to keep consistent with MQTT heartbeat +- added debug target to PlatformIO build to help hunt down system crashes +- enumerated values always start at zero +- maintenance settings for time/date as extra setting +- move api/mqtt formats to `settings`, add `enum format` +- UI improvements for editing Dallas Sensor details +- RESTful GET commands can also require authentication (via bearer access token) for better security +- Updated AsyncMqttClient to 0.9.0 and ArduinoJson to 6.18.3 +- Download buttons for settings and info under the Help tab + # [3.1.1] June 26 2021 ## Changed diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index ea1320609..41bf59233 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -5,5 +5,3 @@ ## Fixed ## Changed - -## Removed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f72d11fae..facf3ff90 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,17 +24,17 @@ This document describes rules that are in effect for this repository, meant for ## Triaging of Issues/PR's -1. Any contributor to the project can participate in the triaging process, if he/she chooses to do so. -2. An issue that needs to be closed, either due to not complying with this policy, or for other reasons, should be closed by a contributor. -3. Issues that are accepted should be marked with appropriate labels. -4. Issues that could impact functionality for many users should be considered severe. -5. Issues caused by the SDK or chip should not be marked severe, as there usually isn’t much to be done. Common sense should be applied when deciding. Such issues should be documented in the documentation, for reference by users. -6. Issues with feature requests should be discussed for viability/desirability. -7. Feature requests or changes that are meant to address a very specific/limited use case, especially if at the expense of increased code complexity, may be denied, or may be required to be redesigned, generalized, or simplified. -8. Feature requests that are not accompanied by a PR: - * could be closed immediately (denied). - * could be closed after some predetermined period of time (left as candidate for somebody to pick up). -9. In some cases, feedback may be requested from the issue reporter, either as additional info for clarification, additional testing, or other. If no feedback is provided, the issue may be closed by a contributor or after 40 days by the STALE bot. +1. Any contributor to the project can participate in the triaging process, if he/she chooses to do so. +2. An issue that needs to be closed, either due to not complying with this policy, or for other reasons, should be closed by a contributor. +3. Issues that are accepted should be marked with appropriate labels. +4. Issues that could impact functionality for many users should be considered severe. +5. Issues caused by the SDK or chip should not be marked severe, as there usually isn’t much to be done. Common sense should be applied when deciding. Such issues should be documented in the documentation, for reference by users. +6. Issues with feature requests should be discussed for viability/desirability. +7. Feature requests or changes that are meant to address a very specific/limited use case, especially if at the expense of increased code complexity, may be denied, or may be required to be redesigned, generalized, or simplified. +8. Feature requests that are not accompanied by a PR: + - could be closed immediately (denied). + - could be closed after some predetermined period of time (left as candidate for somebody to pick up). +9. In some cases, feedback may be requested from the issue reporter, either as additional info for clarification, additional testing, or other. If no feedback is provided, the issue may be closed by a contributor or after 40 days by the STALE bot. ## Pull requests @@ -42,24 +42,24 @@ A Pull Request (PR) is the process where code modifications are managed in GitHu The process is straight-forward. - - Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0) - - Fork the EMS-ESP Repository [git repository](https://github.com/emsesp/EMS-ESP32). - - Write/Change the code in your Fork for a new feature, bug fix, new sensor, optimization, etc. - - Ensure tests work. - - Create a Pull Request against the [**dev**](https://github.com/emsesp/EMS-ESP32/tree/dev) branch of EMS-ESP. +- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0) +- Fork the EMS-ESP Repository [git repository](https://github.com/emsesp/EMS-ESP32). +- Write/Change the code in your Fork for a new feature, bug fix, new sensor, optimization, etc. +- Ensure tests work. +- Create a Pull Request against the [**dev**](https://github.com/emsesp/EMS-ESP32/tree/dev) branch of EMS-ESP. 1. All pull requests must be done against the dev branch. -2. Make sure code is formatting per the `.clang-format` -3. Only relevant files should be touched (Also beware if your editor has auto-formatting feature enabled). -4. Only one feature/fix should be added per PR. -5. PRs that don't compile (fail in CI Tests) or cause coding errors will not be merged. Please fix the issue. Same goes for PRs that are raised against older commit in dev - you might need to rebase and resolve conflicts. -6. All pull requests should undergo peer review by at least one contributor other than the creator, excepts for the owner. -7. All pull requests should consider updates to the documentation. -8. Pull requests that address an outstanding issue, particularly an issue deemed to be severe, should be given priority. -9. If a PR is accepted, then it should undergo review and updated based on the feedback provided, then merged. -10. By submitting a PR, it is needed to use the provided PR template and check all boxes, performing the required tasks and accepting the CLA. -11. Pull requests that don't meet the above will be denied and closed. - +2. Make sure code is formatting per the `.clang-format`. +3. Make sure any new code is clearly commented explaining what the function/logic does. +4. Only relevant files should be touched (Also beware if your editor has auto-formatting feature enabled). +5. Only one feature/fix should be added per PR. +6. PRs that don't compile (fail in CI Tests) or cause coding errors will not be merged. Please fix the issue. Same goes for PRs that are raised against older commit in dev - you might need to rebase and resolve conflicts. +7. All pull requests should undergo peer review by at least one contributor other than the creator, excepts for the owner. +8. All pull requests should consider updates to the documentation. +9. Pull requests that address an outstanding issue, particularly an issue deemed to be severe, should be given priority. +10. If a PR is accepted, then it should undergo review and updated based on the feedback provided, then merged. +11. By submitting a PR, it is needed to use the provided PR template and check all boxes, performing the required tasks and accepting the CLA. +12. Pull requests that don't meet the above will be denied and closed. ## Semantic Commit Messages @@ -92,7 +92,7 @@ More Examples: References: -- https://www.conventionalcommits.org/ +- -------------------------------------- @@ -139,4 +139,4 @@ A __CLA__ enables a contributor to grant "inbound" rights to a project. - \ No newline at end of file + diff --git a/makefile b/Makefile similarity index 98% rename from makefile rename to Makefile index 8bb37d0de..4b611b114 100644 --- a/makefile +++ b/Makefile @@ -72,9 +72,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/esp32_partition_app1984k_spiffs64k.csv b/esp32_partition_app1984k_spiffs64k.csv index 3b428f9a9..7eaebdbc1 100644 --- a/esp32_partition_app1984k_spiffs64k.csv +++ b/esp32_partition_app1984k_spiffs64k.csv @@ -1,6 +1,6 @@ -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x5000, -otadata, data, ota, 0xe000, 0x2000, -app0, app, ota_0, 0x10000, 0x1F0000, +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xE000, 0x2000, +app0, app, ota_0, 0x10000, 0x1F0000, app1, app, ota_1, 0x200000, 0x1F0000, -spiffs, data, spiffs, 0x3F0000,0x10000, +spiffs, data, spiffs, 0x3F0000, 0x10000, diff --git a/esp32_partition_debug.csv b/esp32_partition_debug.csv new file mode 100644 index 000000000..2d4923c49 --- /dev/null +++ b/esp32_partition_debug.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xE000, 0x2000, +app0, app, ota_0, 0x10000, 0x210000, +spiffs, data, spiffs, 0x220000, 0x10000, \ No newline at end of file diff --git a/interface/src/api/Env.ts b/interface/src/api/Env.ts index 3bfef1d74..16d361cf3 100644 --- a/interface/src/api/Env.ts +++ b/interface/src/api/Env.ts @@ -4,6 +4,7 @@ export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH!; export const ENDPOINT_ROOT = calculateEndpointRoot('/rest/'); export const WEB_SOCKET_ROOT = calculateWebSocketRoot('/ws/'); export const EVENT_SOURCE_ROOT = calculateEndpointRoot('/es/'); +export const API_ENDPOINT_ROOT = calculateEndpointRoot('/api/'); function calculateEndpointRoot(endpointPath: string) { const httpRoot = process.env.REACT_APP_HTTP_ROOT; diff --git a/interface/src/mqtt/MqttSettingsForm.tsx b/interface/src/mqtt/MqttSettingsForm.tsx index d4ecc3b0f..544ce786a 100644 --- a/interface/src/mqtt/MqttSettingsForm.tsx +++ b/interface/src/mqtt/MqttSettingsForm.tsx @@ -109,7 +109,7 @@ class MqttSettingsForm extends React.Component { /> { value="clean_session" /> } - label="Clean Session" + label="Set Clean Session" /> { value="mqtt_retain" /> } - label="Retain Flag" + label="Use Retain Flag" />

@@ -184,34 +184,8 @@ class MqttSettingsForm extends React.Component { onChange={handleValueChange('nested_format')} margin="normal" > - nested on a single topic - as individual topics - - - by Sensor ID - by Number - - - "on"/"off" - true/false - 1/0 - "ON"/"OFF" + Nested on a single topic + As individual topics { onChange={handleValueChange('subscribe_format')} margin="normal" > - general device topic - individual topics, main heating circuit - individual topics, all heating circuits + General device topic + Individual topics, main heating circuit + Individual topics, all heating circuits { onChange={handleValueChange('ha_climate_format')} margin="normal" > - use Current temperature (default) - use Setpoint temperature - Fix to 0 + Use Current temperature + Use Setpoint temperature + Always set to 0 )}

diff --git a/interface/src/mqtt/types.ts b/interface/src/mqtt/types.ts index b5cbdadeb..2b9cb7c3e 100644 --- a/interface/src/mqtt/types.ts +++ b/interface/src/mqtt/types.ts @@ -34,8 +34,6 @@ export interface MqttSettings { publish_time_mixer: number; publish_time_other: number; publish_time_sensor: number; - dallas_format: number; - bool_format: number; mqtt_qos: number; mqtt_retain: boolean; ha_enabled: boolean; diff --git a/interface/src/network/NetworkSettingsForm.tsx b/interface/src/network/NetworkSettingsForm.tsx index 5928688a5..24c482de8 100644 --- a/interface/src/network/NetworkSettingsForm.tsx +++ b/interface/src/network/NetworkSettingsForm.tsx @@ -51,7 +51,11 @@ class NetworkSettingsForm extends React.Component { ssid: selectedNetwork.ssid, password: '', hostname: props.data.hostname, - static_ip_config: false + static_ip_config: false, + enableIPv6: false, + bandwidth20: false, + tx_power: 20, + nosleep: false }; props.setData(networkSettings); } @@ -145,6 +149,53 @@ class NetworkSettingsForm extends React.Component { onChange={handleValueChange('hostname')} margin="normal" /> + + + } + label="Enable IPv6 support" + /> + + } + label="Use Lower WiFi Bandwidth" + /> + + } + label="Disable WiFi Sleep Mode" + /> { onChange={handleValueChange('static_ip_config')} /> } - label="Static IP Config" + label="Use Static IPs" /> {data.static_ip_config && ( diff --git a/interface/src/network/NetworkStatusForm.tsx b/interface/src/network/NetworkStatusForm.tsx index ed4c3ca8e..6098f04c6 100644 --- a/interface/src/network/NetworkStatusForm.tsx +++ b/interface/src/network/NetworkStatusForm.tsx @@ -40,7 +40,22 @@ class NetworkStatusForm extends Component { if (!status.dns_ip_1) { return 'none'; } - return status.dns_ip_1 + (status.dns_ip_2 ? ',' + status.dns_ip_2 : ''); + if (!status.dns_ip_2 || status.dns_ip_2 === '0.0.0.0') { + return status.dns_ip_1; + } + return status.dns_ip_1 + ', ' + status.dns_ip_2; + } + IPs(status: NetworkStatus) { + if ( + !status.local_ipv6 || + status.local_ipv6 === '0000:0000:0000:0000:0000:0000:0000:0000' + ) { + return status.local_ip; + } + if (!status.local_ip || status.local_ip === '0.0.0.0') { + return status.local_ipv6; + } + return status.local_ip + ', ' + status.local_ipv6; } createListItems() { @@ -77,7 +92,7 @@ class NetworkStatusForm extends Component { IP - + diff --git a/interface/src/network/types.ts b/interface/src/network/types.ts index f5e3cfb2e..538020903 100644 --- a/interface/src/network/types.ts +++ b/interface/src/network/types.ts @@ -21,6 +21,7 @@ export enum WiFiEncryptionType { export interface NetworkStatus { status: NetworkConnectionStatus; local_ip: string; + local_ipv6: string; mac_address: string; rssi: number; ssid: string; @@ -37,6 +38,10 @@ export interface NetworkSettings { password: string; hostname: string; static_ip_config: boolean; + enableIPv6: boolean; + bandwidth20: boolean; + nosleep: boolean; + tx_power: number; local_ip?: string; gateway_ip?: string; subnet_mask?: string; diff --git a/interface/src/project/EMSESPDashboard.tsx b/interface/src/project/EMSESPDashboard.tsx index b567aa79f..5c781aff9 100644 --- a/interface/src/project/EMSESPDashboard.tsx +++ b/interface/src/project/EMSESPDashboard.tsx @@ -8,7 +8,7 @@ import { MenuAppBar } from '../components'; import { AuthenticatedRoute } from '../authentication'; import EMSESPStatusController from './EMSESPStatusController'; -import EMSESPDevicesController from './EMSESPDevicesController'; +import EMSESPDataController from './EMSESPDataController'; import EMSESPHelp from './EMSESPHelp'; class EMSESP extends Component { @@ -24,18 +24,15 @@ class EMSESP extends Component { onChange={(e, path) => this.handleTabChange(path)} variant="fullWidth" > - + { path={`/${PROJECT_PATH}/help`} component={EMSESPHelp} /> - + ); diff --git a/interface/src/project/EMSESPDataController.tsx b/interface/src/project/EMSESPDataController.tsx new file mode 100644 index 000000000..5320497cd --- /dev/null +++ b/interface/src/project/EMSESPDataController.tsx @@ -0,0 +1,35 @@ +import React, { Component } from 'react'; + +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; + +import { ENDPOINT_ROOT } from '../api'; +import EMSESPDataForm from './EMSESPDataForm'; +import { EMSESPData } from './EMSESPtypes'; + +export const EMSESP_DATA_ENDPOINT = ENDPOINT_ROOT + 'data'; + +type EMSESPDataControllerProps = RestControllerProps; + +class EMSESPDataController extends Component { + componentDidMount() { + this.props.loadData(); + } + + render() { + return ( + + } + /> + + ); + } +} + +export default restController(EMSESP_DATA_ENDPOINT, EMSESPDataController); diff --git a/interface/src/project/EMSESPDevicesForm.tsx b/interface/src/project/EMSESPDataForm.tsx similarity index 72% rename from interface/src/project/EMSESPDevicesForm.tsx rename to interface/src/project/EMSESPDataForm.tsx index 98630dae2..d3905e3dc 100644 --- a/interface/src/project/EMSESPDevicesForm.tsx +++ b/interface/src/project/EMSESPDataForm.tsx @@ -40,21 +40,24 @@ import { import { RestFormProps, FormButton, extractEventValue } from '../components'; import { - EMSESPDevices, + EMSESPData, EMSESPDeviceData, Device, DeviceValue, DeviceValueUOM, - DeviceValueUOM_s + DeviceValueUOM_s, + Sensor } from './EMSESPtypes'; import ValueForm from './ValueForm'; +import SensorForm from './SensorForm'; import { ENDPOINT_ROOT } from '../api'; export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + 'scanDevices'; export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + 'deviceData'; export const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + 'writeValue'; +export const WRITE_SENSOR_ENDPOINT = ENDPOINT_ROOT + 'writeSensor'; const StyledTableCell = withStyles((theme: Theme) => createStyles({ @@ -88,15 +91,16 @@ function compareDevices(a: Device, b: Device) { return 0; } -interface EMSESPDevicesFormState { +interface EMSESPDataFormState { confirmScanDevices: boolean; processing: boolean; deviceData?: EMSESPDeviceData; selectedDevice?: number; edit_devicevalue?: DeviceValue; + edit_Sensor?: Sensor; } -type EMSESPDevicesFormProps = RestFormProps & +type EMSESPDataFormProps = RestFormProps & AuthenticatedContextProps & WithWidthProps; @@ -118,18 +122,27 @@ export const formatDuration = (duration_min: number) => { const pluralize = (count: number, noun: string, suffix = 's') => ` ${count} ${noun}${count !== 1 ? suffix : ''} `; -function formatValue(value: any, uom: number) { +function formatValue(value: any, uom: number, digit: number) { switch (uom) { case DeviceValueUOM.HOURS: return value ? formatDuration(value * 60) : '0 hours'; case DeviceValueUOM.MINUTES: return value ? formatDuration(value) : '0 minutes'; case DeviceValueUOM.NONE: + case DeviceValueUOM.LIST: return value; case DeviceValueUOM.NUM: return new Intl.NumberFormat().format(value); case DeviceValueUOM.BOOLEAN: return value ? 'on' : 'off'; + case DeviceValueUOM.DEGREES: + return ( + new Intl.NumberFormat(undefined, { + minimumFractionDigits: digit + }).format(value) + + ' ' + + DeviceValueUOM_s[uom] + ); default: return ( new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom] @@ -137,16 +150,16 @@ function formatValue(value: any, uom: number) { } } -class EMSESPDevicesForm extends Component< - EMSESPDevicesFormProps, - EMSESPDevicesFormState +class EMSESPDataForm extends Component< + EMSESPDataFormProps, + EMSESPDataFormState > { - state: EMSESPDevicesFormState = { + state: EMSESPDataFormState = { confirmScanDevices: false, processing: false }; - handleValueChange = (name: keyof DeviceValue) => ( + handleDeviceValueChange = (name: keyof DeviceValue) => ( event: React.ChangeEvent ) => { this.setState({ @@ -157,11 +170,11 @@ class EMSESPDevicesForm extends Component< }); }; - cancelEditingValue = () => { + cancelEditingDeviceValue = () => { this.setState({ edit_devicevalue: undefined }); }; - doneEditingValue = () => { + doneEditingDeviceValue = () => { const { edit_devicevalue, selectedDevice } = this.state; redirectingAuthorizedFetch(WRITE_VALUE_ENDPOINT, { @@ -179,6 +192,7 @@ class EMSESPDevicesForm extends Component< this.props.enqueueSnackbar('Write command sent to device', { variant: 'success' }); + this.handleRowClick(selectedDevice); } else if (response.status === 204) { this.props.enqueueSnackbar('Write command failed', { variant: 'error' @@ -206,6 +220,72 @@ class EMSESPDevicesForm extends Component< this.setState({ edit_devicevalue: dv }); }; + handleSensorChange = (name: keyof Sensor) => ( + event: React.ChangeEvent + ) => { + this.setState({ + edit_Sensor: { + ...this.state.edit_Sensor!, + [name]: extractEventValue(event) + } + }); + }; + + cancelEditingSensor = () => { + this.setState({ edit_Sensor: undefined }); + }; + + doneEditingSensor = () => { + const { edit_Sensor } = this.state; + + redirectingAuthorizedFetch(WRITE_SENSOR_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ + // because input field with type=number doens't like negative values, force it here + sensor: { + no: edit_Sensor?.no, + id: edit_Sensor?.id, + temp: edit_Sensor?.temp, + offset: Number(edit_Sensor?.offset) + } + }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { + if (response.status === 200) { + this.props.enqueueSnackbar('Sensor updated', { + variant: 'success' + }); + this.props.loadData(); + } else if (response.status === 204) { + this.props.enqueueSnackbar('Sensor change failed', { + variant: 'error' + }); + } else if (response.status === 403) { + this.props.enqueueSnackbar('Write access denied', { + variant: 'error' + }); + } else { + throw Error('Unexpected response code: ' + response.status); + } + }) + .catch((error) => { + this.props.enqueueSnackbar(error.message || 'Problem writing value', { + variant: 'error' + }); + }); + + if (edit_Sensor) { + this.setState({ edit_Sensor: undefined }); + } + }; + + sendSensor = (sn: Sensor) => { + this.setState({ edit_Sensor: sn }); + }; + noDevices = () => { return this.props.data.devices.length === 0; }; @@ -236,7 +316,7 @@ class EMSESPDevicesForm extends Component< this.handleRowClick(device)} + onClick={() => this.handleRowClick(device.id)} >

- Dallas Sensors + Sensors {!this.noSensors() && ( + Sensor # - ID + ID / Name Temperature {data.sensors.map((sensorData) => ( - + + + {me.admin && ( + + this.sendSensor(sensorData)} + > + + + + )} + {sensorData.no} - {sensorData.id} + {sensorData.id} - {formatValue(sensorData.temp, DeviceValueUOM.DEGREES)} + {formatValue(sensorData.temp, DeviceValueUOM.DEGREES, 1)} ))} @@ -322,7 +421,7 @@ class EMSESPDevicesForm extends Component< {this.noSensors() && ( - no external temperature sensors were detected + no connected Dallas temperature sensors were detected )} @@ -330,6 +429,34 @@ class EMSESPDevicesForm extends Component< ); } + renderAnalog() { + const { data } = this.props; + return ( + + {data.analog > 0 && ( +
+ + + Sensortype + Value + + + + + + Analog Input + + + {formatValue(data.analog, DeviceValueUOM.MV, 0)} + + + +
+ )} + + ); + } + renderScanDevicesDialog() { return ( { - this.setState({ selectedDevice: device.id, deviceData: undefined }); + this.setState({ selectedDevice: device, deviceData: undefined }); redirectingAuthorizedFetch(DEVICE_DATA_ENDPOINT, { method: 'POST', - body: JSON.stringify({ id: device.id }), + body: JSON.stringify({ id: device }), headers: { 'Content-Type': 'application/json' } @@ -475,7 +602,7 @@ class EMSESPDevicesForm extends Component< {item.n}
- {formatValue(item.v, item.u)} + {formatValue(item.v, item.u, 0)}
))} @@ -495,13 +622,14 @@ class EMSESPDevicesForm extends Component< } render() { - const { edit_devicevalue } = this.state; + const { edit_devicevalue, edit_Sensor } = this.state; return (

{this.renderDeviceItems()} {this.renderDeviceData()} {this.renderSensorItems()} + {this.renderAnalog()}

@@ -528,9 +656,17 @@ class EMSESPDevicesForm extends Component< {edit_devicevalue && ( + )} + {edit_Sensor && ( + )}
@@ -538,4 +674,4 @@ class EMSESPDevicesForm extends Component< } } -export default withAuthenticatedContext(withWidth()(EMSESPDevicesForm)); +export default withAuthenticatedContext(withWidth()(EMSESPDataForm)); diff --git a/interface/src/project/EMSESPDevicesController.tsx b/interface/src/project/EMSESPDevicesController.tsx deleted file mode 100644 index 7300a5a48..000000000 --- a/interface/src/project/EMSESPDevicesController.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { Component } from 'react'; - -import { - restController, - RestControllerProps, - RestFormLoader, - SectionContent -} from '../components'; - -import { ENDPOINT_ROOT } from '../api'; -import EMSESPDevicesForm from './EMSESPDevicesForm'; -import { EMSESPDevices } from './EMSESPtypes'; - -export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + 'allDevices'; - -type EMSESPDevicesControllerProps = RestControllerProps; - -class EMSESPDevicesController extends Component { - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - - } - /> - - ); - } -} - -export default restController(EMSESP_DEVICES_ENDPOINT, EMSESPDevicesController); diff --git a/interface/src/project/EMSESPHelp.tsx b/interface/src/project/EMSESPHelp.tsx index 46905d542..363eb94c1 100644 --- a/interface/src/project/EMSESPHelp.tsx +++ b/interface/src/project/EMSESPHelp.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { Typography, Box, @@ -14,14 +14,40 @@ import CommentIcon from '@material-ui/icons/CommentTwoTone'; import MenuBookIcon from '@material-ui/icons/MenuBookTwoTone'; import GitHubIcon from '@material-ui/icons/GitHub'; import StarIcon from '@material-ui/icons/Star'; -import ImportExportIcon from '@material-ui/icons/ImportExport'; -import BugReportIcon from '@material-ui/icons/BugReportTwoTone'; +import DownloadIcon from '@material-ui/icons/GetApp'; -export const WebAPISystemSettings = - window.location.origin + '/api/system/settings'; -export const WebAPISystemInfo = window.location.origin + '/api/system/info'; +import { FormButton } from '../components'; + +import { API_ENDPOINT_ROOT } from '../api'; + +import { redirectingAuthorizedFetch } from '../authentication'; class EMSESPHelp extends Component { + onDownload = (endpoint: string) => { + redirectingAuthorizedFetch(API_ENDPOINT_ROOT + 'system/' + endpoint) + .then((response) => { + if (response.status === 200) { + return response.json(); + } + throw Error( + 'Device returned unexpected response code: ' + response.status + ); + }) + .then((json) => { + const a = document.createElement('a'); + const filename = 'emsesp_system_' + endpoint + '.txt'; + a.href = URL.createObjectURL( + new Blob([JSON.stringify(json, null, 2)], { + type: 'text/plain' + }) + ); + a.setAttribute('download', filename); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }); + }; + render() { return ( @@ -31,9 +57,9 @@ class EMSESPHelp extends Component { - For the latest news and updates go to the{' '} + For help and information on the latest updates visit the{' '} - {'official documentation'} website + {'online documentation'}
@@ -55,41 +81,36 @@ class EMSESPHelp extends Component { - To report an issue or feature request go to{' '} + To report an issue or request a feature go to{' '} - {'click here'} - - - - - - - - - - To export your system settings{' '} - - {'click here'} - - - - - - - - - - To export the current status of EMS-ESP{' '} - - {'click here'} + {'GitHub'} + + } + variant="contained" + color="primary" + onClick={() => this.onDownload('info')} + > + download system info + + } + variant="contained" + color="primary" + onClick={() => this.onDownload('settings')} + > + download all settings + + + EMS-ESP is free and open-source. diff --git a/interface/src/project/EMSESPSettingsForm.tsx b/interface/src/project/EMSESPSettingsForm.tsx index 6045d5556..af03d4c1e 100644 --- a/interface/src/project/EMSESPSettingsForm.tsx +++ b/interface/src/project/EMSESPSettingsForm.tsx @@ -32,7 +32,7 @@ import { BlockFormControlLabel } from '../components'; -import { isIP, optional } from '../validators'; +import { isIPv4, optional, isHostname, or } from '../validators'; import { EMSESPSettings } from './EMSESPtypes'; @@ -55,7 +55,10 @@ class EMSESPSettingsForm extends Component { }; componentDidMount() { - ValidatorForm.addValidationRule('isOptionalIP', optional(isIP)); + ValidatorForm.addValidationRule( + 'isOptionalIPorHost', + optional(or(isIPv4, isHostname)) + ); } changeBoardProfile = (event: React.ChangeEvent) => { @@ -109,15 +112,17 @@ class EMSESPSettingsForm extends Component { - Adjust any of the EMS-ESP settings here. For help refer to the{' '} - - {'online documentation'} - - . + + Refer to the + + {' documentation'} + +  for information on each setting + @@ -202,7 +207,7 @@ class EMSESPSettingsForm extends Component { Select a pre-configured board layout to automatically set the GPIO - pins, or set your own custom configuration + pins. Select "Custom..." to view or manually edit the values. @@ -365,7 +370,7 @@ class EMSESPSettingsForm extends Component {

- Options + General Options {data.led_gpio !== 0 && ( @@ -390,20 +395,10 @@ class EMSESPSettingsForm extends Component { value="dallas_parasite" /> } - label="Enable Dallas parasite mode" + label="Use Dallas Sensor parasite power" /> )} - - } - label="Bypass Access Token authorization on API calls" - /> { } label="Enable ADC" /> + + } + label="Run at a lower CPU clock speed" + /> + + } + label="Bypass Access Token authorization on API calls" + /> { /> +

+ + Formatting Options + + + + + + "on"/"off" + "ON"/"OFF" + true/false + 1/0 + + + + + Text + Number + + + + + ID + Number + Name + + + +

Syslog @@ -469,10 +543,10 @@ class EMSESPSettingsForm extends Component { > { 'Max value is 10' ]} name="syslog_mark_interval" - label="Mark Interval seconds (0=off)" + label="Mark Interval (seconds, 0=off)" fullWidth variant="outlined" value={data.syslog_mark_interval} @@ -554,7 +628,7 @@ class EMSESPSettingsForm extends Component { value="trace_raw" /> } - label="Output EMS telegrams in raw format" + label="Output EMS telegrams as hexadecimal bytes" /> )} diff --git a/interface/src/project/EMSESPtypes.ts b/interface/src/project/EMSESPtypes.ts index ffd37c132..764f03e55 100644 --- a/interface/src/project/EMSESPtypes.ts +++ b/interface/src/project/EMSESPtypes.ts @@ -16,11 +16,15 @@ export interface EMSESPSettings { dallas_parasite: boolean; led_gpio: number; hide_led: boolean; + low_clock: boolean; notoken_api: boolean; analog_enabled: boolean; pbutton_gpio: number; trace_raw: boolean; board_profile: string; + bool_format: number; + dallas_format: number; + enum_format: number; } export enum busConnectionStatus { @@ -50,12 +54,14 @@ export interface Device { export interface Sensor { no: number; id: string; - temp: string; + temp: number; + offset: number; } -export interface EMSESPDevices { +export interface EMSESPData { devices: Device[]; sensors: Sensor[]; + analog: number; } export interface DeviceValue { @@ -63,6 +69,7 @@ export interface DeviceValue { u: number; n: string; c: string; + l: string[]; } export interface EMSESPDeviceData { @@ -87,7 +94,9 @@ export enum DeviceValueUOM { SECONDS, DBM, NUM, - BOOLEAN + BOOLEAN, + LIST, + MV } export const DeviceValueUOM_s = [ @@ -107,5 +116,7 @@ export const DeviceValueUOM_s = [ 'seconds', 'dBm', 'number', - 'on/off' + 'on/off', + '', + 'mV' ]; diff --git a/interface/src/project/ProjectMenu.tsx b/interface/src/project/ProjectMenu.tsx index 844c60664..f0bc9811d 100644 --- a/interface/src/project/ProjectMenu.tsx +++ b/interface/src/project/ProjectMenu.tsx @@ -23,7 +23,7 @@ class ProjectMenu extends Component { to="/ems-esp/" selected={ path.startsWith('/ems-esp/status') || - path.startsWith('/ems-esp/devices') || + path.startsWith('/ems-esp/data') || path.startsWith('/ems-esp/help') } button diff --git a/interface/src/project/SensorForm.tsx b/interface/src/project/SensorForm.tsx new file mode 100644 index 000000000..e981007ef --- /dev/null +++ b/interface/src/project/SensorForm.tsx @@ -0,0 +1,101 @@ +import React, { RefObject } from 'react'; +import { ValidatorForm, TextValidator } from 'react-material-ui-form-validator'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions +} from '@material-ui/core'; + +import { FormButton } from '../components'; +import { Sensor } from './EMSESPtypes'; + +interface SensorFormProps { + sensor: Sensor; + onDoneEditing: () => void; + onCancelEditing: () => void; + handleSensorChange: ( + data: keyof Sensor + ) => (event: React.ChangeEvent) => void; +} + +class SensorForm extends React.Component { + formRef: RefObject = React.createRef(); + + submit = () => { + this.formRef.current.submit(); + }; + + render() { + const { + sensor, + handleSensorChange, + onDoneEditing, + onCancelEditing + } = this.props; + + return ( + + + + Editing Sensor #{sensor.no} + + + + + + + + Cancel + + + Done + + + + + ); + } +} + +export default SensorForm; diff --git a/interface/src/project/ValueForm.tsx b/interface/src/project/ValueForm.tsx index 3200a543b..8da27aa49 100644 --- a/interface/src/project/ValueForm.tsx +++ b/interface/src/project/ValueForm.tsx @@ -51,27 +51,39 @@ class ValueForm extends React.Component { > Change Value - {devicevalue.u !== DeviceValueUOM.BOOLEAN && ( - - {DeviceValueUOM_s[devicevalue.u]} - - } - aria-describedby="outlined-value-helper-text" - inputProps={{ - 'aria-label': 'value' - }} - /> + variant="outlined" + > + {devicevalue.l.map((val) => ( + {val} + ))} +
)} + {devicevalue.u !== DeviceValueUOM.BOOLEAN && + devicevalue.u !== DeviceValueUOM.LIST && ( + + {DeviceValueUOM_s[devicevalue.u]} + + } + /> + )} {devicevalue.u === DeviceValueUOM.BOOLEAN && ( { off )} - - {devicevalue.n} - + {devicevalue.n} diff --git a/interface/src/security/GenerateToken.tsx b/interface/src/security/GenerateToken.tsx index 5904c25b8..6c6ee19b2 100644 --- a/interface/src/security/GenerateToken.tsx +++ b/interface/src/security/GenerateToken.tsx @@ -44,7 +44,6 @@ class GenerateToken extends React.Component< } }) .then((generatedToken) => { - // console.log(generatedToken); this.setState({ token: generatedToken.token }); }) .catch((error) => { diff --git a/interface/src/system/LogEventConsole.tsx b/interface/src/system/LogEventConsole.tsx index 19a96bb77..43a6df419 100644 --- a/interface/src/system/LogEventConsole.tsx +++ b/interface/src/system/LogEventConsole.tsx @@ -6,6 +6,8 @@ import { useWindowSize } from '../components'; interface LogEventConsoleProps { events: LogEvent[]; + compact: boolean; + level: number; } interface Offsets { @@ -63,7 +65,9 @@ const useStyles = makeStyles((theme: Theme) => ({ const LogEventConsole: FC = (props) => { useWindowSize(); const classes = useStyles({ topOffset, leftOffset }); - const { events } = props; + const { events, compact, level } = props; + + const filter_events = events.filter((e) => e.l <= level); const styleLevel = (level: LogLevel) => { switch (level) { @@ -103,23 +107,34 @@ const LogEventConsole: FC = (props) => { } }; - const paddedLevelLabel = (level: LogLevel) => { + const paddedLevelLabel = (level: LogLevel, compact: boolean) => { const label = levelLabel(level); - return label.padStart(8, '\xa0'); + return compact ? ' ' + label[0] : label.padStart(8, '\xa0'); }; - const paddedNameLabel = (name: string) => { + const paddedNameLabel = (name: string, compact: boolean) => { const label = '[' + name + ']'; - return label.padStart(8, '\xa0'); + return compact ? label : label.padEnd(12, '\xa0'); + }; + + const paddedIDLabel = (id: number, compact: boolean) => { + const label = id + ':'; + return compact ? label : label.padEnd(7, '\xa0'); }; return ( - {events.map((e) => ( -
+ {filter_events.map((e) => ( +
{e.t} - {paddedLevelLabel(e.l)} - {paddedNameLabel(e.n)} + {compact && {paddedLevelLabel(e.l, compact)} } + {!compact && ( + + {paddedLevelLabel(e.l, compact)}{' '} + + )} + {paddedIDLabel(e.i, compact)} + {paddedNameLabel(e.n, compact)} {e.m}
))} diff --git a/interface/src/system/LogEventController.tsx b/interface/src/system/LogEventController.tsx index cdeffafb1..fff3f9478 100644 --- a/interface/src/system/LogEventController.tsx +++ b/interface/src/system/LogEventController.tsx @@ -3,22 +3,38 @@ import { Component } from 'react'; import { restController, RestControllerProps, - RestFormLoader, - SectionContent + SectionContent, + BlockFormControlLabel } from '../components'; -import { addAccessTokenParameter } from '../authentication'; +import { + ValidatorForm, + SelectValidator +} from 'react-material-ui-form-validator'; + +import { + Grid, + Slider, + FormLabel, + Checkbox, + MenuItem, + Button +} from '@material-ui/core'; + +import { + addAccessTokenParameter, + redirectingAuthorizedFetch +} from '../authentication'; + +import DownloadIcon from '@material-ui/icons/GetApp'; import { ENDPOINT_ROOT, EVENT_SOURCE_ROOT } from '../api'; export const FETCH_LOG_ENDPOINT = ENDPOINT_ROOT + 'fetchLog'; export const LOG_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'logSettings'; - export const LOG_EVENT_EVENT_SOURCE_URL = EVENT_SOURCE_ROOT + 'log'; -import LogEventForm from './LogEventForm'; import LogEventConsole from './LogEventConsole'; - -import { LogEvent, LogSettings } from './types'; +import { LogEvent, LogSettings, LogLevel } from './types'; import { Decoder } from '@msgpack/msgpack'; const decoder = new Decoder(); @@ -26,6 +42,9 @@ const decoder = new Decoder(); interface LogEventControllerState { eventSource?: EventSource; events: LogEvent[]; + compact: boolean; + level: number; + max_messages: number; } type LogEventControllerProps = RestControllerProps; @@ -40,12 +59,15 @@ class LogEventController extends Component< constructor(props: LogEventControllerProps) { super(props); this.state = { - events: [] + events: [], + compact: false, + level: 6, + max_messages: 25 }; } componentDidMount() { - this.props.loadData(); + this.fetchValues(); this.fetchLog(); this.configureEventSource(); } @@ -59,6 +81,15 @@ class LogEventController extends Component< } } + changeCompact = ( + event: React.ChangeEvent, + checked: boolean + ) => { + this.setState({ + compact: checked + }); + }; + fetchLog = () => { fetch(FETCH_LOG_ENDPOINT) .then((response) => { @@ -78,6 +109,25 @@ class LogEventController extends Component< }); }; + fetchValues = () => { + redirectingAuthorizedFetch(LOG_SETTINGS_ENDPOINT) + .then((response) => { + if (response.status === 200) { + return response.json(); + } + throw Error('Unexpected status code: ' + response.status); + }) + .then((json) => { + this.setState({ level: json.level, max_messages: json.max_messages }); + }) + .catch((error) => { + const errorMessage = error.message || 'Unknown error'; + this.props.enqueueSnackbar('Problem fetching: ' + errorMessage, { + variant: 'error' + }); + }); + }; + configureEventSource = () => { this.eventSource = new EventSource( addAccessTokenParameter(LOG_EVENT_EVENT_SOURCE_URL) @@ -102,14 +152,172 @@ class LogEventController extends Component< } }; + changeMaxMessages = ( + event: React.ChangeEvent<{}>, + value: number | number[] + ) => { + this.setState({ + max_messages: value as number + }); + this.send_data(this.state.level, value as number); + }; + + changeLevel = (event: React.ChangeEvent) => { + this.setState({ + level: parseInt(event.target.value) + }); + this.send_data(parseInt(event.target.value), this.state.max_messages); + }; + + send_data = (level: number, max_messages: number) => { + redirectingAuthorizedFetch(LOG_SETTINGS_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ + level: level, + max_messages: max_messages + }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { + if (response.status !== 200) { + throw Error('Unexpected response code: ' + response.status); + } + }) + .catch((error) => { + this.props.enqueueSnackbar( + error.message || 'Problem applying log settings', + { variant: 'warning' } + ); + }); + }; + + levelLabel = (level: LogLevel) => { + switch (level) { + case LogLevel.ERROR: + return 'E'; + case LogLevel.WARNING: + return 'W'; + case LogLevel.NOTICE: + return 'N'; + case LogLevel.INFO: + return 'I'; + case LogLevel.DEBUG: + return 'D'; + case LogLevel.TRACE: + return 'TRACE'; + default: + return ''; + } + }; + + onDownload = () => { + const { events, level } = this.state; + let result = ''; + for (const i in events) { + if (events[i].l <= level) { + result += + events[i].t + + ' ' + + this.levelLabel(events[i].l) + + ' ' + + events[i].i + + ': [' + + events[i].n + + '] ' + + events[i].m + + '\n'; + } + } + const a = document.createElement('a'); + a.setAttribute( + 'href', + 'data:text/plain;charset=utf-8,' + encodeURIComponent(result) + ); + a.setAttribute('download', 'log.txt'); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }; + render() { + const { saveData } = this.props; return ( - } + + + + + ERROR + WARNING + NOTICE + INFO + DEBUG + ALL + + + + Buffer size + + + + + } + label="Compact Layout" + /> + + + + + + + + - ); } diff --git a/interface/src/system/LogEventForm.tsx b/interface/src/system/LogEventForm.tsx deleted file mode 100644 index 16de6ba0e..000000000 --- a/interface/src/system/LogEventForm.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { Component } from 'react'; - -import { - ValidatorForm, - SelectValidator -} from 'react-material-ui-form-validator'; - -import { Typography, Grid } from '@material-ui/core'; - -import MenuItem from '@material-ui/core/MenuItem'; - -import { - redirectingAuthorizedFetch, - withAuthenticatedContext, - AuthenticatedContextProps -} from '../authentication'; - -import { RestFormProps } from '../components'; -import { LogSettings } from './types'; - -import { ENDPOINT_ROOT } from '../api'; -export const LOG_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'logSettings'; - -type LogEventFormProps = AuthenticatedContextProps & RestFormProps; - -class LogEventForm extends Component { - changeLevel = (event: React.ChangeEvent) => { - const { data, setData } = this.props; - setData({ - ...data, - level: parseInt(event.target.value) - }); - - redirectingAuthorizedFetch(LOG_SETTINGS_ENDPOINT, { - method: 'POST', - body: JSON.stringify({ level: event.target.value }), - headers: { - 'Content-Type': 'application/json' - } - }) - .then((response) => { - if (response.status === 200) { - return response.json(); - } - throw Error('Unexpected response code: ' + response.status); - }) - .then((json) => { - this.props.enqueueSnackbar('Log settings changed', { - variant: 'success' - }); - setData({ - ...data, - level: json.level - }); - }) - .catch((error) => { - this.props.enqueueSnackbar( - error.message || 'Problem changing log settings', - { variant: 'warning' } - ); - }); - }; - - render() { - const { data, saveData } = this.props; - return ( - - - - - OFF - ERROR - WARNING - NOTICE - INFO - DEBUG - ALL - - - - - - (the last {data.max_messages} messages are buffered and new log - events are shown in real time) - - - - - - ); - } -} - -export default withAuthenticatedContext(LogEventForm); diff --git a/interface/src/system/types.ts b/interface/src/system/types.ts index c7e522440..fdc51626c 100644 --- a/interface/src/system/types.ts +++ b/interface/src/system/types.ts @@ -50,6 +50,7 @@ export enum LogLevel { export interface LogEvent { t: string; l: LogLevel; + i: number; n: string; m: string; } diff --git a/interface/src/validators/index.ts b/interface/src/validators/index.ts index ac394b76a..24224ffd2 100644 --- a/interface/src/validators/index.ts +++ b/interface/src/validators/index.ts @@ -3,3 +3,4 @@ export { default as isIP } from './isIP'; export { default as optional } from './optional'; export { default as or } from './or'; export { default as isPath } from './isPath'; +export { default as isIPv4 } from './isIPv4'; diff --git a/interface/src/validators/isIP.ts b/interface/src/validators/isIP.ts index 295b41398..36ffee158 100644 --- a/interface/src/validators/isIP.ts +++ b/interface/src/validators/isIP.ts @@ -1,4 +1,4 @@ -const ipAddressRegexp = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; +const ipAddressRegexp = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/; export default function isIp(ipAddress: string) { return ipAddressRegexp.test(ipAddress); diff --git a/interface/src/validators/isIPv4.ts b/interface/src/validators/isIPv4.ts new file mode 100644 index 000000000..160bda7c5 --- /dev/null +++ b/interface/src/validators/isIPv4.ts @@ -0,0 +1,5 @@ +const ipv4AddressRegexp = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + +export default function isIpv4(ipAddress: string) { + return ipv4AddressRegexp.test(ipAddress); +} diff --git a/lib/ArduinoJson/CHANGELOG.md b/lib/ArduinoJson/CHANGELOG.md index f3866b3c8..a1bbc5d24 100644 --- a/lib/ArduinoJson/CHANGELOG.md +++ b/lib/ArduinoJson/CHANGELOG.md @@ -1,6 +1,31 @@ ArduinoJson: change log ======================= +v6.18.3 (2021-07-27) +------- + +* Changed return type of `convertToJson()` and `Converter::toJson()` to `void` +* Added `as()` and `is()` + +v6.18.2 (2021-07-19) +------- + +* Removed a symlink because the Arduino Library Specification forbids it + +v6.18.1 (2021-07-03) +------- + +* Fixed support for `volatile float` and `volatile double` (issue #1557) +* Fixed error `[Pe070]: incomplete type is not allowed` on IAR (issue #1560) +* Fixed `serializeJson(doc, String)` when allocation fails (issue #1572) +* Fixed clang-tidy warnings (issue #1574, PR #1577 by @armandas) +* Added fake class `InvalidConversion` to easily identify invalid conversions (issue #1585) +* Added support for `std::string_view` (issue #1578, PR #1554 by @0xFEEDC0DE64) +* Fixed warning `definition of implicit copy constructor for 'MsgPackDeserializer' is deprecated because it has a user-declared copy assignment operator` +* Added `JsonArray::clear()` (issue #1597) +* Fixed `JsonVariant::as()` (issue #1601) +* Added support for ESP-IDF component build (PR #1562 by @qt1, PR #1599 by @andreaskuster) + v6.18.0 (2021-05-05) ------- diff --git a/lib/ArduinoJson/README.md b/lib/ArduinoJson/README.md index c30028435..a9dfbc7f5 100644 --- a/lib/ArduinoJson/README.md +++ b/lib/ArduinoJson/README.md @@ -2,7 +2,7 @@ --- -[![arduino-library-badge](https://www.ardu-badge.com/badge/ArduinoJson.svg?version=6.18.0)](https://www.ardu-badge.com/ArduinoJson/6.18.0) +[![arduino-library-badge](https://www.ardu-badge.com/badge/ArduinoJson.svg?version=6.18.3)](https://www.ardu-badge.com/ArduinoJson/6.18.3) [![Continuous Integration](https://github.com/bblanchon/ArduinoJson/workflows/Continuous%20Integration/badge.svg?branch=6.x)](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A6.x) [![Continuous Integration](https://ci.appveyor.com/api/projects/status/m7s53wav1l0abssg/branch/6.x?svg=true)](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/arduinojson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson) @@ -33,15 +33,15 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things). * [Optionally works without heap memory (zero malloc)](https://arduinojson.org/v6/api/staticjsondocument/?utm_source=github&utm_medium=readme) * Deduplicates strings * Versatile - * [Supports custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/?utm_source=github&utm_medium=readme) - * Supports [Arduino's `String`](https://arduinojson.org/v6/api/config/enable_arduino_string/?utm_source=github&utm_medium=readme) and [STL's `std::string`](https://arduinojson.org/v6/api/config/enable_std_string/?utm_source=github&utm_medium=readme) - * Supports [Arduino's `Stream`](https://arduinojson.org/v6/api/config/enable_arduino_stream/?utm_source=github&utm_medium=readme) and [STL's `std::istream`/`std::ostream`](https://arduinojson.org/v6/api/config/enable_std_stream/?utm_source=github&utm_medium=readme) - * [Supports Flash strings](https://arduinojson.org/v6/api/config/enable_progmem/?utm_source=github&utm_medium=readme) + * Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/?utm_source=github&utm_medium=readme) + * Supports [`String`](https://arduinojson.org/v6/api/config/enable_arduino_string/?utm_source=github&utm_medium=readme), [`std::string`](https://arduinojson.org/v6/api/config/enable_std_string/?utm_source=github&utm_medium=readme) and [`std::string_view`](https://arduinojson.org/v6/api/config/enable_string_view/?utm_source=github&utm_medium=readme) + * Supports [`Stream`](https://arduinojson.org/v6/api/config/enable_arduino_stream/?utm_source=github&utm_medium=readme) and [`std::istream`/`std::ostream`](https://arduinojson.org/v6/api/config/enable_std_stream/?utm_source=github&utm_medium=readme) + * Supports [Flash strings](https://arduinojson.org/v6/api/config/enable_progmem/?utm_source=github&utm_medium=readme) * Supports [custom readers](https://arduinojson.org/v6/api/json/deserializejson/?utm_source=github&utm_medium=readme#custom-reader) and [custom writers](https://arduinojson.org/v6/api/json/serializejson/?utm_source=github&utm_medium=readme#custom-writer) - * Supports custom converters + * Supports [custom converters](https://arduinojson.org/news/2021/05/04/version-6-18-0/?utm_source=github&utm_medium=readme) * Portable * Usable on any C++ project (not limited to Arduino) - * Compatible with C++98 + * Compatible with C++98, C++11, C++14 and C++17 * Zero warnings with `-Wall -Wextra -pedantic` and `/W4` * [Header-only library](https://en.wikipedia.org/wiki/Header-only) * Works with virtually any board @@ -81,15 +81,17 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things). * [GCC 4.4, 4.6, 4.7, 4.8, 4.9, 5, 6, 7, 8, 9, 10](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22) * [Clang 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 5.0, 6.0, 7, 8, 9, 10](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22) * [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson) + * Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/) * Well documented * [Tutorials](https://arduinojson.org/v6/doc/deserialization/?utm_source=github&utm_medium=readme) * [Examples](https://arduinojson.org/v6/example/?utm_source=github&utm_medium=readme) * [How-tos](https://arduinojson.org/v6/example/?utm_source=github&utm_medium=readme) * [FAQ](https://arduinojson.org/v6/faq/?utm_source=github&utm_medium=readme) + * [Troubleshooter](https://arduinojson.org/v6/troubleshooter/?utm_source=github&utm_medium=readme) * [Book](https://arduinojson.org/book/?utm_source=github&utm_medium=readme) * [Changelog](CHANGELOG.md) * Vibrant user community - * Most popular of all Arduino libraries on [GitHub](https://github.com/search?o=desc&q=arduino+library&s=stars&type=Repositories) and [PlatformIO](https://platformio.org/lib/search) + * Most popular of all Arduino libraries on [GitHub](https://github.com/search?o=desc&q=arduino+library&s=stars&type=Repositories) * [Used in hundreds of projects](https://www.hackster.io/search?i=projects&q=arduinojson) * [Responsive support](https://github.com/bblanchon/ArduinoJson/issues?q=is%3Aissue+is%3Aclosed) @@ -132,9 +134,11 @@ serializeJson(doc, Serial); See the [tutorial on arduinojson.org](https://arduinojson.org/doc/encoding/?utm_source=github&utm_medium=readme) -## Support the project +## Support the project ❤️ -Do you like this library? Please [star this project on GitHub](https://github.com/bblanchon/ArduinoJson/stargazers)! +Do you like this library? +Please [star this project on GitHub](https://github.com/bblanchon/ArduinoJson/stargazers)! What? You don't like it but you *love* it? -We don't take donations anymore, but [we sell a book](https://arduinojson.org/book/?utm_source=github&utm_medium=readme), so you can help and learn at the same time. +You can support the project by [purchasing my book](https://arduinojson.org/book/?utm_source=github&utm_medium=readme). +Alternatively, you can make a recurring donation via [GitHub Sponsors](https://github.com/sponsors/bblanchon). diff --git a/lib/ArduinoJson/src/ArduinoJson.hpp b/lib/ArduinoJson/src/ArduinoJson.hpp index 28d42eef5..d16ca08cd 100644 --- a/lib/ArduinoJson/src/ArduinoJson.hpp +++ b/lib/ArduinoJson/src/ArduinoJson.hpp @@ -7,11 +7,11 @@ #include "ArduinoJson/Configuration.hpp" #if !ARDUINOJSON_DEBUG -#ifdef __clang__ -#pragma clang system_header -#elif defined __GNUC__ -#pragma GCC system_header -#endif +# ifdef __clang__ +# pragma clang system_header +# elif defined __GNUC__ +# pragma GCC system_header +# endif #endif #include "ArduinoJson/Array/ArrayRef.hpp" diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp index a991db0d7..4f8d0c631 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp @@ -161,14 +161,20 @@ class ArrayRef : public ArrayRefBase, _data->removeElement(index); } + void clear() const { + if (!_data) + return; + _data->clear(); + } + private: MemoryPool* _pool; }; template <> struct Converter { - static bool toJson(VariantConstRef src, VariantRef dst) { - return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + static void toJson(VariantConstRef src, VariantRef dst) { + variantCopyFrom(getData(dst), getData(src), getPool(dst)); } static ArrayConstRef fromJson(VariantConstRef src) { @@ -183,8 +189,8 @@ struct Converter { template <> struct Converter { - static bool toJson(VariantConstRef src, VariantRef dst) { - return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + static void toJson(VariantConstRef src, VariantRef dst) { + variantCopyFrom(getData(dst), getData(src), getPool(dst)); } static ArrayRef fromJson(VariantRef src) { @@ -193,6 +199,8 @@ struct Converter { return ArrayRef(pool, data != 0 ? data->asArray() : 0); } + static InvalidConversion fromJson(VariantConstRef); + static bool checkJson(VariantConstRef) { return false; } diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp index c6062e492..c1016eb01 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp @@ -10,8 +10,8 @@ #include #ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4522) +# pragma warning(push) +# pragma warning(disable : 4522) #endif namespace ARDUINOJSON_NAMESPACE { @@ -178,8 +178,8 @@ class ElementProxy : public VariantOperators >, return _array.getOrAddElement(_index); } - friend bool convertToJson(const this_type& src, VariantRef dst) { - return dst.set(src.getUpstreamElement()); + friend void convertToJson(const this_type& src, VariantRef dst) { + dst.set(src.getUpstreamElement()); } TArray _array; @@ -189,5 +189,5 @@ class ElementProxy : public VariantOperators >, } // namespace ARDUINOJSON_NAMESPACE #ifdef _MSC_VER -#pragma warning(pop) +# pragma warning(pop) #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp b/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp index 49a24beed..f814bcdf2 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp @@ -62,9 +62,9 @@ inline bool CollectionData::copyFrom(const CollectionData& src, VariantData* var; if (s->key() != 0) { if (s->ownsKey()) - var = addMember(RamStringAdapter(s->key()), pool); + var = addMember(adaptString(const_cast(s->key())), pool); else - var = addMember(ConstRamStringAdapter(s->key()), pool); + var = addMember(adaptString(s->key()), pool); } else { var = addElement(pool); } @@ -107,7 +107,7 @@ template inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const { VariantSlot* slot = _head; while (slot) { - if (key.equals(slot->key())) + if (key.compare(slot->key()) == 0) break; slot = slot->next(); } diff --git a/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp b/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp index 0c0d4c489..332abb6a7 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp @@ -5,251 +5,269 @@ #pragma once #if __cplusplus >= 201103L -#define ARDUINOJSON_HAS_LONG_LONG 1 -#define ARDUINOJSON_HAS_NULLPTR 1 -#define ARDUINOJSON_HAS_RVALUE_REFERENCES 1 +# define ARDUINOJSON_HAS_LONG_LONG 1 +# define ARDUINOJSON_HAS_RVALUE_REFERENCES 1 #else -#define ARDUINOJSON_HAS_LONG_LONG 0 -#define ARDUINOJSON_HAS_NULLPTR 0 -#define ARDUINOJSON_HAS_RVALUE_REFERENCES 0 +# define ARDUINOJSON_HAS_LONG_LONG 0 +# define ARDUINOJSON_HAS_RVALUE_REFERENCES 0 +#endif + +#ifndef ARDUINOJSON_HAS_NULLPTR +# if __cplusplus >= 201103L +# define ARDUINOJSON_HAS_NULLPTR 1 +# else +# define ARDUINOJSON_HAS_NULLPTR 0 +# endif #endif #if defined(_MSC_VER) && !ARDUINOJSON_HAS_LONG_LONG -#define ARDUINOJSON_HAS_INT64 1 +# define ARDUINOJSON_HAS_INT64 1 #else -#define ARDUINOJSON_HAS_INT64 0 +# define ARDUINOJSON_HAS_INT64 0 #endif // Small or big machine? #ifndef ARDUINOJSON_EMBEDDED_MODE -#if defined(ARDUINO) /* Arduino*/ \ - || defined(__IAR_SYSTEMS_ICC__) /* IAR Embedded Workbench */ \ - || defined(__XC) /* MPLAB XC compiler */ \ - || defined(__ARMCC_VERSION) /* Keil ARM Compiler */ \ - || defined(__AVR) /* Atmel AVR8/GNU C Compiler */ -#define ARDUINOJSON_EMBEDDED_MODE 1 -#else -#define ARDUINOJSON_EMBEDDED_MODE 0 -#endif +# if defined(ARDUINO) /* Arduino*/ \ + || defined(__IAR_SYSTEMS_ICC__) /* IAR Embedded Workbench */ \ + || defined(__XC) /* MPLAB XC compiler */ \ + || defined(__ARMCC_VERSION) /* Keil ARM Compiler */ \ + || defined(__AVR) /* Atmel AVR8/GNU C Compiler */ +# define ARDUINOJSON_EMBEDDED_MODE 1 +# else +# define ARDUINOJSON_EMBEDDED_MODE 0 +# endif #endif // Auto enable std::stream if the right headers are here and no conflicting // macro is defined #if !defined(ARDUINOJSON_ENABLE_STD_STREAM) && defined(__has_include) -#if __has_include() && \ +# if __has_include() && \ __has_include() && \ !defined(min) && \ !defined(max) -#define ARDUINOJSON_ENABLE_STD_STREAM 1 -#else -#define ARDUINOJSON_ENABLE_STD_STREAM 0 -#endif +# define ARDUINOJSON_ENABLE_STD_STREAM 1 +# else +# define ARDUINOJSON_ENABLE_STD_STREAM 0 +# endif #endif // Auto enable std::string if the right header is here and no conflicting // macro is defined #if !defined(ARDUINOJSON_ENABLE_STD_STRING) && defined(__has_include) -#if __has_include() && !defined(min) && !defined(max) -#define ARDUINOJSON_ENABLE_STD_STRING 1 -#else -#define ARDUINOJSON_ENABLE_STD_STRING 0 +# if __has_include() && !defined(min) && !defined(max) +# define ARDUINOJSON_ENABLE_STD_STRING 1 +# else +# define ARDUINOJSON_ENABLE_STD_STRING 0 +# endif #endif + +#ifndef ARDUINOJSON_ENABLE_STRING_VIEW +# ifdef __has_include +# if __has_include() && __cplusplus >= 201703L +# define ARDUINOJSON_ENABLE_STRING_VIEW 1 +# endif +# endif +#endif +#ifndef ARDUINOJSON_ENABLE_STRING_VIEW +# define ARDUINOJSON_ENABLE_STRING_VIEW 0 #endif #if ARDUINOJSON_EMBEDDED_MODE // Store floats by default to reduce the memory usage (issue #134) -#ifndef ARDUINOJSON_USE_DOUBLE -#define ARDUINOJSON_USE_DOUBLE 0 -#endif +# ifndef ARDUINOJSON_USE_DOUBLE +# define ARDUINOJSON_USE_DOUBLE 0 +# endif // Store longs by default, because they usually match the size of a float. -#ifndef ARDUINOJSON_USE_LONG_LONG -#define ARDUINOJSON_USE_LONG_LONG 0 -#endif +# ifndef ARDUINOJSON_USE_LONG_LONG +# define ARDUINOJSON_USE_LONG_LONG 0 +# endif // Embedded systems usually don't have std::string -#ifndef ARDUINOJSON_ENABLE_STD_STRING -#define ARDUINOJSON_ENABLE_STD_STRING 0 -#endif +# ifndef ARDUINOJSON_ENABLE_STD_STRING +# define ARDUINOJSON_ENABLE_STD_STRING 0 +# endif // Embedded systems usually don't have std::stream -#ifndef ARDUINOJSON_ENABLE_STD_STREAM -#define ARDUINOJSON_ENABLE_STD_STREAM 0 -#endif +# ifndef ARDUINOJSON_ENABLE_STD_STREAM +# define ARDUINOJSON_ENABLE_STD_STREAM 0 +# endif // Limit nesting as the stack is likely to be small -#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT -#define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10 -#endif +# ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT +# define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10 +# endif // Number of bits to store the pointer to next node // (saves RAM but limits the number of values in a document) -#ifndef ARDUINOJSON_SLOT_OFFSET_SIZE -#if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 2 +# ifndef ARDUINOJSON_SLOT_OFFSET_SIZE +# if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 2 // Address space == 16-bit => max 127 values -#define ARDUINOJSON_SLOT_OFFSET_SIZE 1 -#else +# define ARDUINOJSON_SLOT_OFFSET_SIZE 1 +# else // Address space > 16-bit => max 32767 values -#define ARDUINOJSON_SLOT_OFFSET_SIZE 2 -#endif -#endif +# define ARDUINOJSON_SLOT_OFFSET_SIZE 2 +# endif +# endif #else // ARDUINOJSON_EMBEDDED_MODE // On a computer we have plenty of memory so we can use doubles -#ifndef ARDUINOJSON_USE_DOUBLE -#define ARDUINOJSON_USE_DOUBLE 1 -#endif +# ifndef ARDUINOJSON_USE_DOUBLE +# define ARDUINOJSON_USE_DOUBLE 1 +# endif // Use long long when available -#ifndef ARDUINOJSON_USE_LONG_LONG -#if ARDUINOJSON_HAS_LONG_LONG || ARDUINOJSON_HAS_INT64 -#define ARDUINOJSON_USE_LONG_LONG 1 -#else -#define ARDUINOJSON_USE_LONG_LONG 0 -#endif -#endif +# ifndef ARDUINOJSON_USE_LONG_LONG +# if ARDUINOJSON_HAS_LONG_LONG || ARDUINOJSON_HAS_INT64 +# define ARDUINOJSON_USE_LONG_LONG 1 +# else +# define ARDUINOJSON_USE_LONG_LONG 0 +# endif +# endif // On a computer, we can use std::string -#ifndef ARDUINOJSON_ENABLE_STD_STRING -#define ARDUINOJSON_ENABLE_STD_STRING 1 -#endif +# ifndef ARDUINOJSON_ENABLE_STD_STRING +# define ARDUINOJSON_ENABLE_STD_STRING 1 +# endif // On a computer, we can assume std::stream -#ifndef ARDUINOJSON_ENABLE_STD_STREAM -#define ARDUINOJSON_ENABLE_STD_STREAM 1 -#endif +# ifndef ARDUINOJSON_ENABLE_STD_STREAM +# define ARDUINOJSON_ENABLE_STD_STREAM 1 +# endif // On a computer, the stack is large so we can increase nesting limit -#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT -#define ARDUINOJSON_DEFAULT_NESTING_LIMIT 50 -#endif +# ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT +# define ARDUINOJSON_DEFAULT_NESTING_LIMIT 50 +# endif // Number of bits to store the pointer to next node -#ifndef ARDUINOJSON_SLOT_OFFSET_SIZE -#define ARDUINOJSON_SLOT_OFFSET_SIZE 4 -#endif +# ifndef ARDUINOJSON_SLOT_OFFSET_SIZE +# define ARDUINOJSON_SLOT_OFFSET_SIZE 4 +# endif #endif // ARDUINOJSON_EMBEDDED_MODE #ifdef ARDUINO -#include +# include // Enable support for Arduino's String class -#ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING -#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 -#endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING +# define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 +# endif // Enable support for Arduino's Stream class -#ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM -#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1 -#endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM +# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1 +# endif // Enable support for Arduino's Print class -#ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT -#define ARDUINOJSON_ENABLE_ARDUINO_PRINT 1 -#endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT +# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 1 +# endif #else // ARDUINO // Disable support for Arduino's String class -#ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING -#define ARDUINOJSON_ENABLE_ARDUINO_STRING 0 -#endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING +# define ARDUINOJSON_ENABLE_ARDUINO_STRING 0 +# endif // Disable support for Arduino's Stream class -#ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM -#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0 -#endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM +# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0 +# endif // Disable support for Arduino's Print class -#ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT -#define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0 -#endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT +# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0 +# endif #endif // ARDUINO #ifndef ARDUINOJSON_ENABLE_PROGMEM -#if defined(PROGMEM) && defined(pgm_read_byte) && defined(pgm_read_dword) && \ - defined(pgm_read_ptr) && defined(pgm_read_float) -#define ARDUINOJSON_ENABLE_PROGMEM 1 -#else -#define ARDUINOJSON_ENABLE_PROGMEM 0 -#endif +# if defined(PROGMEM) && defined(pgm_read_byte) && defined(pgm_read_dword) && \ + defined(pgm_read_ptr) && defined(pgm_read_float) +# define ARDUINOJSON_ENABLE_PROGMEM 1 +# else +# define ARDUINOJSON_ENABLE_PROGMEM 0 +# endif #endif // Convert unicode escape sequence (\u0123) to UTF-8 #ifndef ARDUINOJSON_DECODE_UNICODE -#define ARDUINOJSON_DECODE_UNICODE 1 +# define ARDUINOJSON_DECODE_UNICODE 1 #endif // Ignore comments in input #ifndef ARDUINOJSON_ENABLE_COMMENTS -#define ARDUINOJSON_ENABLE_COMMENTS 0 +# define ARDUINOJSON_ENABLE_COMMENTS 0 #endif // Support NaN in JSON #ifndef ARDUINOJSON_ENABLE_NAN -#define ARDUINOJSON_ENABLE_NAN 0 +# define ARDUINOJSON_ENABLE_NAN 0 #endif // Support Infinity in JSON #ifndef ARDUINOJSON_ENABLE_INFINITY -#define ARDUINOJSON_ENABLE_INFINITY 0 +# define ARDUINOJSON_ENABLE_INFINITY 0 #endif // Control the exponentiation threshold for big numbers // CAUTION: cannot be more that 1e9 !!!! #ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD -#define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e7 +# define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e7 #endif // Control the exponentiation threshold for small numbers #ifndef ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD -#define ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD 1e-5 +# define ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD 1e-5 #endif #ifndef ARDUINOJSON_LITTLE_ENDIAN -#if defined(_MSC_VER) || \ - (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ - defined(__LITTLE_ENDIAN__) || defined(__i386) || defined(__x86_64) -#define ARDUINOJSON_LITTLE_ENDIAN 1 -#else -#define ARDUINOJSON_LITTLE_ENDIAN 0 -#endif +# if defined(_MSC_VER) || \ + (defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + defined(__LITTLE_ENDIAN__) || defined(__i386) || defined(__x86_64) +# define ARDUINOJSON_LITTLE_ENDIAN 1 +# else +# define ARDUINOJSON_LITTLE_ENDIAN 0 +# endif #endif #ifndef ARDUINOJSON_ENABLE_ALIGNMENT -#if defined(__AVR) -#define ARDUINOJSON_ENABLE_ALIGNMENT 0 -#else -#define ARDUINOJSON_ENABLE_ALIGNMENT 1 -#endif +# if defined(__AVR) +# define ARDUINOJSON_ENABLE_ALIGNMENT 0 +# else +# define ARDUINOJSON_ENABLE_ALIGNMENT 1 +# endif #endif #ifndef ARDUINOJSON_TAB -#define ARDUINOJSON_TAB " " +# define ARDUINOJSON_TAB " " #endif #ifndef ARDUINOJSON_ENABLE_STRING_DEDUPLICATION -#define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 1 +# define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 1 #endif #ifndef ARDUINOJSON_STRING_BUFFER_SIZE -#define ARDUINOJSON_STRING_BUFFER_SIZE 32 +# define ARDUINOJSON_STRING_BUFFER_SIZE 32 #endif #ifndef ARDUINOJSON_DEBUG -#ifdef __PLATFORMIO_BUILD_DEBUG__ -#define ARDUINOJSON_DEBUG 1 -#else -#define ARDUINOJSON_DEBUG 0 -#endif +# ifdef __PLATFORMIO_BUILD_DEBUG__ +# define ARDUINOJSON_DEBUG 1 +# else +# define ARDUINOJSON_DEBUG 0 +# endif #endif #if ARDUINOJSON_HAS_NULLPTR && defined(nullptr) -#error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr +# error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr // See https://github.com/bblanchon/ArduinoJson/issues/1355 #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp index 7b617111a..11ec7df66 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp @@ -9,7 +9,7 @@ #include #if ARDUINOJSON_ENABLE_STD_STREAM -#include +# include #endif namespace ARDUINOJSON_NAMESPACE { diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp index e965c8256..9ae2d5987 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp @@ -40,17 +40,17 @@ struct BoundedReader { #include #if ARDUINOJSON_ENABLE_ARDUINO_STREAM -#include +# include #endif #if ARDUINOJSON_ENABLE_ARDUINO_STRING -#include +# include #endif #if ARDUINOJSON_ENABLE_PROGMEM -#include +# include #endif #if ARDUINOJSON_ENABLE_STD_STREAM -#include +# include #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp b/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp index d67d93496..d81853c3a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp @@ -32,7 +32,7 @@ class JsonDocument : public Visitable { void clear() { _pool.clear(); - _data.setNull(); + _data.init(); } template @@ -304,15 +304,15 @@ class JsonDocument : public Visitable { protected: JsonDocument() : _pool(0, 0) { - _data.setNull(); + _data.init(); } JsonDocument(MemoryPool pool) : _pool(pool) { - _data.setNull(); + _data.init(); } JsonDocument(char* buf, size_t capa) : _pool(buf, capa) { - _data.setNull(); + _data.init(); } ~JsonDocument() {} @@ -337,8 +337,8 @@ class JsonDocument : public Visitable { JsonDocument& operator=(const JsonDocument&); }; -inline bool convertToJson(const JsonDocument& src, VariantRef dst) { - return dst.set(src.as()); +inline void convertToJson(const JsonDocument& src, VariantRef dst) { + dst.set(src.as()); } } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp index aef1fe368..70866d6b7 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp @@ -45,7 +45,8 @@ class Latch { } TReader _reader; - char _current; + char _current; // NOLINT(clang-analyzer-optin.cplusplus.UninitializedObject) + // Not initialized in constructor (+10 bytes on AVR) bool _loaded; #if ARDUINOJSON_DEBUG bool _ended; diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp index 18694f14e..7795671ff 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp @@ -155,7 +155,6 @@ class TextFormatter { protected: CountingDecorator _writer; - size_t _length; private: TextFormatter &operator=(const TextFormatter &); // cannot be assigned diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp index 4e2750f3b..e2b901056 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp @@ -12,10 +12,10 @@ // we choose to ignore the problem to reduce the size of the code // Garbage in => Garbage out #if defined(__GNUC__) -#if __GNUC__ >= 7 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif +# if __GNUC__ >= 7 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +# endif #endif namespace ARDUINOJSON_NAMESPACE { @@ -31,7 +31,7 @@ inline bool isLowSurrogate(uint16_t codeunit) { class Codepoint { public: - Codepoint() : _highSurrogate(0) {} + Codepoint() : _highSurrogate(0), _codepoint(0) {} bool append(uint16_t codeunit) { if (isHighSurrogate(codeunit)) { @@ -61,7 +61,7 @@ class Codepoint { } // namespace ARDUINOJSON_NAMESPACE #if defined(__GNUC__) -#if __GNUC__ >= 8 -#pragma GCC diagnostic pop -#endif +# if __GNUC__ >= 8 +# pragma GCC diagnostic pop +# endif #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp b/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp index 49debf856..04e5b2d28 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include // memmove @@ -37,7 +38,8 @@ class MemoryPool { } void* buffer() { - return _begin; + return _begin; // NOLINT(clang-analyzer-unix.Malloc) + // movePointers() alters this pointer } // Gets the capacity of the memoryPool in bytes @@ -63,7 +65,7 @@ class MemoryPool { return 0; #if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION - const char* existingCopy = findString(str.begin()); + const char* existingCopy = findString(str); if (existingCopy) return existingCopy; #endif @@ -85,7 +87,7 @@ class MemoryPool { const char* saveStringFromFreeZone(size_t len) { #if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION - const char* dup = findString(_left); + const char* dup = findString(adaptString(_left)); if (dup) return dup; #endif @@ -162,16 +164,11 @@ class MemoryPool { } #if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION - template - const char* findString(TIterator str) { + template + const char* findString(const TAdaptedString& str) { for (char* next = _begin; next < _left; ++next) { - char* begin = next; - - // try to match - for (TIterator it = str; *it == *next; ++it) { - if (*next++ == 0) - return begin; - } + if (str.compare(next) == 0) + return next; // jump to next terminator while (*next) ++next; diff --git a/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp b/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp index 8988173e3..1b153a7ca 100644 --- a/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp @@ -15,468 +15,475 @@ namespace ARDUINOJSON_NAMESPACE { template class MsgPackDeserializer { - public: - MsgPackDeserializer(MemoryPool & pool, TReader reader, TStringStorage stringStorage) - : _pool(&pool) - , _reader(reader) - , _stringStorage(stringStorage) - , _error(DeserializationError::Ok) - , _foundSomething(false) { - } + public: + MsgPackDeserializer(MemoryPool &pool, TReader reader, + TStringStorage stringStorage) + : _pool(&pool), + _reader(reader), + _stringStorage(stringStorage), + _error(DeserializationError::Ok), + _foundSomething(false) {} - template - DeserializationError parse(VariantData & variant, TFilter filter, NestingLimit nestingLimit) { - parseVariant(&variant, filter, nestingLimit); - return _foundSomething ? _error : DeserializationError::EmptyInput; - } + template + DeserializationError parse(VariantData &variant, TFilter filter, + NestingLimit nestingLimit) { + parseVariant(&variant, filter, nestingLimit); + return _foundSomething ? _error : DeserializationError::EmptyInput; + } - private: - // Prevent VS warning "assignment operator could not be generated" - MsgPackDeserializer & operator=(const MsgPackDeserializer &); + private: + bool invalidInput() { + _error = DeserializationError::InvalidInput; + return false; + } - bool invalidInput() { - _error = DeserializationError::InvalidInput; - return false; - } + template + bool parseVariant(VariantData *variant, TFilter filter, + NestingLimit nestingLimit) { + uint8_t code = 0; + if (!readByte(code)) + return false; - template - bool parseVariant(VariantData * variant, TFilter filter, NestingLimit nestingLimit) { - uint8_t code = 0; - if (!readByte(code)) - return false; + _foundSomething = true; - _foundSomething = true; + bool allowValue = filter.allowValue(); - bool allowValue = filter.allowValue(); + switch (code) { + case 0xc0: + // already null + return true; - switch (code) { - case 0xc0: - // already null - return true; - - case 0xc1: - return invalidInput(); - - case 0xc2: - if (allowValue) - variant->setBoolean(false); - return true; - - case 0xc3: - if (allowValue) - variant->setBoolean(true); - return true; - - case 0xc4: // bin 8 (not supported) - return skipString(); - - case 0xc5: // bin 16 (not supported) - return skipString(); - - case 0xc6: // bin 32 (not supported) - return skipString(); - - case 0xc7: // ext 8 (not supported) - return skipExt(); - - case 0xc8: // ext 16 (not supported) - return skipExt(); - - case 0xc9: // ext 32 (not supported) - return skipExt(); - - case 0xca: - if (allowValue) - return readFloat(variant); - else - return skipBytes(4); - - case 0xcb: - if (allowValue) - return readDouble(variant); - else - return skipBytes(8); - - case 0xcc: - if (allowValue) - return readInteger(variant); - else - return skipBytes(1); - - case 0xcd: - if (allowValue) - return readInteger(variant); - else - return skipBytes(2); - - case 0xce: - if (allowValue) - return readInteger(variant); - else - return skipBytes(4); - - case 0xcf: -#if ARDUINOJSON_USE_LONG_LONG - if (allowValue) - return readInteger(variant); - else - return skipBytes(8); -#else - return skipBytes(8); // not supported -#endif - - case 0xd0: - if (allowValue) - return readInteger(variant); - else - return skipBytes(1); - - case 0xd1: - if (allowValue) - return readInteger(variant); - else - return skipBytes(2); - - case 0xd2: - if (allowValue) - return readInteger(variant); - else - return skipBytes(4); - - case 0xd3: -#if ARDUINOJSON_USE_LONG_LONG - if (allowValue) - return readInteger(variant); - else - return skipBytes(8); // not supported -#else - return skipBytes(8); -#endif - - case 0xd4: // fixext 1 (not supported) - return skipBytes(2); - - case 0xd5: // fixext 2 (not supported) - return skipBytes(3); - - case 0xd6: // fixext 4 (not supported) - return skipBytes(5); - - case 0xd7: // fixext 8 (not supported) - return skipBytes(9); - - case 0xd8: // fixext 16 (not supported) - return skipBytes(17); - - case 0xd9: - if (allowValue) - return readString(variant); - else - return skipString(); - - case 0xda: - if (allowValue) - return readString(variant); - else - return skipString(); - - case 0xdb: - if (allowValue) - return readString(variant); - else - return skipString(); - - case 0xdc: - return readArray(variant, filter, nestingLimit); - - case 0xdd: - return readArray(variant, filter, nestingLimit); - - case 0xde: - return readObject(variant, filter, nestingLimit); - - case 0xdf: - return readObject(variant, filter, nestingLimit); - } - - switch (code & 0xf0) { - case 0x80: - return readObject(variant, code & 0x0F, filter, nestingLimit); - - case 0x90: - return readArray(variant, code & 0x0F, filter, nestingLimit); - } - - if ((code & 0xe0) == 0xa0) { - if (allowValue) - return readString(variant, code & 0x1f); - else - return skipBytes(code & 0x1f); - } + case 0xc1: + return invalidInput(); + case 0xc2: if (allowValue) - variant->setInteger(static_cast(code)); - + variant->setBoolean(false); return true; + + case 0xc3: + if (allowValue) + variant->setBoolean(true); + return true; + + case 0xc4: // bin 8 (not supported) + return skipString(); + + case 0xc5: // bin 16 (not supported) + return skipString(); + + case 0xc6: // bin 32 (not supported) + return skipString(); + + case 0xc7: // ext 8 (not supported) + return skipExt(); + + case 0xc8: // ext 16 (not supported) + return skipExt(); + + case 0xc9: // ext 32 (not supported) + return skipExt(); + + case 0xca: + if (allowValue) + return readFloat(variant); + else + return skipBytes(4); + + case 0xcb: + if (allowValue) + return readDouble(variant); + else + return skipBytes(8); + + case 0xcc: + if (allowValue) + return readInteger(variant); + else + return skipBytes(1); + + case 0xcd: + if (allowValue) + return readInteger(variant); + else + return skipBytes(2); + + case 0xce: + if (allowValue) + return readInteger(variant); + else + return skipBytes(4); + + case 0xcf: +#if ARDUINOJSON_USE_LONG_LONG + if (allowValue) + return readInteger(variant); + else + return skipBytes(8); +#else + return skipBytes(8); // not supported +#endif + + case 0xd0: + if (allowValue) + return readInteger(variant); + else + return skipBytes(1); + + case 0xd1: + if (allowValue) + return readInteger(variant); + else + return skipBytes(2); + + case 0xd2: + if (allowValue) + return readInteger(variant); + else + return skipBytes(4); + + case 0xd3: +#if ARDUINOJSON_USE_LONG_LONG + if (allowValue) + return readInteger(variant); + else + return skipBytes(8); // not supported +#else + return skipBytes(8); +#endif + + case 0xd4: // fixext 1 (not supported) + return skipBytes(2); + + case 0xd5: // fixext 2 (not supported) + return skipBytes(3); + + case 0xd6: // fixext 4 (not supported) + return skipBytes(5); + + case 0xd7: // fixext 8 (not supported) + return skipBytes(9); + + case 0xd8: // fixext 16 (not supported) + return skipBytes(17); + + case 0xd9: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xda: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xdb: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xdc: + return readArray(variant, filter, nestingLimit); + + case 0xdd: + return readArray(variant, filter, nestingLimit); + + case 0xde: + return readObject(variant, filter, nestingLimit); + + case 0xdf: + return readObject(variant, filter, nestingLimit); } - bool readByte(uint8_t & value) { - int c = _reader.read(); - if (c < 0) { - _error = DeserializationError::IncompleteInput; - return false; - } - value = static_cast(c); - return true; + switch (code & 0xf0) { + case 0x80: + return readObject(variant, code & 0x0F, filter, nestingLimit); + + case 0x90: + return readArray(variant, code & 0x0F, filter, nestingLimit); } - bool readBytes(uint8_t * p, size_t n) { - if (_reader.readBytes(reinterpret_cast(p), n) == n) - return true; + if ((code & 0xe0) == 0xa0) { + if (allowValue) + return readString(variant, code & 0x1f); + else + return skipBytes(code & 0x1f); + } + + if (allowValue) + variant->setInteger(static_cast(code)); + + return true; + } + + bool readByte(uint8_t &value) { + int c = _reader.read(); + if (c < 0) { + _error = DeserializationError::IncompleteInput; + return false; + } + value = static_cast(c); + return true; + } + + bool readBytes(uint8_t *p, size_t n) { + if (_reader.readBytes(reinterpret_cast(p), n) == n) + return true; + _error = DeserializationError::IncompleteInput; + return false; + } + + template + bool readBytes(T &value) { + return readBytes(reinterpret_cast(&value), sizeof(value)); + } + + bool skipBytes(size_t n) { + for (; n; --n) { + if (_reader.read() < 0) { _error = DeserializationError::IncompleteInput; return false; + } + } + return true; + } + + template + bool readInteger(T &value) { + if (!readBytes(value)) + return false; + fixEndianess(value); + return true; + } + + template + bool readInteger(VariantData *variant) { + T value; + if (!readInteger(value)) + return false; + variant->setInteger(value); + return true; + } + + template + typename enable_if::type readFloat( + VariantData *variant) { + T value; + if (!readBytes(value)) + return false; + fixEndianess(value); + variant->setFloat(value); + return true; + } + + template + typename enable_if::type readDouble( + VariantData *variant) { + T value; + if (!readBytes(value)) + return false; + fixEndianess(value); + variant->setFloat(value); + return true; + } + + template + typename enable_if::type readDouble( + VariantData *variant) { + uint8_t i[8]; // input is 8 bytes + T value; // output is 4 bytes + uint8_t *o = reinterpret_cast(&value); + if (!readBytes(i, 8)) + return false; + doubleToFloat(i, o); + fixEndianess(value); + variant->setFloat(value); + return true; + } + + template + bool readString(VariantData *variant) { + T size; + if (!readInteger(size)) + return false; + return readString(variant, size); + } + + template + bool readString() { + T size; + if (!readInteger(size)) + return false; + return readString(size); + } + + template + bool skipString() { + T size; + if (!readInteger(size)) + return false; + return skipBytes(size); + } + + bool readString(VariantData *variant, size_t n) { + if (!readString(n)) + return false; + variant->setStringPointer(_stringStorage.save(), + typename TStringStorage::storage_policy()); + return true; + } + + bool readString(size_t n) { + _stringStorage.startString(); + for (; n; --n) { + uint8_t c; + if (!readBytes(c)) + return false; + _stringStorage.append(static_cast(c)); + } + _stringStorage.append('\0'); + if (!_stringStorage.isValid()) { + _error = DeserializationError::NoMemory; + return false; } - template - bool readBytes(T & value) { - return readBytes(reinterpret_cast(&value), sizeof(value)); + return true; + } + + template + bool readArray(VariantData *variant, TFilter filter, + NestingLimit nestingLimit) { + TSize size; + if (!readInteger(size)) + return false; + return readArray(variant, size, filter, nestingLimit); + } + + template + bool readArray(VariantData *variant, size_t n, TFilter filter, + NestingLimit nestingLimit) { + if (nestingLimit.reached()) { + _error = DeserializationError::TooDeep; + return false; } - bool skipBytes(size_t n) { - for (; n; --n) { - if (_reader.read() < 0) { - _error = DeserializationError::IncompleteInput; - return false; - } + bool allowArray = filter.allowArray(); + + CollectionData *array = allowArray ? &variant->toArray() : 0; + + TFilter memberFilter = filter[0U]; + + for (; n; --n) { + VariantData *value; + + if (memberFilter.allow()) { + value = array->addElement(_pool); + if (!value) { + _error = DeserializationError::NoMemory; + return false; } - return true; + } else { + value = 0; + } + + if (!parseVariant(value, memberFilter, nestingLimit.decrement())) + return false; } - template - bool readInteger(T & value) { - if (!readBytes(value)) - return false; - fixEndianess(value); - return true; + return true; + } + + template + bool readObject(VariantData *variant, TFilter filter, + NestingLimit nestingLimit) { + TSize size; + if (!readInteger(size)) + return false; + return readObject(variant, size, filter, nestingLimit); + } + + template + bool readObject(VariantData *variant, size_t n, TFilter filter, + NestingLimit nestingLimit) { + if (nestingLimit.reached()) { + _error = DeserializationError::TooDeep; + return false; } - template - bool readInteger(VariantData * variant) { - T value; - if (!readInteger(value)) - return false; - variant->setInteger(value); - return true; - } + CollectionData *object = filter.allowObject() ? &variant->toObject() : 0; - template - typename enable_if::type readFloat(VariantData * variant) { - T value; - if (!readBytes(value)) - return false; - fixEndianess(value); - variant->setFloat(value); - return true; - } + for (; n; --n) { + if (!readKey()) + return false; - template - typename enable_if::type readDouble(VariantData * variant) { - T value; - if (!readBytes(value)) - return false; - fixEndianess(value); - variant->setFloat(value); - return true; - } + const char *key = _stringStorage.c_str(); + TFilter memberFilter = filter[key]; + VariantData *member; - template - typename enable_if::type readDouble(VariantData * variant) { - uint8_t i[8]; // input is 8 bytes - T value; // output is 4 bytes - uint8_t * o = reinterpret_cast(&value); - if (!readBytes(i, 8)) - return false; - doubleToFloat(i, o); - fixEndianess(value); - variant->setFloat(value); - return true; - } + if (memberFilter.allow()) { + // Save key in memory pool. + // This MUST be done before adding the slot. + key = _stringStorage.save(); - template - bool readString(VariantData * variant) { - T size; - if (!readInteger(size)) - return false; - return readString(variant, size); - } - - template - bool readString() { - T size; - if (!readInteger(size)) - return false; - return readString(size); - } - - template - bool skipString() { - T size; - if (!readInteger(size)) - return false; - return skipBytes(size); - } - - bool readString(VariantData * variant, size_t n) { - if (!readString(n)) - return false; - variant->setStringPointer(_stringStorage.save(), typename TStringStorage::storage_policy()); - return true; - } - - bool readString(size_t n) { - _stringStorage.startString(); - for (; n; --n) { - uint8_t c; - if (!readBytes(c)) - return false; - _stringStorage.append(static_cast(c)); - } - _stringStorage.append('\0'); - if (!_stringStorage.isValid()) { - _error = DeserializationError::NoMemory; - return false; + VariantSlot *slot = object->addSlot(_pool); + if (!slot) { + _error = DeserializationError::NoMemory; + return false; } - return true; + slot->setKey(key, typename TStringStorage::storage_policy()); + + member = slot->data(); + } else { + member = 0; + } + + if (!parseVariant(member, memberFilter, nestingLimit.decrement())) + return false; } - template - bool readArray(VariantData * variant, TFilter filter, NestingLimit nestingLimit) { - TSize size; - if (!readInteger(size)) - return false; - return readArray(variant, size, filter, nestingLimit); + return true; + } + + bool readKey() { + uint8_t code; + if (!readByte(code)) + return false; + + if ((code & 0xe0) == 0xa0) + return readString(code & 0x1f); + + switch (code) { + case 0xd9: + return readString(); + + case 0xda: + return readString(); + + case 0xdb: + return readString(); + + default: + return invalidInput(); } + } - template - bool readArray(VariantData * variant, size_t n, TFilter filter, NestingLimit nestingLimit) { - if (nestingLimit.reached()) { - _error = DeserializationError::TooDeep; - return false; - } + template + bool skipExt() { + T size; + if (!readInteger(size)) + return false; + return skipBytes(size + 1); + } - bool allowArray = filter.allowArray(); - - CollectionData * array = allowArray ? &variant->toArray() : 0; - - TFilter memberFilter = filter[0U]; - - for (; n; --n) { - VariantData * value; - - if (memberFilter.allow()) { - value = array->addElement(_pool); - if (!value) { - _error = DeserializationError::NoMemory; - return false; - } - } else { - value = 0; - } - - if (!parseVariant(value, memberFilter, nestingLimit.decrement())) - return false; - } - - return true; - } - - template - bool readObject(VariantData * variant, TFilter filter, NestingLimit nestingLimit) { - TSize size; - if (!readInteger(size)) - return false; - return readObject(variant, size, filter, nestingLimit); - } - - template - bool readObject(VariantData * variant, size_t n, TFilter filter, NestingLimit nestingLimit) { - if (nestingLimit.reached()) { - _error = DeserializationError::TooDeep; - return false; - } - - CollectionData * object = filter.allowObject() ? &variant->toObject() : 0; - - for (; n; --n) { - if (!readKey()) - return false; - - const char * key = _stringStorage.c_str(); - TFilter memberFilter = filter[key]; - VariantData * member; - - if (memberFilter.allow()) { - // Save key in memory pool. - // This MUST be done before adding the slot. - key = _stringStorage.save(); - - VariantSlot * slot = object->addSlot(_pool); - if (!slot) { - _error = DeserializationError::NoMemory; - return false; - } - - slot->setKey(key, typename TStringStorage::storage_policy()); - - member = slot->data(); - } else { - member = 0; - } - - if (!parseVariant(member, memberFilter, nestingLimit.decrement())) - return false; - } - - return true; - } - - bool readKey() { - uint8_t code; - if (!readByte(code)) - return false; - - if ((code & 0xe0) == 0xa0) - return readString(code & 0x1f); - - switch (code) { - case 0xd9: - return readString(); - - case 0xda: - return readString(); - - case 0xdb: - return readString(); - - default: - return invalidInput(); - } - } - - template - bool skipExt() { - T size; - if (!readInteger(size)) - return false; - return skipBytes(size + 1); - } - - MemoryPool * _pool; - TReader _reader; - TStringStorage _stringStorage; - DeserializationError _error; - bool _foundSomething; + MemoryPool *_pool; + TReader _reader; + TStringStorage _stringStorage; + DeserializationError _error; + bool _foundSomething; }; // @@ -484,18 +491,25 @@ class MsgPackDeserializer { // // ... = NestingLimit template -DeserializationError deserializeMsgPack(JsonDocument & doc, const TString & input, NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, AllowAllFilter()); +DeserializationError deserializeMsgPack( + JsonDocument &doc, const TString &input, + NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, + AllowAllFilter()); } // ... = Filter, NestingLimit template -DeserializationError deserializeMsgPack(JsonDocument & doc, const TString & input, Filter filter, NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack( + JsonDocument &doc, const TString &input, Filter filter, + NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, filter); } // ... = NestingLimit, Filter template -DeserializationError deserializeMsgPack(JsonDocument & doc, const TString & input, NestingLimit nestingLimit, Filter filter) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument &doc, const TString &input, + NestingLimit nestingLimit, + Filter filter) { + return deserialize(doc, input, nestingLimit, filter); } // @@ -503,18 +517,25 @@ DeserializationError deserializeMsgPack(JsonDocument & doc, const TString & inpu // // ... = NestingLimit template -DeserializationError deserializeMsgPack(JsonDocument & doc, TStream & input, NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, AllowAllFilter()); +DeserializationError deserializeMsgPack( + JsonDocument &doc, TStream &input, + NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, + AllowAllFilter()); } // ... = Filter, NestingLimit template -DeserializationError deserializeMsgPack(JsonDocument & doc, TStream & input, Filter filter, NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack( + JsonDocument &doc, TStream &input, Filter filter, + NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, filter); } // ... = NestingLimit, Filter template -DeserializationError deserializeMsgPack(JsonDocument & doc, TStream & input, NestingLimit nestingLimit, Filter filter) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument &doc, TStream &input, + NestingLimit nestingLimit, + Filter filter) { + return deserialize(doc, input, nestingLimit, filter); } // @@ -522,18 +543,25 @@ DeserializationError deserializeMsgPack(JsonDocument & doc, TStream & input, Nes // // ... = NestingLimit template -DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, AllowAllFilter()); +DeserializationError deserializeMsgPack( + JsonDocument &doc, TChar *input, + NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, + AllowAllFilter()); } // ... = Filter, NestingLimit template -DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, Filter filter, NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack( + JsonDocument &doc, TChar *input, Filter filter, + NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, filter); } // ... = NestingLimit, Filter template -DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, NestingLimit nestingLimit, Filter filter) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument &doc, TChar *input, + NestingLimit nestingLimit, + Filter filter) { + return deserialize(doc, input, nestingLimit, filter); } // @@ -541,18 +569,28 @@ DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, Nesti // // ... = NestingLimit template -DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, size_t inputSize, NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, inputSize, nestingLimit, AllowAllFilter()); +DeserializationError deserializeMsgPack( + JsonDocument &doc, TChar *input, size_t inputSize, + NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, inputSize, nestingLimit, + AllowAllFilter()); } // ... = Filter, NestingLimit template -DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, size_t inputSize, Filter filter, NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, inputSize, nestingLimit, filter); +DeserializationError deserializeMsgPack( + JsonDocument &doc, TChar *input, size_t inputSize, Filter filter, + NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, inputSize, nestingLimit, + filter); } // ... = NestingLimit, Filter template -DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, size_t inputSize, NestingLimit nestingLimit, Filter filter) { - return deserialize(doc, input, inputSize, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument &doc, TChar *input, + size_t inputSize, + NestingLimit nestingLimit, + Filter filter) { + return deserialize(doc, input, inputSize, nestingLimit, + filter); } -} // namespace ARDUINOJSON_NAMESPACE +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp b/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp index 2d85440aa..26def8ab0 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp @@ -10,17 +10,17 @@ #ifndef ARDUINOJSON_NAMESPACE -#define ARDUINOJSON_NAMESPACE \ - ARDUINOJSON_CONCAT4( \ - ARDUINOJSON_CONCAT4(ArduinoJson, ARDUINOJSON_VERSION_MAJOR, \ - ARDUINOJSON_VERSION_MINOR, \ - ARDUINOJSON_VERSION_REVISION), \ - _, \ - ARDUINOJSON_HEX_DIGIT(ARDUINOJSON_ENABLE_PROGMEM, \ - ARDUINOJSON_USE_LONG_LONG, ARDUINOJSON_USE_DOUBLE, \ - ARDUINOJSON_ENABLE_STRING_DEDUPLICATION), \ - ARDUINOJSON_HEX_DIGIT( \ - ARDUINOJSON_ENABLE_NAN, ARDUINOJSON_ENABLE_INFINITY, \ - ARDUINOJSON_ENABLE_COMMENTS, ARDUINOJSON_DECODE_UNICODE)) +# define ARDUINOJSON_NAMESPACE \ + ARDUINOJSON_CONCAT4( \ + ARDUINOJSON_CONCAT4(ArduinoJson, ARDUINOJSON_VERSION_MAJOR, \ + ARDUINOJSON_VERSION_MINOR, \ + ARDUINOJSON_VERSION_REVISION), \ + _, \ + ARDUINOJSON_HEX_DIGIT( \ + ARDUINOJSON_ENABLE_PROGMEM, ARDUINOJSON_USE_LONG_LONG, \ + ARDUINOJSON_USE_DOUBLE, ARDUINOJSON_ENABLE_STRING_DEDUPLICATION), \ + ARDUINOJSON_HEX_DIGIT( \ + ARDUINOJSON_ENABLE_NAN, ARDUINOJSON_ENABLE_INFINITY, \ + ARDUINOJSON_ENABLE_COMMENTS, ARDUINOJSON_DECODE_UNICODE)) #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp index fb656a7d9..e0ed519a1 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp @@ -22,11 +22,11 @@ typedef unsigned long UInt; } // namespace ARDUINOJSON_NAMESPACE #if ARDUINOJSON_HAS_LONG_LONG && !ARDUINOJSON_USE_LONG_LONG -#define ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T) \ - static_assert(sizeof(T) <= sizeof(ARDUINOJSON_NAMESPACE::Integer), \ - "To use 64-bit integers with ArduinoJson, you must set " \ - "ARDUINOJSON_USE_LONG_LONG to 1. See " \ - "https://arduinojson.org/v6/api/config/use_long_long/"); +# define ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T) \ + static_assert(sizeof(T) <= sizeof(ARDUINOJSON_NAMESPACE::Integer), \ + "To use 64-bit integers with ArduinoJson, you must set " \ + "ARDUINOJSON_USE_LONG_LONG to 1. See " \ + "https://arduinojson.org/v6/api/config/use_long_long/"); #else -#define ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T) +# define ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T) #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp index 02bbefa50..3087459f4 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp @@ -5,13 +5,13 @@ #pragma once #if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wconversion" #elif defined(__GNUC__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) -#pragma GCC diagnostic push -#endif -#pragma GCC diagnostic ignored "-Wconversion" +# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic push +# endif +# pragma GCC diagnostic ignored "-Wconversion" #endif #include @@ -71,9 +71,23 @@ canConvertNumber(TIn) { } // int32 -> uint32 +// int32 -> uint64 template typename enable_if::value && is_signed::value && - is_integral::value && is_unsigned::value, + is_integral::value && is_unsigned::value && + sizeof(TOut) >= sizeof(TIn), + bool>::type +canConvertNumber(TIn value) { + if (value < 0) + return false; + return TOut(value) <= numeric_limits::highest(); +} + +// int32 -> uint16 +template +typename enable_if::value && is_signed::value && + is_integral::value && is_unsigned::value && + sizeof(TOut) < sizeof(TIn), bool>::type canConvertNumber(TIn value) { if (value < 0) @@ -99,9 +113,9 @@ TOut convertNumber(TIn value) { } // namespace ARDUINOJSON_NAMESPACE #if defined(__clang__) -#pragma clang diagnostic pop +# pragma clang diagnostic pop #elif defined(__GNUC__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) -#pragma GCC diagnostic pop -#endif +# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic pop +# endif #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp index 0bee84bf2..f1463a3dd 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp @@ -12,8 +12,8 @@ #include #ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4522) +# pragma warning(push) +# pragma warning(disable : 4522) #endif namespace ARDUINOJSON_NAMESPACE { @@ -187,8 +187,8 @@ class MemberProxy : public VariantOperators >, return _object.getOrAddMember(_key); } - friend bool convertToJson(const this_type &src, VariantRef dst) { - return dst.set(src.getUpstreamMember()); + friend void convertToJson(const this_type &src, VariantRef dst) { + dst.set(src.getUpstreamMember()); } TObject _object; @@ -198,5 +198,5 @@ class MemberProxy : public VariantOperators >, } // namespace ARDUINOJSON_NAMESPACE #ifdef _MSC_VER -#pragma warning(pop) +# pragma warning(pop) #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp index c945fb6ca..047d9b1b5 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp @@ -239,8 +239,8 @@ class ObjectRef : public ObjectRefBase, template <> struct Converter { - static bool toJson(VariantConstRef src, VariantRef dst) { - return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + static void toJson(VariantConstRef src, VariantRef dst) { + variantCopyFrom(getData(dst), getData(src), getPool(dst)); } static ObjectConstRef fromJson(VariantConstRef src) { @@ -255,8 +255,8 @@ struct Converter { template <> struct Converter { - static bool toJson(VariantConstRef src, VariantRef dst) { - return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + static void toJson(VariantConstRef src, VariantRef dst) { + variantCopyFrom(getData(dst), getData(src), getPool(dst)); } static ObjectRef fromJson(VariantRef src) { @@ -265,6 +265,9 @@ struct Converter { return ObjectRef(pool, data != 0 ? data->asObject() : 0); } + static InvalidConversion fromJson( + VariantConstRef); + static bool checkJson(VariantConstRef) { return false; } diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp index 1030ec60e..f5fa94673 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp @@ -7,8 +7,8 @@ #include #if ARDUINOJSON_DEBUG -#include -#define ARDUINOJSON_ASSERT(X) assert(X) +# include +# define ARDUINOJSON_ASSERT(X) assert(X) #else -#define ARDUINOJSON_ASSERT(X) ((void)0) +# define ARDUINOJSON_ASSERT(X) ((void)0) #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp index f04c9acce..8ef33afef 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp @@ -6,49 +6,49 @@ #ifdef _MSC_VER // Visual Studio -#define FORCE_INLINE // __forceinline causes C4714 when returning std::string -#define NO_INLINE __declspec(noinline) +# define FORCE_INLINE // __forceinline causes C4714 when returning std::string +# define NO_INLINE __declspec(noinline) -#ifndef ARDUINOJSON_DEPRECATED -#define ARDUINOJSON_DEPRECATED(msg) __declspec(deprecated(msg)) -#endif +# ifndef ARDUINOJSON_DEPRECATED +# define ARDUINOJSON_DEPRECATED(msg) __declspec(deprecated(msg)) +# endif #elif defined(__GNUC__) // GCC or Clang -#define FORCE_INLINE __attribute__((always_inline)) -#define NO_INLINE __attribute__((noinline)) +# define FORCE_INLINE __attribute__((always_inline)) +# define NO_INLINE __attribute__((noinline)) -#ifndef ARDUINOJSON_DEPRECATED -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) -#define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated(msg))) -#else -#define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated)) -#endif -#endif +# ifndef ARDUINOJSON_DEPRECATED +# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +# define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated(msg))) +# else +# define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated)) +# endif +# endif #else // Other compilers -#define FORCE_INLINE -#define NO_INLINE +# define FORCE_INLINE +# define NO_INLINE -#ifndef ARDUINOJSON_DEPRECATED -#define ARDUINOJSON_DEPRECATED(msg) -#endif +# ifndef ARDUINOJSON_DEPRECATED +# define ARDUINOJSON_DEPRECATED(msg) +# endif #endif #if __cplusplus >= 201103L -#define NOEXCEPT noexcept +# define NOEXCEPT noexcept #else -#define NOEXCEPT throw() +# define NOEXCEPT throw() #endif #if defined(__has_attribute) -#if __has_attribute(no_sanitize) -#define ARDUINOJSON_NO_SANITIZE(check) __attribute__((no_sanitize(check))) +# if __has_attribute(no_sanitize) +# define ARDUINOJSON_NO_SANITIZE(check) __attribute__((no_sanitize(check))) +# else +# define ARDUINOJSON_NO_SANITIZE(check) +# endif #else -#define ARDUINOJSON_NO_SANITIZE(check) -#endif -#else -#define ARDUINOJSON_NO_SANITIZE(check) +# define ARDUINOJSON_NO_SANITIZE(check) #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp index 80048284b..68a2bf8a6 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp @@ -7,8 +7,8 @@ #include "type_traits.hpp" #ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4310) +# pragma warning(push) +# pragma warning(disable : 4310) #endif namespace ARDUINOJSON_NAMESPACE { @@ -41,5 +41,5 @@ struct numeric_limits< } // namespace ARDUINOJSON_NAMESPACE #ifdef _MSC_VER -#pragma warning(pop) +# pragma warning(pop) #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp index a877b4caa..b070628cc 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp @@ -8,27 +8,27 @@ #if ARDUINOJSON_ENABLE_PROGMEM -#include +# include -#ifndef ARDUINOJSON_DEFINE_STATIC_ARRAY -#define ARDUINOJSON_DEFINE_STATIC_ARRAY(type, name, value) \ - static type const name[] PROGMEM = value; -#endif +# ifndef ARDUINOJSON_DEFINE_STATIC_ARRAY +# define ARDUINOJSON_DEFINE_STATIC_ARRAY(type, name, value) \ + static type const name[] PROGMEM = value; +# endif -#ifndef ARDUINOJSON_READ_STATIC_ARRAY -#define ARDUINOJSON_READ_STATIC_ARRAY(type, name, index) \ - pgm_read(name + index) -#endif +# ifndef ARDUINOJSON_READ_STATIC_ARRAY +# define ARDUINOJSON_READ_STATIC_ARRAY(type, name, index) \ + pgm_read(name + index) +# endif #else // i.e. ARDUINOJSON_ENABLE_PROGMEM == 0 -#ifndef ARDUINOJSON_DEFINE_STATIC_ARRAY -#define ARDUINOJSON_DEFINE_STATIC_ARRAY(type, name, value) \ - static type const name[] = value; -#endif +# ifndef ARDUINOJSON_DEFINE_STATIC_ARRAY +# define ARDUINOJSON_DEFINE_STATIC_ARRAY(type, name, value) \ + static type const name[] = value; +# endif -#ifndef ARDUINOJSON_READ_STATIC_ARRAY -#define ARDUINOJSON_READ_STATIC_ARRAY(type, name, index) name[index] -#endif +# ifndef ARDUINOJSON_READ_STATIC_ARRAY +# define ARDUINOJSON_READ_STATIC_ARRAY(type, name, index) name[index] +# endif #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp index 4a8ff4b94..24440b196 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp @@ -20,5 +20,6 @@ #include "type_traits/is_signed.hpp" #include "type_traits/is_unsigned.hpp" #include "type_traits/make_unsigned.hpp" +#include "type_traits/make_void.hpp" #include "type_traits/remove_const.hpp" #include "type_traits/remove_reference.hpp" diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp index 847525a93..b3a073b83 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp @@ -7,9 +7,9 @@ #include "declval.hpp" #ifdef _MSC_VER -#pragma warning(push) +# pragma warning(push) // conversion from 'T' to 'To', possible loss of data -#pragma warning(disable : 4244) +# pragma warning(disable : 4244) #endif // clang-format off @@ -37,7 +37,7 @@ struct is_convertible { } // namespace ARDUINOJSON_NAMESPACE #ifdef _MSC_VER -#pragma warning(pop) +# pragma warning(pop) #endif // clang-format off diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp index b7e9d3d24..fb42f0598 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp @@ -5,15 +5,16 @@ #pragma once #include "integral_constant.hpp" +#include "is_same.hpp" +#include "remove_cv.hpp" namespace ARDUINOJSON_NAMESPACE { -template -struct is_floating_point : false_type {}; +template +struct is_floating_point + : integral_constant< + bool, // + is_same::type>::value || + is_same::type>::value> {}; -template <> -struct is_floating_point : true_type {}; - -template <> -struct is_floating_point : true_type {}; } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp index 26e895c8a..65918cf9c 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp @@ -5,29 +5,33 @@ #pragma once #include + +#include "integral_constant.hpp" #include "is_same.hpp" +#include "remove_cv.hpp" namespace ARDUINOJSON_NAMESPACE { -// A meta-function that returns true if T is an integral type. +// clang-format off template -struct is_integral { - static const bool value = - is_same::value || is_same::value || - is_same::value || is_same::value || - is_same::value || is_same::value || - is_same::value || is_same::value || +struct is_integral : integral_constant::type, signed char>::value || + is_same::type, unsigned char>::value || + is_same::type, signed short>::value || + is_same::type, unsigned short>::value || + is_same::type, signed int>::value || + is_same::type, unsigned int>::value || + is_same::type, signed long>::value || + is_same::type, unsigned long>::value || #if ARDUINOJSON_HAS_LONG_LONG - is_same::value || - is_same::value || + is_same::type, signed long long>::value || + is_same::type, unsigned long long>::value || #endif #if ARDUINOJSON_HAS_INT64 - is_same::value || - is_same::value || + is_same::type, signed __int64>::value || + is_same::type, unsigned __int64>::value || #endif - is_same::value || is_same::value; -}; - -template -struct is_integral : is_integral {}; + is_same::type, char>::value || + is_same::type, bool>::value> {}; +// clang-format on } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp index fbb701cf7..3e064e315 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp @@ -5,39 +5,26 @@ #pragma once #include "integral_constant.hpp" +#include "is_same.hpp" +#include "remove_cv.hpp" + namespace ARDUINOJSON_NAMESPACE { -template -struct is_signed : false_type {}; - -template <> -struct is_signed : true_type {}; - -template <> -struct is_signed : true_type {}; - -template <> -struct is_signed : true_type {}; - -template <> -struct is_signed : true_type {}; - -template <> -struct is_signed : true_type {}; - -template <> -struct is_signed : true_type {}; - -template <> -struct is_signed : true_type {}; - +// clang-format off +template +struct is_signed : integral_constant::type, char>::value || + is_same::type, signed char>::value || + is_same::type, signed short>::value || + is_same::type, signed int>::value || + is_same::type, signed long>::value || #if ARDUINOJSON_HAS_LONG_LONG -template <> -struct is_signed : true_type {}; + is_same::type, signed long long>::value || #endif - #if ARDUINOJSON_HAS_INT64 -template <> -struct is_signed : true_type {}; + is_same::type, signed __int64>::value || #endif + is_same::type, float>::value || + is_same::type, double>::value> {}; +// clang-format on } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp index be2649829..57acff232 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp @@ -5,33 +5,24 @@ #pragma once #include "integral_constant.hpp" +#include "is_same.hpp" +#include "remove_cv.hpp" + namespace ARDUINOJSON_NAMESPACE { -template -struct is_unsigned : false_type {}; - -template <> -struct is_unsigned : true_type {}; - -template <> -struct is_unsigned : true_type {}; - -template <> -struct is_unsigned : true_type {}; - -template <> -struct is_unsigned : true_type {}; - -template <> -struct is_unsigned : true_type {}; - +// clang-format off +template +struct is_unsigned : integral_constant::type, unsigned char>::value || + is_same::type, unsigned short>::value || + is_same::type, unsigned int>::value || + is_same::type, unsigned long>::value || #if ARDUINOJSON_HAS_INT64 -template <> -struct is_unsigned : true_type {}; + is_same::type, unsigned __int64>::value || #endif - #if ARDUINOJSON_HAS_LONG_LONG -template <> -struct is_unsigned : true_type {}; + is_same::type, unsigned long long>::value || #endif + is_same::type, bool>::value> {}; +// clang-format on } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_void.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_void.hpp new file mode 100644 index 000000000..cb2ebde52 --- /dev/null +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_void.hpp @@ -0,0 +1,14 @@ +// ArduinoJson - https://arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#pragma once + +namespace ARDUINOJSON_NAMESPACE { + +template +struct make_void { + typedef void type; +}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_cv.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_cv.hpp new file mode 100644 index 000000000..021e0ec5c --- /dev/null +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_cv.hpp @@ -0,0 +1,27 @@ +// ArduinoJson - https://arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp index 52bd1175d..f9a979855 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp @@ -31,17 +31,17 @@ class Writer { #include #if ARDUINOJSON_ENABLE_STD_STRING -#include +# include #endif #if ARDUINOJSON_ENABLE_ARDUINO_STRING -#include +# include #endif #if ARDUINOJSON_ENABLE_STD_STREAM -#include +# include #endif #if ARDUINOJSON_ENABLE_ARDUINO_PRINT -#include +# include #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp index 5efa6e492..5bb1ebd78 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp @@ -22,10 +22,10 @@ class Writer< ::String, void> { } size_t write(uint8_t c) { - ARDUINOJSON_ASSERT(_size < bufferCapacity); - _buffer[_size++] = static_cast(c); if (_size + 1 >= bufferCapacity) - flush(); + if (flush() != 0) + return 0; + _buffer[_size++] = static_cast(c); return 1; } @@ -36,14 +36,15 @@ class Writer< ::String, void> { return n; } - private: - void flush() { + size_t flush() { ARDUINOJSON_ASSERT(_size < bufferCapacity); _buffer[_size] = 0; - *_destination += _buffer; - _size = 0; + if (_destination->concat(_buffer)) + _size = 0; + return _size; } + private: ::String *_destination; char _buffer[bufferCapacity]; size_t _size; diff --git a/lib/ArduinoJson/src/ArduinoJson/StringStorage/StringCopier.hpp b/lib/ArduinoJson/src/ArduinoJson/StringStorage/StringCopier.hpp index 8b1104b4c..80670aad3 100644 --- a/lib/ArduinoJson/src/ArduinoJson/StringStorage/StringCopier.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/StringStorage/StringCopier.hpp @@ -55,8 +55,12 @@ class StringCopier { private: MemoryPool* _pool; + + // These fields aren't initialized by the constructor but startString() + // + // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.UninitializedObject) char* _ptr; - size_t _size; - size_t _capacity; + // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.UninitializedObject) + size_t _size, _capacity; }; } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/ArduinoStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/ArduinoStringAdapter.hpp similarity index 63% rename from lib/ArduinoJson/src/ArduinoJson/Strings/ArduinoStringAdapter.hpp rename to lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/ArduinoStringAdapter.hpp index 24646ccda..5e4b62403 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/ArduinoStringAdapter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/ArduinoStringAdapter.hpp @@ -7,14 +7,15 @@ #include #include -#include #include +#include namespace ARDUINOJSON_NAMESPACE { -class ArduinoStringAdapter { +template <> +class StringAdapter< ::String> { public: - ArduinoStringAdapter(const ::String& str) : _str(&str) {} + StringAdapter(const ::String& str) : _str(&str) {} void copyTo(char* p, size_t n) const { memcpy(p, _str->c_str(), n); @@ -31,18 +32,10 @@ class ArduinoStringAdapter { return safe_strcmp(me, other); } - bool equals(const char* expected) const { - return compare(expected) == 0; - } - size_t size() const { return _str->length(); } - const char* begin() const { - return _str->c_str(); - } - typedef storage_policies::store_by_copy storage_policy; private: @@ -50,13 +43,10 @@ class ArduinoStringAdapter { }; template <> -struct IsString< ::String> : true_type {}; - -template <> -struct IsString< ::StringSumHelper> : true_type {}; - -inline ArduinoStringAdapter adaptString(const ::String& str) { - return ArduinoStringAdapter(str); -} +class StringAdapter< ::StringSumHelper> : public StringAdapter< ::String> { + public: + StringAdapter< ::StringSumHelper>(const ::String& s) + : StringAdapter< ::String>(s) {} +}; } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/ConstRamStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/ConstRamStringAdapter.hpp similarity index 61% rename from lib/ArduinoJson/src/ArduinoJson/Strings/ConstRamStringAdapter.hpp rename to lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/ConstRamStringAdapter.hpp index ec7d53a6d..1ca7c02cc 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/ConstRamStringAdapter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/ConstRamStringAdapter.hpp @@ -8,23 +8,20 @@ #include // strcmp #include -#include #include +#include namespace ARDUINOJSON_NAMESPACE { -class ConstRamStringAdapter { +template <> +class StringAdapter { public: - ConstRamStringAdapter(const char* str = 0) : _str(str) {} + StringAdapter(const char* str = 0) : _str(str) {} int compare(const char* other) const { return safe_strcmp(_str, other); } - bool equals(const char* expected) const { - return compare(expected) == 0; - } - bool isNull() const { return !_str; } @@ -39,24 +36,16 @@ class ConstRamStringAdapter { return _str; } - const char* begin() const { - return _str; - } - typedef storage_policies::store_by_address storage_policy; protected: const char* _str; }; -template <> -struct IsString : true_type {}; - template -struct IsString : true_type {}; - -inline ConstRamStringAdapter adaptString(const char* str) { - return ConstRamStringAdapter(str); -} +class StringAdapter : public StringAdapter { + public: + StringAdapter(const char* s) : StringAdapter(s) {} +}; } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/FlashStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/FlashStringAdapter.hpp similarity index 62% rename from lib/ArduinoJson/src/ArduinoJson/Strings/FlashStringAdapter.hpp rename to lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/FlashStringAdapter.hpp index 292e348b4..3a958181d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/FlashStringAdapter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/FlashStringAdapter.hpp @@ -5,15 +5,15 @@ #pragma once #include -#include -#include #include +#include namespace ARDUINOJSON_NAMESPACE { -class FlashStringAdapter { +template <> +class StringAdapter { public: - FlashStringAdapter(const __FlashStringHelper* str) : _str(str) {} + StringAdapter(const __FlashStringHelper* str) : _str(str) {} int compare(const char* other) const { if (!other && !_str) @@ -25,10 +25,6 @@ class FlashStringAdapter { return -strcmp_P(other, reinterpret_cast(_str)); } - bool equals(const char* expected) const { - return compare(expected) == 0; - } - bool isNull() const { return !_str; } @@ -43,20 +39,10 @@ class FlashStringAdapter { return strlen_P(reinterpret_cast(_str)); } - FlashStringIterator begin() const { - return FlashStringIterator(_str); - } - typedef storage_policies::store_by_copy storage_policy; private: const __FlashStringHelper* _str; }; -inline FlashStringAdapter adaptString(const __FlashStringHelper* str) { - return FlashStringAdapter(str); -} - -template <> -struct IsString : true_type {}; } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/JsonStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/JsonStringAdapter.hpp new file mode 100644 index 000000000..c34abce5e --- /dev/null +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/JsonStringAdapter.hpp @@ -0,0 +1,27 @@ +// ArduinoJson - https://arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#pragma once + +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +template <> +class StringAdapter : public StringAdapter { + public: + StringAdapter(const String& str) + : StringAdapter(str.c_str()), _isStatic(str.isStatic()) {} + + bool isStatic() const { + return _isStatic; + } + + typedef storage_policies::decide_at_runtime storage_policy; + + private: + bool _isStatic; +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/RamStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/RamStringAdapter.hpp new file mode 100644 index 000000000..f2b01d171 --- /dev/null +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/RamStringAdapter.hpp @@ -0,0 +1,29 @@ +// ArduinoJson - https://arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#pragma once + +#include +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +class StringAdapter::value>::type> + : public StringAdapter { + public: + StringAdapter(const TChar* str) + : StringAdapter(reinterpret_cast(str)) {} + + void copyTo(char* p, size_t n) const { + memcpy(p, _str, n); + } + + typedef ARDUINOJSON_NAMESPACE::storage_policies::store_by_copy storage_policy; +}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/SizedFlashStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/SizedFlashStringAdapter.hpp similarity index 61% rename from lib/ArduinoJson/src/ArduinoJson/Strings/SizedFlashStringAdapter.hpp rename to lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/SizedFlashStringAdapter.hpp index b9cc0bff8..b2d012fce 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/SizedFlashStringAdapter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/SizedFlashStringAdapter.hpp @@ -5,15 +5,15 @@ #pragma once #include -#include -#include #include +#include namespace ARDUINOJSON_NAMESPACE { -class SizedFlashStringAdapter { +template <> +class StringAdapter { public: - SizedFlashStringAdapter(const __FlashStringHelper* str, size_t sz) + StringAdapter(const __FlashStringHelper* str, size_t sz) : _str(str), _size(sz) {} int compare(const char* other) const { @@ -26,10 +26,6 @@ class SizedFlashStringAdapter { return -strncmp_P(other, reinterpret_cast(_str), _size); } - bool equals(const char* expected) const { - return compare(expected) == 0; - } - bool isNull() const { return !_str; } @@ -42,10 +38,6 @@ class SizedFlashStringAdapter { return _size; } - FlashStringIterator begin() const { - return FlashStringIterator(_str); - } - typedef storage_policies::store_by_copy storage_policy; private: @@ -53,8 +45,4 @@ class SizedFlashStringAdapter { size_t _size; }; -inline SizedFlashStringAdapter adaptString(const __FlashStringHelper* str, - size_t sz) { - return SizedFlashStringAdapter(str, sz); -} } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/SizedRamStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/SizedRamStringAdapter.hpp similarity index 61% rename from lib/ArduinoJson/src/ArduinoJson/Strings/SizedRamStringAdapter.hpp rename to lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/SizedRamStringAdapter.hpp index fe23408f8..a18d5ab92 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/SizedRamStringAdapter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/SizedRamStringAdapter.hpp @@ -5,25 +5,22 @@ #pragma once #include -#include #include +#include #include // strcmp namespace ARDUINOJSON_NAMESPACE { -class SizedRamStringAdapter { +template +class StringAdapter { public: - SizedRamStringAdapter(const char* str, size_t n) : _str(str), _size(n) {} + StringAdapter(const char* str, size_t n) : _str(str), _size(n) {} int compare(const char* other) const { return safe_strncmp(_str, other, _size); } - bool equals(const char* expected) const { - return compare(expected) == 0; - } - bool isNull() const { return !_str; } @@ -36,10 +33,6 @@ class SizedRamStringAdapter { return _size; } - const char* begin() const { - return _str; - } - typedef storage_policies::store_by_copy storage_policy; private: @@ -47,9 +40,4 @@ class SizedRamStringAdapter { size_t _size; }; -template -inline SizedRamStringAdapter adaptString(const TChar* str, size_t size) { - return SizedRamStringAdapter(reinterpret_cast(str), size); -} - } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/StdStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/StdStringAdapter.hpp new file mode 100644 index 000000000..4d2d32c52 --- /dev/null +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/StdStringAdapter.hpp @@ -0,0 +1,46 @@ +// ArduinoJson - https://arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#pragma once + +#include +#include +#include + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +class StringAdapter > { + public: + typedef std::basic_string string_type; + + StringAdapter(const string_type& str) : _str(&str) {} + + void copyTo(char* p, size_t n) const { + memcpy(p, _str->c_str(), n); + } + + bool isNull() const { + return false; + } + + int compare(const char* other) const { + if (!other) + return 1; + return _str->compare(other); + } + + size_t size() const { + return _str->size(); + } + + typedef storage_policies::store_by_copy storage_policy; + + private: + const string_type* _str; +}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/StringViewAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/StringViewAdapter.hpp new file mode 100644 index 000000000..787f7c21f --- /dev/null +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/StringViewAdapter.hpp @@ -0,0 +1,44 @@ +// ArduinoJson - https://arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#pragma once + +#include +#include +#include + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template <> +class StringAdapter { + public: + StringAdapter(std::string_view str) : _str(str) {} + + void copyTo(char* p, size_t n) const { + memcpy(p, _str.data(), n); + } + + bool isNull() const { + return false; + } + + int compare(const char* other) const { + if (!other) + return 1; + return _str.compare(other); + } + + size_t size() const { + return _str.size(); + } + + typedef storage_policies::store_by_copy storage_policy; + + private: + std::string_view _str; +}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/FlashStringIterator.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/FlashStringIterator.hpp deleted file mode 100644 index 9a97f3247..000000000 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/FlashStringIterator.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// ArduinoJson - https://arduinojson.org -// Copyright Benoit Blanchon 2014-2021 -// MIT License - -#pragma once - -namespace ARDUINOJSON_NAMESPACE { - -class FlashStringIterator { - public: - explicit FlashStringIterator(const __FlashStringHelper* ptr) - : _ptr(reinterpret_cast(ptr)) {} - - explicit FlashStringIterator(const char* ptr) : _ptr(ptr) {} - - FlashStringIterator operator+(ptrdiff_t d) const { - return FlashStringIterator(_ptr + d); - } - - ptrdiff_t operator-(FlashStringIterator other) const { - return _ptr - other._ptr; - } - - FlashStringIterator operator++(int) { - return FlashStringIterator(_ptr++); - } - - FlashStringIterator operator++() { - return FlashStringIterator(++_ptr); - } - - bool operator!=(FlashStringIterator other) const { - return _ptr != other._ptr; - } - - char operator*() const { - return char(pgm_read_byte(_ptr)); - } - - private: - const char* _ptr; -}; - -} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/IsString.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/IsString.hpp deleted file mode 100644 index af5a91a22..000000000 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/IsString.hpp +++ /dev/null @@ -1,18 +0,0 @@ -// ArduinoJson - https://arduinojson.org -// Copyright Benoit Blanchon 2014-2021 -// MIT License - -#pragma once - -#include - -namespace ARDUINOJSON_NAMESPACE { -template -struct IsString : false_type {}; - -template -struct IsString : IsString {}; - -template -struct IsString : IsString {}; -} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/IsWriteableString.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/IsWriteableString.hpp index 32039e3dd..556c4765a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/IsWriteableString.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/IsWriteableString.hpp @@ -8,11 +8,11 @@ #include #if ARDUINOJSON_ENABLE_ARDUINO_STRING -#include +# include #endif #if ARDUINOJSON_ENABLE_STD_STRING -#include +# include #endif namespace ARDUINOJSON_NAMESPACE { diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/RamStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/RamStringAdapter.hpp deleted file mode 100644 index eded6c97d..000000000 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/RamStringAdapter.hpp +++ /dev/null @@ -1,43 +0,0 @@ -// ArduinoJson - https://arduinojson.org -// Copyright Benoit Blanchon 2014-2021 -// MIT License - -#pragma once - -#include -#include -#include - -namespace ARDUINOJSON_NAMESPACE { - -class RamStringAdapter : public ConstRamStringAdapter { - public: - RamStringAdapter(const char* str) : ConstRamStringAdapter(str) {} - - void copyTo(char* p, size_t n) const { - memcpy(p, _str, n); - } - - typedef ARDUINOJSON_NAMESPACE::storage_policies::store_by_copy storage_policy; -}; - -template -inline RamStringAdapter adaptString(const TChar* str) { - return RamStringAdapter(reinterpret_cast(str)); -} - -inline RamStringAdapter adaptString(char* str) { - return RamStringAdapter(str); -} - -template -struct IsString { - static const bool value = sizeof(TChar) == 1; -}; - -template <> -struct IsString { - static const bool value = false; -}; - -} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/StdStringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/StdStringAdapter.hpp deleted file mode 100644 index ebf4c3969..000000000 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/StdStringAdapter.hpp +++ /dev/null @@ -1,65 +0,0 @@ -// ArduinoJson - https://arduinojson.org -// Copyright Benoit Blanchon 2014-2021 -// MIT License - -#pragma once - -#include -#include -#include - -#include - -namespace ARDUINOJSON_NAMESPACE { - -template -class StdStringAdapter { - public: - StdStringAdapter(const TString& str) : _str(&str) {} - - void copyTo(char* p, size_t n) const { - memcpy(p, _str->c_str(), n); - } - - bool isNull() const { - return false; - } - - int compare(const char* other) const { - if (!other) - return 1; - return _str->compare(other); - } - - bool equals(const char* expected) const { - if (!expected) - return false; - return *_str == expected; - } - - size_t size() const { - return _str->size(); - } - - const char* begin() const { - return _str->c_str(); - } - - typedef storage_policies::store_by_copy storage_policy; - - private: - const TString* _str; -}; - -template -struct IsString > : true_type { -}; - -template -inline StdStringAdapter > -adaptString(const std::basic_string& str) { - return StdStringAdapter >( - str); -} - -} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/String.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/String.hpp index 4f2abdea4..fff4077d9 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/String.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/String.hpp @@ -4,10 +4,6 @@ #pragma once -#include -#include -#include - namespace ARDUINOJSON_NAMESPACE { class String { @@ -53,25 +49,4 @@ class String { bool _isStatic; }; -class StringAdapter : public RamStringAdapter { - public: - StringAdapter(const String& str) - : RamStringAdapter(str.c_str()), _isStatic(str.isStatic()) {} - - bool isStatic() const { - return _isStatic; - } - - typedef storage_policies::decide_at_runtime storage_policy; - - private: - bool _isStatic; -}; - -template <> -struct IsString : true_type {}; - -inline StringAdapter adaptString(const String& str) { - return StringAdapter(str); -} } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/StringAdapter.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/StringAdapter.hpp new file mode 100644 index 000000000..1d55b2123 --- /dev/null +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/StringAdapter.hpp @@ -0,0 +1,32 @@ +// ArduinoJson - https://arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +class StringAdapter; + +template +inline StringAdapter adaptString(const T& str) { + return StringAdapter(str); +} + +template +inline StringAdapter adaptString(const T& str, size_t sz) { + return StringAdapter(str, sz); +} + +template +struct IsString : false_type {}; + +template +struct IsString< + T, typename make_void::storage_policy>::type> + : true_type {}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp b/lib/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp index 3d294d29c..ba763a67f 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp @@ -4,19 +4,24 @@ #pragma once -#include -#include -#include +#include +#include +#include +#include #if ARDUINOJSON_ENABLE_STD_STRING -#include +# include +#endif + +#if ARDUINOJSON_ENABLE_STRING_VIEW +# include #endif #if ARDUINOJSON_ENABLE_ARDUINO_STRING -#include +# include #endif #if ARDUINOJSON_ENABLE_PROGMEM -#include -#include +# include +# include #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp b/lib/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp index 476ca8fdf..88afa526d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp @@ -9,4 +9,9 @@ namespace ARDUINOJSON_NAMESPACE { template struct Converter; +// clang-format off +template +class InvalidConversion; // Error here? See https://arduinojson.org/v6/invalid-conversion/ +// clang-format on + } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp b/lib/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp index 34e12bbb7..33f8c6509 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp @@ -12,9 +12,9 @@ namespace ARDUINOJSON_NAMESPACE { template struct Converter { - static bool toJson(const T& src, VariantRef dst) { + static void toJson(const T& src, VariantRef dst) { // clang-format off - return convertToJson(src, dst); // Error here? See https://arduinojson.org/v6/unsupported-set/ + convertToJson(src, dst); // Error here? See https://arduinojson.org/v6/unsupported-set/ // clang-format on } @@ -38,13 +38,11 @@ template struct Converter< T, typename enable_if::value && !is_same::value && !is_same::value>::type> { - static bool toJson(T src, VariantRef dst) { + static void toJson(T src, VariantRef dst) { VariantData* data = getData(dst); ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T); - if (!data) - return false; - data->setInteger(src); - return true; + if (data) + data->setInteger(src); } static T fromJson(VariantConstRef src) { @@ -61,8 +59,8 @@ struct Converter< template struct Converter::value>::type> { - static bool toJson(T src, VariantRef dst) { - return dst.set(static_cast(src)); + static void toJson(T src, VariantRef dst) { + dst.set(static_cast(src)); } static T fromJson(VariantConstRef src) { @@ -78,12 +76,10 @@ struct Converter::value>::type> { template <> struct Converter { - static bool toJson(bool src, VariantRef dst) { + static void toJson(bool src, VariantRef dst) { VariantData* data = getData(dst); - if (!data) - return false; - data->setBoolean(src); - return true; + if (data) + data->setBoolean(src); } static bool fromJson(VariantConstRef src) { @@ -99,12 +95,10 @@ struct Converter { template struct Converter::value>::type> { - static bool toJson(T src, VariantRef dst) { + static void toJson(T src, VariantRef dst) { VariantData* data = getData(dst); - if (!data) - return false; - data->setFloat(static_cast(src)); - return true; + if (data) + data->setFloat(static_cast(src)); } static T fromJson(VariantConstRef src) { @@ -120,8 +114,8 @@ struct Converter::value>::type> { template <> struct Converter { - static bool toJson(const char* src, VariantRef dst) { - return variantSetString(getData(dst), adaptString(src), getPool(dst)); + static void toJson(const char* src, VariantRef dst) { + variantSetString(getData(dst), adaptString(src), getPool(dst)); } static const char* fromJson(VariantConstRef src) { @@ -163,12 +157,10 @@ canConvertFromJson(VariantConstRef src, const T&) { template <> struct Converter > { - static bool toJson(SerializedValue src, VariantRef dst) { + static void toJson(SerializedValue src, VariantRef dst) { VariantData* data = getData(dst); - if (!data) - return false; - data->setLinkedRaw(src); - return true; + if (data) + data->setLinkedRaw(src); } }; @@ -178,10 +170,11 @@ struct Converter > { template struct Converter, typename enable_if::value>::type> { - static bool toJson(SerializedValue src, VariantRef dst) { + static void toJson(SerializedValue src, VariantRef dst) { VariantData* data = getData(dst); MemoryPool* pool = getPool(dst); - return data != 0 && data->setOwnedRaw(src, pool); + if (data) + data->setOwnedRaw(src, pool); } }; @@ -189,9 +182,8 @@ struct Converter, template <> struct Converter { - static bool toJson(decltype(nullptr), VariantRef dst) { + static void toJson(decltype(nullptr), VariantRef dst) { variantSetNull(getData(dst)); - return true; } static decltype(nullptr) fromJson(VariantConstRef) { return nullptr; @@ -247,20 +239,33 @@ class MemoryPoolPrint : public Print { size_t _capacity; }; -inline bool convertToJson(const ::Printable& src, VariantRef dst) { +inline void convertToJson(const ::Printable& src, VariantRef dst) { MemoryPool* pool = getPool(dst); VariantData* data = getData(dst); if (!pool || !data) - return false; + return; MemoryPoolPrint print(pool); src.printTo(print); if (print.overflowed()) { pool->markAsOverflowed(); data->setNull(); - return false; + return; } data->setStringPointer(print.c_str(), storage_policies::store_by_copy()); - return true; +} + +#endif + +#if ARDUINOJSON_ENABLE_STRING_VIEW + +inline void convertFromJson(VariantConstRef src, std::string_view& dst) { + const char* str = src.as(); + if (str) // the standard doesn't allow passing null to the constructor + dst = std::string_view(str); +} + +inline bool canConvertFromJson(VariantConstRef src, const std::string_view&) { + return src.is(); } #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp index 025ef90a2..4e0471a60 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include namespace ARDUINOJSON_NAMESPACE { diff --git a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp index 82ae745d4..388b7e251 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp @@ -7,17 +7,17 @@ #include #include #include -#include +#include #include // VariantData can't have a constructor (to be a POD), so we have no way to fix // this warning #if defined(__GNUC__) -#if __GNUC__ >= 7 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#pragma GCC diagnostic ignored "-Wuninitialized" -#endif +# if __GNUC__ >= 7 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +# pragma GCC diagnostic ignored "-Wuninitialized" +# endif #endif namespace ARDUINOJSON_NAMESPACE { @@ -33,7 +33,7 @@ class VariantData { // - no virtual // - no inheritance void init() { - _flags = 0; + _flags = VALUE_IS_NULL; } template @@ -103,7 +103,8 @@ class VariantData { case VALUE_IS_OBJECT: return toObject().copyFrom(src._content.asCollection, pool); case VALUE_IS_OWNED_STRING: - return setString(RamStringAdapter(src._content.asString), pool); + return setString(adaptString(const_cast(src._content.asString)), + pool); case VALUE_IS_OWNED_RAW: return setOwnedRaw( serialized(src._content.asRaw.data, src._content.asRaw.size), pool); @@ -362,7 +363,7 @@ class VariantData { } // namespace ARDUINOJSON_NAMESPACE #if defined(__GNUC__) -#if __GNUC__ >= 8 -#pragma GCC diagnostic pop -#endif +# if __GNUC__ >= 8 +# pragma GCC diagnostic pop +# endif #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantImpl.hpp b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantImpl.hpp index b91e58450..ea49a92f6 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantImpl.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantImpl.hpp @@ -140,4 +140,9 @@ inline VariantConstRef operator|(VariantConstRef preferedValue, VariantConstRef defaultValue) { return preferedValue ? preferedValue : defaultValue; } + +// Out of class definition to avoid #1560 +inline bool VariantRef::set(char value) const { + return set(value); +} } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantRef.hpp b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantRef.hpp index 8d4c41088..b05ed90ca 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantRef.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantRef.hpp @@ -85,18 +85,18 @@ class VariantRef : public VariantRefBase, template FORCE_INLINE bool set(const T &value) const { - return Converter::toJson(value, *this); + Converter::toJson(value, *this); + return _pool && !_pool->overflowed(); } - FORCE_INLINE bool ARDUINOJSON_DEPRECATED( + bool ARDUINOJSON_DEPRECATED( "Support for char is deprecated, use int8_t or uint8_t instead") - set(char value) const { - return set(value); - } + set(char value) const; template FORCE_INLINE bool set(T *value) const { - return Converter::toJson(value, *this); + Converter::toJson(value, *this); + return _pool && !_pool->overflowed(); } template @@ -341,16 +341,22 @@ class VariantConstRef : public VariantRefBase, template <> struct Converter { - static bool toJson(VariantRef src, VariantRef dst) { - return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + static void toJson(VariantRef src, VariantRef dst) { + variantCopyFrom(getData(dst), getData(src), getPool(dst)); } + static VariantRef fromJson(VariantRef src) { return src; } + + static InvalidConversion fromJson( + VariantConstRef); + static bool checkJson(VariantRef src) { VariantData *data = getData(src); return !!data; } + static bool checkJson(VariantConstRef) { return false; } @@ -358,8 +364,8 @@ struct Converter { template <> struct Converter { - static bool toJson(VariantConstRef src, VariantRef dst) { - return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + static void toJson(VariantConstRef src, VariantRef dst) { + variantCopyFrom(getData(dst), getData(src), getPool(dst)); } static VariantConstRef fromJson(VariantConstRef src) { diff --git a/lib/ArduinoJson/src/ArduinoJson/version.hpp b/lib/ArduinoJson/src/ArduinoJson/version.hpp index d8aaf77d1..6ddc84486 100644 --- a/lib/ArduinoJson/src/ArduinoJson/version.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/version.hpp @@ -4,7 +4,7 @@ #pragma once -#define ARDUINOJSON_VERSION "6.18.0" +#define ARDUINOJSON_VERSION "6.18.3" #define ARDUINOJSON_VERSION_MAJOR 6 #define ARDUINOJSON_VERSION_MINOR 18 -#define ARDUINOJSON_VERSION_REVISION 0 +#define ARDUINOJSON_VERSION_REVISION 3 diff --git a/lib/AsyncTCP/src/AsyncTCP.cpp b/lib/AsyncTCP/src/AsyncTCP.cpp index 89ff6ee32..aff7842c1 100644 --- a/lib/AsyncTCP/src/AsyncTCP.cpp +++ b/lib/AsyncTCP/src/AsyncTCP.cpp @@ -22,7 +22,7 @@ #include "Arduino.h" #include "AsyncTCP.h" -extern "C"{ +extern "C" { #include "lwip/opt.h" #include "lwip/tcp.h" #include "lwip/inet.h" @@ -36,44 +36,52 @@ extern "C"{ * */ typedef enum { - LWIP_TCP_SENT, LWIP_TCP_RECV, LWIP_TCP_FIN, LWIP_TCP_ERROR, LWIP_TCP_POLL, LWIP_TCP_CLEAR, LWIP_TCP_ACCEPT, LWIP_TCP_CONNECTED, LWIP_TCP_DNS + LWIP_TCP_SENT, + LWIP_TCP_RECV, + LWIP_TCP_FIN, + LWIP_TCP_ERROR, + LWIP_TCP_POLL, + LWIP_TCP_CLEAR, + LWIP_TCP_ACCEPT, + LWIP_TCP_CONNECTED, + LWIP_TCP_DNS } lwip_event_t; typedef struct { - lwip_event_t event; - void *arg; - union { - struct { - void * pcb; - int8_t err; - } connected; - struct { - int8_t err; - } error; - struct { - tcp_pcb * pcb; - uint16_t len; - } sent; - struct { - tcp_pcb * pcb; - pbuf * pb; - int8_t err; - } recv; - struct { - tcp_pcb * pcb; - int8_t err; - } fin; - struct { - tcp_pcb * pcb; - } poll; - struct { - AsyncClient * client; - } accept; - struct { - const char * name; - ip_addr_t addr; - } dns; - }; + lwip_event_t event; + void * arg; + union { + struct { + void * pcb; + int8_t err; + } connected; + struct { + int8_t err; + } error; + struct { + tcp_pcb * pcb; + uint16_t len; + } sent; + struct { + tcp_pcb * pcb; + pbuf * pb; + int8_t err; + } recv; + struct { + tcp_pcb * pcb; + int8_t err; + } fin; + struct { + tcp_pcb * pcb; + } poll; + struct { + AsyncClient * client; + } accept; + struct { + const char * name; + ip_addr_t addr; + } dns; + }; } lwip_event_packet_t; static xQueueHandle _async_queue; @@ -81,122 +89,122 @@ static TaskHandle_t _async_service_task_handle = NULL; SemaphoreHandle_t _slots_lock; -const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP; -static uint32_t _closed_slots[_number_of_closed_slots]; -static uint32_t _closed_index = []() { +const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP; +static uint32_t _closed_slots[_number_of_closed_slots]; +static uint32_t _closed_index = []() { _slots_lock = xSemaphoreCreateBinary(); xSemaphoreGive(_slots_lock); - for (int i = 0; i < _number_of_closed_slots; ++ i) { + for (int i = 0; i < _number_of_closed_slots; ++i) { _closed_slots[i] = 1; } return 1; }(); -static inline bool _init_async_event_queue(){ - if(!_async_queue){ +static inline bool _init_async_event_queue() { + if (!_async_queue) { _async_queue = xQueueCreate(32, sizeof(lwip_event_packet_t *)); - if(!_async_queue){ + if (!_async_queue) { return false; } } return true; } -static inline bool _send_async_event(lwip_event_packet_t ** e){ +static inline bool _send_async_event(lwip_event_packet_t ** e) { return _async_queue && xQueueSend(_async_queue, e, portMAX_DELAY) == pdPASS; } -static inline bool _prepend_async_event(lwip_event_packet_t ** e){ +static inline bool _prepend_async_event(lwip_event_packet_t ** e) { return _async_queue && xQueueSendToFront(_async_queue, e, portMAX_DELAY) == pdPASS; } -static inline bool _get_async_event(lwip_event_packet_t ** e){ +static inline bool _get_async_event(lwip_event_packet_t ** e) { return _async_queue && xQueueReceive(_async_queue, e, portMAX_DELAY) == pdPASS; } -static bool _remove_events_with_arg(void * arg){ +static bool _remove_events_with_arg(void * arg) { lwip_event_packet_t * first_packet = NULL; - lwip_event_packet_t * packet = NULL; + lwip_event_packet_t * packet = NULL; - if(!_async_queue){ + if (!_async_queue) { return false; } //figure out which is the first packet so we can keep the order - while(!first_packet){ - if(xQueueReceive(_async_queue, &first_packet, 0) != pdPASS){ + while (!first_packet) { + if (xQueueReceive(_async_queue, &first_packet, 0) != pdPASS) { return false; } //discard packet if matching - if((int)first_packet->arg == (int)arg){ + if ((int)first_packet->arg == (int)arg) { free(first_packet); first_packet = NULL; - //return first packet to the back of the queue - } else if(xQueueSend(_async_queue, &first_packet, portMAX_DELAY) != pdPASS){ + //return first packet to the back of the queue + } else if (xQueueSend(_async_queue, &first_packet, portMAX_DELAY) != pdPASS) { return false; } } - while(xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet){ - if(xQueueReceive(_async_queue, &packet, 0) != pdPASS){ + while (xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet) { + if (xQueueReceive(_async_queue, &packet, 0) != pdPASS) { return false; } - if((int)packet->arg == (int)arg){ + if ((int)packet->arg == (int)arg) { free(packet); packet = NULL; - } else if(xQueueSend(_async_queue, &packet, portMAX_DELAY) != pdPASS){ + } else if (xQueueSend(_async_queue, &packet, portMAX_DELAY) != pdPASS) { return false; } } return true; } -static void _handle_async_event(lwip_event_packet_t * e){ - if(e->arg == NULL){ +static void _handle_async_event(lwip_event_packet_t * e) { + if (e->arg == NULL) { // do nothing when arg is NULL //ets_printf("event arg == NULL: 0x%08x\n", e->recv.pcb); - } else if(e->event == LWIP_TCP_CLEAR){ + } else if (e->event == LWIP_TCP_CLEAR) { _remove_events_with_arg(e->arg); - } else if(e->event == LWIP_TCP_RECV){ + } else if (e->event == LWIP_TCP_RECV) { //ets_printf("-R: 0x%08x\n", e->recv.pcb); AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err); - } else if(e->event == LWIP_TCP_FIN){ + } else if (e->event == LWIP_TCP_FIN) { //ets_printf("-F: 0x%08x\n", e->fin.pcb); AsyncClient::_s_fin(e->arg, e->fin.pcb, e->fin.err); - } else if(e->event == LWIP_TCP_SENT){ + } else if (e->event == LWIP_TCP_SENT) { //ets_printf("-S: 0x%08x\n", e->sent.pcb); AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len); - } else if(e->event == LWIP_TCP_POLL){ + } else if (e->event == LWIP_TCP_POLL) { //ets_printf("-P: 0x%08x\n", e->poll.pcb); AsyncClient::_s_poll(e->arg, e->poll.pcb); - } else if(e->event == LWIP_TCP_ERROR){ + } else if (e->event == LWIP_TCP_ERROR) { //ets_printf("-E: 0x%08x %d\n", e->arg, e->error.err); AsyncClient::_s_error(e->arg, e->error.err); - } else if(e->event == LWIP_TCP_CONNECTED){ + } else if (e->event == LWIP_TCP_CONNECTED) { //ets_printf("C: 0x%08x 0x%08x %d\n", e->arg, e->connected.pcb, e->connected.err); AsyncClient::_s_connected(e->arg, e->connected.pcb, e->connected.err); - } else if(e->event == LWIP_TCP_ACCEPT){ + } else if (e->event == LWIP_TCP_ACCEPT) { //ets_printf("A: 0x%08x 0x%08x\n", e->arg, e->accept.client); AsyncServer::_s_accepted(e->arg, e->accept.client); - } else if(e->event == LWIP_TCP_DNS){ + } else if (e->event == LWIP_TCP_DNS) { //ets_printf("D: 0x%08x %s = %s\n", e->arg, e->dns.name, ipaddr_ntoa(&e->dns.addr)); AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg); } - free((void*)(e)); + free((void *)(e)); } -static void _async_service_task(void *pvParameters){ +static void _async_service_task(void * pvParameters) { lwip_event_packet_t * packet = NULL; for (;;) { - if(_get_async_event(&packet)){ + if (_get_async_event(&packet)) { #if CONFIG_ASYNC_TCP_USE_WDT - if(esp_task_wdt_add(NULL) != ESP_OK){ + if (esp_task_wdt_add(NULL) != ESP_OK) { log_e("Failed to add async task to WDT"); } #endif _handle_async_event(packet); #if CONFIG_ASYNC_TCP_USE_WDT - if(esp_task_wdt_delete(NULL) != ESP_OK){ + if (esp_task_wdt_delete(NULL) != ESP_OK) { log_e("Failed to remove loop task from WDT"); } #endif @@ -213,13 +221,13 @@ static void _stop_async_task(){ } } */ -static bool _start_async_task(){ - if(!_init_async_event_queue()){ +static bool _start_async_task() { + if (!_init_async_event_queue()) { return false; } - if(!_async_service_task_handle){ + if (!_async_service_task_handle) { xTaskCreateUniversal(_async_service_task, "async_tcp", 8192 * 2, NULL, 3, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE); - if(!_async_service_task_handle){ + if (!_async_service_task_handle) { return false; } } @@ -232,10 +240,10 @@ static bool _start_async_task(){ static int8_t _tcp_clear_events(void * arg) { lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_CLEAR; - e->arg = arg; + e->event = LWIP_TCP_CLEAR; + e->arg = arg; if (!_prepend_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); } return ERR_OK; } @@ -243,12 +251,12 @@ static int8_t _tcp_clear_events(void * arg) { static int8_t _tcp_connected(void * arg, tcp_pcb * pcb, int8_t err) { //ets_printf("+C: 0x%08x\n", pcb); lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_CONNECTED; - e->arg = arg; - e->connected.pcb = pcb; - e->connected.err = err; + e->event = LWIP_TCP_CONNECTED; + e->arg = arg; + e->connected.pcb = pcb; + e->connected.err = err; if (!_prepend_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); } return ERR_OK; } @@ -256,34 +264,34 @@ static int8_t _tcp_connected(void * arg, tcp_pcb * pcb, int8_t err) { static int8_t _tcp_poll(void * arg, struct tcp_pcb * pcb) { //ets_printf("+P: 0x%08x\n", pcb); lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_POLL; - e->arg = arg; - e->poll.pcb = pcb; + e->event = LWIP_TCP_POLL; + e->arg = arg; + e->poll.pcb = pcb; if (!_send_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); } return ERR_OK; } -static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) { +static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf * pb, int8_t err) { lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->arg = arg; - if(pb){ + e->arg = arg; + if (pb) { //ets_printf("+R: 0x%08x\n", pcb); - e->event = LWIP_TCP_RECV; + e->event = LWIP_TCP_RECV; e->recv.pcb = pcb; - e->recv.pb = pb; + e->recv.pb = pb; e->recv.err = err; } else { //ets_printf("+F: 0x%08x\n", pcb); - e->event = LWIP_TCP_FIN; + e->event = LWIP_TCP_FIN; e->fin.pcb = pcb; e->fin.err = err; //close the PCB in LwIP thread AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err); } if (!_send_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); } return ERR_OK; } @@ -291,12 +299,12 @@ static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_ static int8_t _tcp_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { //ets_printf("+S: 0x%08x\n", pcb); lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_SENT; - e->arg = arg; - e->sent.pcb = pcb; - e->sent.len = len; + e->event = LWIP_TCP_SENT; + e->arg = arg; + e->sent.pcb = pcb; + e->sent.len = len; if (!_send_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); } return ERR_OK; } @@ -304,19 +312,19 @@ static int8_t _tcp_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { static void _tcp_error(void * arg, int8_t err) { //ets_printf("+E: 0x%08x\n", arg); lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_ERROR; - e->arg = arg; - e->error.err = err; + e->event = LWIP_TCP_ERROR; + e->arg = arg; + e->error.err = err; if (!_send_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); } } static void _tcp_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) { lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); //ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg); - e->event = LWIP_TCP_DNS; - e->arg = arg; + e->event = LWIP_TCP_DNS; + e->arg = arg; e->dns.name = name; if (ipaddr) { memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr)); @@ -324,18 +332,18 @@ static void _tcp_dns_found(const char * name, struct ip_addr * ipaddr, void * ar memset(&e->dns.addr, 0, sizeof(e->dns.addr)); } if (!_send_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); } } //Used to switch out from LwIP thread static int8_t _tcp_accept(void * arg, AsyncClient * client) { lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_ACCEPT; - e->arg = arg; - e->accept.client = client; + e->event = LWIP_TCP_ACCEPT; + e->arg = arg; + e->accept.client = client; if (!_prepend_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); } return ERR_OK; } @@ -348,76 +356,76 @@ static int8_t _tcp_accept(void * arg, AsyncClient * client) { typedef struct { struct tcpip_api_call_data call; - tcp_pcb * pcb; - int8_t closed_slot; - int8_t err; + tcp_pcb * pcb; + int8_t closed_slot; + int8_t err; union { - struct { - const char* data; - size_t size; - uint8_t apiflags; - } write; - size_t received; - struct { - ip_addr_t * addr; - uint16_t port; - tcp_connected_fn cb; - } connect; - struct { - ip_addr_t * addr; - uint16_t port; - } bind; - uint8_t backlog; + struct { + const char * data; + size_t size; + uint8_t apiflags; + } write; + size_t received; + struct { + ip_addr_t * addr; + uint16_t port; + tcp_connected_fn cb; + } connect; + struct { + ip_addr_t * addr; + uint16_t port; + } bind; + uint8_t backlog; }; } tcp_api_call_t; -static err_t _tcp_output_api(struct tcpip_api_call_data *api_call_msg){ +static err_t _tcp_output_api(struct tcpip_api_call_data * api_call_msg) { tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = ERR_CONN; + if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { msg->err = tcp_output(msg->pcb); } return msg->err; } static esp_err_t _tcp_output(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ + if (!pcb) { return ERR_CONN; } tcp_api_call_t msg; - msg.pcb = pcb; + msg.pcb = pcb; msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data*)&msg); + tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data *)&msg); return msg.err; } -static err_t _tcp_write_api(struct tcpip_api_call_data *api_call_msg){ +static err_t _tcp_write_api(struct tcpip_api_call_data * api_call_msg) { tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = ERR_CONN; + if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags); } return msg->err; } -static esp_err_t _tcp_write(tcp_pcb * pcb, int8_t closed_slot, const char* data, size_t size, uint8_t apiflags) { - if(!pcb){ +static esp_err_t _tcp_write(tcp_pcb * pcb, int8_t closed_slot, const char * data, size_t size, uint8_t apiflags) { + if (!pcb) { return ERR_CONN; } tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.write.data = data; - msg.write.size = size; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + msg.write.data = data; + msg.write.size = size; msg.write.apiflags = apiflags; - tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg); + tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data *)&msg); return msg.err; } -static err_t _tcp_recved_api(struct tcpip_api_call_data *api_call_msg){ +static err_t _tcp_recved_api(struct tcpip_api_call_data * api_call_msg) { tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = ERR_CONN; + if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { msg->err = 0; tcp_recved(msg->pcb, msg->received); } @@ -425,112 +433,112 @@ static err_t _tcp_recved_api(struct tcpip_api_call_data *api_call_msg){ } static esp_err_t _tcp_recved(tcp_pcb * pcb, int8_t closed_slot, size_t len) { - if(!pcb){ + if (!pcb) { return ERR_CONN; } tcp_api_call_t msg; - msg.pcb = pcb; + msg.pcb = pcb; msg.closed_slot = closed_slot; - msg.received = len; - tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg); + msg.received = len; + tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data *)&msg); return msg.err; } -static err_t _tcp_close_api(struct tcpip_api_call_data *api_call_msg){ +static err_t _tcp_close_api(struct tcpip_api_call_data * api_call_msg) { tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = ERR_CONN; + if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { msg->err = tcp_close(msg->pcb); } return msg->err; } static esp_err_t _tcp_close(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ + if (!pcb) { return ERR_CONN; } tcp_api_call_t msg; - msg.pcb = pcb; + msg.pcb = pcb; msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data*)&msg); + tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data *)&msg); return msg.err; } -static err_t _tcp_abort_api(struct tcpip_api_call_data *api_call_msg){ +static err_t _tcp_abort_api(struct tcpip_api_call_data * api_call_msg) { tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = ERR_CONN; + if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { tcp_abort(msg->pcb); } return msg->err; } static esp_err_t _tcp_abort(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ + if (!pcb) { return ERR_CONN; } tcp_api_call_t msg; - msg.pcb = pcb; + msg.pcb = pcb; msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data*)&msg); + tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data *)&msg); return msg.err; } -static err_t _tcp_connect_api(struct tcpip_api_call_data *api_call_msg){ +static err_t _tcp_connect_api(struct tcpip_api_call_data * api_call_msg) { tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb); + msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb); return msg->err; } static esp_err_t _tcp_connect(tcp_pcb * pcb, int8_t closed_slot, ip_addr_t * addr, uint16_t port, tcp_connected_fn cb) { - if(!pcb){ + if (!pcb) { return ESP_FAIL; } tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; + msg.pcb = pcb; + msg.closed_slot = closed_slot; msg.connect.addr = addr; msg.connect.port = port; - msg.connect.cb = cb; - tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg); + msg.connect.cb = cb; + tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data *)&msg); return msg.err; } -static err_t _tcp_bind_api(struct tcpip_api_call_data *api_call_msg){ +static err_t _tcp_bind_api(struct tcpip_api_call_data * api_call_msg) { tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port); + msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port); return msg->err; } static esp_err_t _tcp_bind(tcp_pcb * pcb, ip_addr_t * addr, uint16_t port) { - if(!pcb){ + if (!pcb) { return ESP_FAIL; } tcp_api_call_t msg; - msg.pcb = pcb; + msg.pcb = pcb; msg.closed_slot = -1; - msg.bind.addr = addr; - msg.bind.port = port; - tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg); + msg.bind.addr = addr; + msg.bind.port = port; + tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data *)&msg); return msg.err; } -static err_t _tcp_listen_api(struct tcpip_api_call_data *api_call_msg){ +static err_t _tcp_listen_api(struct tcpip_api_call_data * api_call_msg) { tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = 0; - msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog); + msg->err = 0; + msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog); return msg->err; } static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) { - if(!pcb){ + if (!pcb) { return NULL; } tcp_api_call_t msg; - msg.pcb = pcb; + msg.pcb = pcb; msg.closed_slot = -1; - msg.backlog = backlog?backlog:0xFF; - tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg); + msg.backlog = backlog ? backlog : 0xFF; + tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data *)&msg); return msg.pcb; } @@ -540,34 +548,33 @@ static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) { Async TCP Client */ -AsyncClient::AsyncClient(tcp_pcb* pcb) -: _connect_cb(0) -, _connect_cb_arg(0) -, _discard_cb(0) -, _discard_cb_arg(0) -, _sent_cb(0) -, _sent_cb_arg(0) -, _error_cb(0) -, _error_cb_arg(0) -, _recv_cb(0) -, _recv_cb_arg(0) -, _pb_cb(0) -, _pb_cb_arg(0) -, _timeout_cb(0) -, _timeout_cb_arg(0) -, _pcb_busy(false) -, _pcb_sent_at(0) -, _ack_pcb(true) -, _rx_last_packet(0) -, _rx_since_timeout(0) -, _ack_timeout(ASYNC_MAX_ACK_TIME) -, _connect_port(0) -, prev(NULL) -, next(NULL) -{ - _pcb = pcb; +AsyncClient::AsyncClient(tcp_pcb * pcb) + : _connect_cb(0) + , _connect_cb_arg(0) + , _discard_cb(0) + , _discard_cb_arg(0) + , _sent_cb(0) + , _sent_cb_arg(0) + , _error_cb(0) + , _error_cb_arg(0) + , _recv_cb(0) + , _recv_cb_arg(0) + , _pb_cb(0) + , _pb_cb_arg(0) + , _timeout_cb(0) + , _timeout_cb_arg(0) + , _pcb_busy(false) + , _pcb_sent_at(0) + , _ack_pcb(true) + , _rx_last_packet(0) + , _rx_since_timeout(0) + , _ack_timeout(ASYNC_MAX_ACK_TIME) + , _connect_port(0) + , prev(NULL) + , next(NULL) { + _pcb = pcb; _closed_slot = -1; - if(_pcb){ + if (_pcb) { _allocate_closed_slot(); _rx_last_packet = millis(); tcp_arg(_pcb, this); @@ -578,8 +585,8 @@ AsyncClient::AsyncClient(tcp_pcb* pcb) } } -AsyncClient::~AsyncClient(){ - if(_pcb) { +AsyncClient::~AsyncClient() { + if (_pcb) { _close(); } _free_closed_slot(); @@ -589,12 +596,12 @@ AsyncClient::~AsyncClient(){ * Operators * */ -AsyncClient& AsyncClient::operator=(const AsyncClient& other){ +AsyncClient & AsyncClient::operator=(const AsyncClient & other) { if (_pcb) { _close(); } - _pcb = other._pcb; + _pcb = other._pcb; _closed_slot = other._closed_slot; if (_pcb) { _rx_last_packet = millis(); @@ -607,20 +614,20 @@ AsyncClient& AsyncClient::operator=(const AsyncClient& other){ return *this; } -bool AsyncClient::operator==(const AsyncClient &other) { +bool AsyncClient::operator==(const AsyncClient & other) { return _pcb == other._pcb; } -AsyncClient & AsyncClient::operator+=(const AsyncClient &other) { - if(next == NULL){ - next = (AsyncClient*)(&other); +AsyncClient & AsyncClient::operator+=(const AsyncClient & other) { + if (next == NULL) { + next = (AsyncClient *)(&other); next->prev = this; } else { - AsyncClient *c = next; - while(c->next != NULL) { + AsyncClient * c = next; + while (c->next != NULL) { c = c->next; } - c->next =(AsyncClient*)(&other); + c->next = (AsyncClient *)(&other); c->next->prev = c; } return *this; @@ -630,43 +637,43 @@ AsyncClient & AsyncClient::operator+=(const AsyncClient &other) { * Callback Setters * */ -void AsyncClient::onConnect(AcConnectHandler cb, void* arg){ - _connect_cb = cb; +void AsyncClient::onConnect(AcConnectHandler cb, void * arg) { + _connect_cb = cb; _connect_cb_arg = arg; } -void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg){ - _discard_cb = cb; +void AsyncClient::onDisconnect(AcConnectHandler cb, void * arg) { + _discard_cb = cb; _discard_cb_arg = arg; } -void AsyncClient::onAck(AcAckHandler cb, void* arg){ - _sent_cb = cb; +void AsyncClient::onAck(AcAckHandler cb, void * arg) { + _sent_cb = cb; _sent_cb_arg = arg; } -void AsyncClient::onError(AcErrorHandler cb, void* arg){ - _error_cb = cb; +void AsyncClient::onError(AcErrorHandler cb, void * arg) { + _error_cb = cb; _error_cb_arg = arg; } -void AsyncClient::onData(AcDataHandler cb, void* arg){ - _recv_cb = cb; +void AsyncClient::onData(AcDataHandler cb, void * arg) { + _recv_cb = cb; _recv_cb_arg = arg; } -void AsyncClient::onPacket(AcPacketHandler cb, void* arg){ - _pb_cb = cb; - _pb_cb_arg = arg; +void AsyncClient::onPacket(AcPacketHandler cb, void * arg) { + _pb_cb = cb; + _pb_cb_arg = arg; } -void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg){ - _timeout_cb = cb; +void AsyncClient::onTimeout(AcTimeoutHandler cb, void * arg) { + _timeout_cb = cb; _timeout_cb_arg = arg; } -void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ - _poll_cb = cb; +void AsyncClient::onPoll(AcConnectHandler cb, void * arg) { + _poll_cb = cb; _poll_cb_arg = arg; } @@ -674,22 +681,18 @@ void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ * Main Public Methods * */ -bool AsyncClient::connect(IPAddress ip, uint16_t port){ - if (_pcb){ +bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) { + if (_pcb) { log_w("already connected, state %d", _pcb->state); return false; } - if(!_start_async_task()){ + if (!_start_async_task()) { log_e("failed to start task"); return false; } - ip_addr_t addr; - addr.type = IPADDR_TYPE_V4; - addr.u_addr.ip4.addr = ip; - - tcp_pcb* pcb = tcp_new_ip_type(IPADDR_TYPE_V4); - if (!pcb){ + tcp_pcb * pcb = tcp_new_ip_type(addr.type); + if (!pcb) { log_e("pcb == NULL"); return false; } @@ -699,23 +702,41 @@ bool AsyncClient::connect(IPAddress ip, uint16_t port){ tcp_recv(pcb, &_tcp_recv); tcp_sent(pcb, &_tcp_sent); tcp_poll(pcb, &_tcp_poll, 1); - //_tcp_connect(pcb, &addr, port,(tcp_connected_fn)&_s_connected); - _tcp_connect(pcb, _closed_slot, &addr, port,(tcp_connected_fn)&_tcp_connected); + _tcp_connect(pcb, _closed_slot, &addr, port, (tcp_connected_fn)&_tcp_connected); return true; } -bool AsyncClient::connect(const char* host, uint16_t port){ +bool AsyncClient::connect(IPAddress ip, uint16_t port) { ip_addr_t addr; - - if(!_start_async_task()){ - log_e("failed to start task"); - return false; + addr.type = IPADDR_TYPE_V4; + addr.u_addr.ip4.addr = ip; + + return _connect(addr, port); +} + +bool AsyncClient::connect(IPv6Address ip, uint16_t port) { + ip_addr_t addr; + addr.type = IPADDR_TYPE_V6; + memcpy(addr.u_addr.ip6.addr, static_cast(ip), sizeof(uint32_t) * 4); + + return _connect(addr, port); +} + +bool AsyncClient::connect(const char * host, uint16_t port) { + ip_addr_t addr; + + if (!_start_async_task()) { + log_e("failed to start task"); + return false; } - + err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this); - if(err == ERR_OK) { + if (err == ERR_OK) { + if (addr.type == IPADDR_TYPE_V6) { + return connect(IPv6Address(addr.u_addr.ip6.addr), port); + } return connect(IPAddress(addr.u_addr.ip4.addr), port); - } else if(err == ERR_INPROGRESS) { + } else if (err == ERR_INPROGRESS) { _connect_port = port; return true; } @@ -723,82 +744,82 @@ bool AsyncClient::connect(const char* host, uint16_t port){ return false; } -void AsyncClient::close(bool now){ - if(_pcb){ +void AsyncClient::close(bool now) { + if (_pcb) { _tcp_recved(_pcb, _closed_slot, _rx_ack_len); } _close(); } -int8_t AsyncClient::abort(){ - if(_pcb) { - _tcp_abort(_pcb, _closed_slot ); +int8_t AsyncClient::abort() { + if (_pcb) { + _tcp_abort(_pcb, _closed_slot); _pcb = NULL; } return ERR_ABRT; } -size_t AsyncClient::space(){ - if((_pcb != NULL) && (_pcb->state == 4)){ +size_t AsyncClient::space() { + if ((_pcb != NULL) && (_pcb->state == 4)) { return tcp_sndbuf(_pcb); } return 0; } -size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { - if(!_pcb || size == 0 || data == NULL) { +size_t AsyncClient::add(const char * data, size_t size, uint8_t apiflags) { + if (!_pcb || size == 0 || data == NULL) { return 0; } size_t room = space(); - if(!room) { + if (!room) { return 0; } size_t will_send = (room < size) ? room : size; - int8_t err = ERR_OK; - err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); - if(err != ERR_OK) { + int8_t err = ERR_OK; + err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); + if (err != ERR_OK) { return 0; } return will_send; } -bool AsyncClient::send(){ +bool AsyncClient::send() { int8_t err = ERR_OK; - err = _tcp_output(_pcb, _closed_slot); - if(err == ERR_OK){ - _pcb_busy = true; + err = _tcp_output(_pcb, _closed_slot); + if (err == ERR_OK) { + _pcb_busy = true; _pcb_sent_at = millis(); return true; } return false; } -size_t AsyncClient::ack(size_t len){ - if(len > _rx_ack_len) +size_t AsyncClient::ack(size_t len) { + if (len > _rx_ack_len) len = _rx_ack_len; - if(len){ + if (len) { _tcp_recved(_pcb, _closed_slot, len); } _rx_ack_len -= len; return len; } -void AsyncClient::ackPacket(struct pbuf * pb){ - if(!pb){ - return; - } - _tcp_recved(_pcb, _closed_slot, pb->len); - pbuf_free(pb); +void AsyncClient::ackPacket(struct pbuf * pb) { + if (!pb) { + return; + } + _tcp_recved(_pcb, _closed_slot, pb->len); + pbuf_free(pb); } /* * Main Private Methods * */ -int8_t AsyncClient::_close(){ +int8_t AsyncClient::_close() { //ets_printf("X: 0x%08x\n", (uint32_t)this); int8_t err = ERR_OK; - if(_pcb) { + if (_pcb) { //log_i(""); tcp_arg(_pcb, NULL); tcp_sent(_pcb, NULL); @@ -807,24 +828,24 @@ int8_t AsyncClient::_close(){ tcp_poll(_pcb, NULL, 0); _tcp_clear_events(this); err = _tcp_close(_pcb, _closed_slot); - if(err != ERR_OK) { + if (err != ERR_OK) { err = abort(); } _pcb = NULL; - if(_discard_cb) { + if (_discard_cb) { _discard_cb(_discard_cb_arg, this); } } return err; } -void AsyncClient::_allocate_closed_slot(){ +void AsyncClient::_allocate_closed_slot() { xSemaphoreTake(_slots_lock, portMAX_DELAY); uint32_t closed_slot_min_index = 0; - for (int i = 0; i < _number_of_closed_slots; ++ i) { + for (int i = 0; i < _number_of_closed_slots; ++i) { if ((_closed_slot == -1 || _closed_slots[i] <= closed_slot_min_index) && _closed_slots[i] != 0) { closed_slot_min_index = _closed_slots[i]; - _closed_slot = i; + _closed_slot = i; } } if (_closed_slot != -1) { @@ -833,11 +854,11 @@ void AsyncClient::_allocate_closed_slot(){ xSemaphoreGive(_slots_lock); } -void AsyncClient::_free_closed_slot(){ +void AsyncClient::_free_closed_slot() { if (_closed_slot != -1) { _closed_slots[_closed_slot] = _closed_index; - _closed_slot = -1; - ++ _closed_index; + _closed_slot = -1; + ++_closed_index; } } @@ -845,25 +866,25 @@ void AsyncClient::_free_closed_slot(){ * Private Callbacks * */ -int8_t AsyncClient::_connected(void* pcb, int8_t err){ - _pcb = reinterpret_cast(pcb); - if(_pcb){ +int8_t AsyncClient::_connected(void * pcb, int8_t err) { + _pcb = reinterpret_cast(pcb); + if (_pcb) { _rx_last_packet = millis(); - _pcb_busy = false; -// tcp_recv(_pcb, &_tcp_recv); -// tcp_sent(_pcb, &_tcp_sent); -// tcp_poll(_pcb, &_tcp_poll, 1); + _pcb_busy = false; + // tcp_recv(_pcb, &_tcp_recv); + // tcp_sent(_pcb, &_tcp_sent); + // tcp_poll(_pcb, &_tcp_poll, 1); } - if(_connect_cb) { + if (_connect_cb) { _connect_cb(_connect_cb_arg, this); } return ERR_OK; } void AsyncClient::_error(int8_t err) { - if(_pcb){ + if (_pcb) { tcp_arg(_pcb, NULL); - if(_pcb->state == LISTEN) { + if (_pcb->state == LISTEN) { tcp_sent(_pcb, NULL); tcp_recv(_pcb, NULL); tcp_err(_pcb, NULL); @@ -871,28 +892,28 @@ void AsyncClient::_error(int8_t err) { } _pcb = NULL; } - if(_error_cb) { + if (_error_cb) { _error_cb(_error_cb_arg, this, err); } - if(_discard_cb) { + if (_discard_cb) { _discard_cb(_discard_cb_arg, this); } } //In LwIP Thread -int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { - if(!_pcb || pcb != _pcb){ +int8_t AsyncClient::_lwip_fin(tcp_pcb * pcb, int8_t err) { + if (!_pcb || pcb != _pcb) { log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); return ERR_OK; } tcp_arg(_pcb, NULL); - if(_pcb->state == LISTEN) { + if (_pcb->state == LISTEN) { tcp_sent(_pcb, NULL); tcp_recv(_pcb, NULL); tcp_err(_pcb, NULL); tcp_poll(_pcb, NULL, 0); } - if(tcp_close(_pcb) != ERR_OK) { + if (tcp_close(_pcb) != ERR_OK) { tcp_abort(_pcb); } _free_closed_slot(); @@ -901,41 +922,41 @@ int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { } //In Async Thread -int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) { +int8_t AsyncClient::_fin(tcp_pcb * pcb, int8_t err) { _tcp_clear_events(this); - if(_discard_cb) { + if (_discard_cb) { _discard_cb(_discard_cb_arg, this); } return ERR_OK; } -int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) { +int8_t AsyncClient::_sent(tcp_pcb * pcb, uint16_t len) { _rx_last_packet = millis(); //log_i("%u", len); _pcb_busy = false; - if(_sent_cb) { + if (_sent_cb) { _sent_cb(_sent_cb_arg, this, len, (millis() - _pcb_sent_at)); } return ERR_OK; } -int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { - while(pb != NULL) { +int8_t AsyncClient::_recv(tcp_pcb * pcb, pbuf * pb, int8_t err) { + while (pb != NULL) { _rx_last_packet = millis(); //we should not ack before we assimilate the data _ack_pcb = true; - pbuf *b = pb; - pb = b->next; - b->next = NULL; - if(_pb_cb){ + pbuf * b = pb; + pb = b->next; + b->next = NULL; + if (_pb_cb) { _pb_cb(_pb_cb_arg, this, b); } else { - if(_recv_cb) { + if (_recv_cb) { _recv_cb(_recv_cb_arg, this, b->payload, b->len); } - if(!_ack_pcb) { + if (!_ack_pcb) { _rx_ack_len += b->len; - } else if(_pcb) { + } else if (_pcb) { _tcp_recved(_pcb, _closed_slot, b->len); } pbuf_free(b); @@ -944,12 +965,12 @@ int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { return ERR_OK; } -int8_t AsyncClient::_poll(tcp_pcb* pcb){ - if(!_pcb){ +int8_t AsyncClient::_poll(tcp_pcb * pcb) { + if (!_pcb) { log_w("pcb is NULL"); return ERR_OK; } - if(pcb != _pcb){ + if (pcb != _pcb) { log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); return ERR_OK; } @@ -957,34 +978,36 @@ int8_t AsyncClient::_poll(tcp_pcb* pcb){ uint32_t now = millis(); // ACK Timeout - if(_pcb_busy && _ack_timeout && (now - _pcb_sent_at) >= _ack_timeout){ + if (_pcb_busy && _ack_timeout && (now - _pcb_sent_at) >= _ack_timeout) { _pcb_busy = false; log_w("ack timeout %d", pcb->state); - if(_timeout_cb) + if (_timeout_cb) _timeout_cb(_timeout_cb_arg, this, (now - _pcb_sent_at)); return ERR_OK; } // RX Timeout - if(_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)){ + if (_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)) { log_w("rx timeout %d", pcb->state); _close(); return ERR_OK; } // Everything is fine - if(_poll_cb) { + if (_poll_cb) { _poll_cb(_poll_cb_arg, this); } return ERR_OK; } -void AsyncClient::_dns_found(struct ip_addr *ipaddr){ - if(ipaddr && ipaddr->u_addr.ip4.addr){ +void AsyncClient::_dns_found(struct ip_addr * ipaddr) { + if (ipaddr && ipaddr->u_addr.ip4.addr) { connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port); + } else if (ipaddr && ipaddr->u_addr.ip6.addr) { + connect(IPv6Address(ipaddr->u_addr.ip6.addr), _connect_port); } else { - if(_error_cb) { + if (_error_cb) { _error_cb(_error_cb_arg, this, -55); } - if(_discard_cb) { + if (_discard_cb) { _discard_cb(_discard_cb_arg, this); } } @@ -998,95 +1021,113 @@ void AsyncClient::stop() { close(false); } -bool AsyncClient::free(){ - if(!_pcb) { +bool AsyncClient::free() { + if (!_pcb) { return true; } - if(_pcb->state == 0 || _pcb->state > 4) { + if (_pcb->state == 0 || _pcb->state > 4) { return true; } return false; } -size_t AsyncClient::write(const char* data) { - if(data == NULL) { +size_t AsyncClient::write(const char * data) { + if (data == NULL) { return 0; } return write(data, strlen(data)); } -size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { +size_t AsyncClient::write(const char * data, size_t size, uint8_t apiflags) { size_t will_send = add(data, size, apiflags); - if(!will_send || !send()) { + if (!will_send || !send()) { return 0; } return will_send; } -void AsyncClient::setRxTimeout(uint32_t timeout){ +void AsyncClient::setRxTimeout(uint32_t timeout) { _rx_since_timeout = timeout; } -uint32_t AsyncClient::getRxTimeout(){ +uint32_t AsyncClient::getRxTimeout() { return _rx_since_timeout; } -uint32_t AsyncClient::getAckTimeout(){ +uint32_t AsyncClient::getAckTimeout() { return _ack_timeout; } -void AsyncClient::setAckTimeout(uint32_t timeout){ +void AsyncClient::setAckTimeout(uint32_t timeout) { _ack_timeout = timeout; } -void AsyncClient::setNoDelay(bool nodelay){ - if(!_pcb) { +void AsyncClient::setNoDelay(bool nodelay) { + if (!_pcb) { return; } - if(nodelay) { + if (nodelay) { tcp_nagle_disable(_pcb); } else { tcp_nagle_enable(_pcb); } } -bool AsyncClient::getNoDelay(){ - if(!_pcb) { +bool AsyncClient::getNoDelay() { + if (!_pcb) { return false; } return tcp_nagle_disabled(_pcb); } -uint16_t AsyncClient::getMss(){ - if(!_pcb) { +uint16_t AsyncClient::getMss() { + if (!_pcb) { return 0; } return tcp_mss(_pcb); } uint32_t AsyncClient::getRemoteAddress() { - if(!_pcb) { + if (!_pcb) { return 0; } return _pcb->remote_ip.u_addr.ip4.addr; } +ip6_addr_t AsyncClient::getRemoteAddress6() { + if (!_pcb) { + ip6_addr_t nulladdr; + ip6_addr_set_zero(&nulladdr); + return nulladdr; + } + return _pcb->remote_ip.u_addr.ip6; +} + uint16_t AsyncClient::getRemotePort() { - if(!_pcb) { + if (!_pcb) { return 0; } return _pcb->remote_port; } uint32_t AsyncClient::getLocalAddress() { - if(!_pcb) { + if (!_pcb) { return 0; } return _pcb->local_ip.u_addr.ip4.addr; } +ip6_addr_t AsyncClient::getLocalAddress6() { + if (!_pcb) { + ip6_addr_t nulladdr; + ip6_addr_set_zero(&nulladdr); + return nulladdr; + } + return _pcb->local_ip.u_addr.ip6; +} + uint16_t AsyncClient::getLocalPort() { - if(!_pcb) { + if (!_pcb) { return 0; } return _pcb->local_port; @@ -1096,6 +1137,10 @@ IPAddress AsyncClient::remoteIP() { return IPAddress(getRemoteAddress()); } +IPv6Address AsyncClient::remoteIP6() { + return IPv6Address(getRemoteAddress6().addr); +} + uint16_t AsyncClient::remotePort() { return getRemotePort(); } @@ -1104,93 +1149,127 @@ IPAddress AsyncClient::localIP() { return IPAddress(getLocalAddress()); } +IPv6Address AsyncClient::localIP6() { + return IPv6Address(getLocalAddress6().addr); +} + uint16_t AsyncClient::localPort() { return getLocalPort(); } uint8_t AsyncClient::state() { - if(!_pcb) { + if (!_pcb) { return 0; } return _pcb->state; } -bool AsyncClient::connected(){ +bool AsyncClient::connected() { if (!_pcb) { return false; } return _pcb->state == 4; } -bool AsyncClient::connecting(){ +bool AsyncClient::connecting() { if (!_pcb) { return false; } return _pcb->state > 0 && _pcb->state < 4; } -bool AsyncClient::disconnecting(){ +bool AsyncClient::disconnecting() { if (!_pcb) { return false; } return _pcb->state > 4 && _pcb->state < 10; } -bool AsyncClient::disconnected(){ +bool AsyncClient::disconnected() { if (!_pcb) { return true; } return _pcb->state == 0 || _pcb->state == 10; } -bool AsyncClient::freeable(){ +bool AsyncClient::freeable() { if (!_pcb) { return true; } return _pcb->state == 0 || _pcb->state > 4; } -bool AsyncClient::canSend(){ +bool AsyncClient::canSend() { return space() > 0; } -const char * AsyncClient::errorToString(int8_t error){ - switch(error){ - case ERR_OK: return "OK"; - case ERR_MEM: return "Out of memory error"; - case ERR_BUF: return "Buffer error"; - case ERR_TIMEOUT: return "Timeout"; - case ERR_RTE: return "Routing problem"; - case ERR_INPROGRESS: return "Operation in progress"; - case ERR_VAL: return "Illegal value"; - case ERR_WOULDBLOCK: return "Operation would block"; - case ERR_USE: return "Address in use"; - case ERR_ALREADY: return "Already connected"; - case ERR_CONN: return "Not connected"; - case ERR_IF: return "Low-level netif error"; - case ERR_ABRT: return "Connection aborted"; - case ERR_RST: return "Connection reset"; - case ERR_CLSD: return "Connection closed"; - case ERR_ARG: return "Illegal argument"; - case -55: return "DNS failed"; - default: return "UNKNOWN"; +const char * AsyncClient::errorToString(int8_t error) { + switch (error) { + case ERR_OK: + return "OK"; + case ERR_MEM: + return "Out of memory error"; + case ERR_BUF: + return "Buffer error"; + case ERR_TIMEOUT: + return "Timeout"; + case ERR_RTE: + return "Routing problem"; + case ERR_INPROGRESS: + return "Operation in progress"; + case ERR_VAL: + return "Illegal value"; + case ERR_WOULDBLOCK: + return "Operation would block"; + case ERR_USE: + return "Address in use"; + case ERR_ALREADY: + return "Already connected"; + case ERR_CONN: + return "Not connected"; + case ERR_IF: + return "Low-level netif error"; + case ERR_ABRT: + return "Connection aborted"; + case ERR_RST: + return "Connection reset"; + case ERR_CLSD: + return "Connection closed"; + case ERR_ARG: + return "Illegal argument"; + case -55: + return "DNS failed"; + default: + return "UNKNOWN"; } } -const char * AsyncClient::stateToString(){ - switch(state()){ - case 0: return "Closed"; - case 1: return "Listen"; - case 2: return "SYN Sent"; - case 3: return "SYN Received"; - case 4: return "Established"; - case 5: return "FIN Wait 1"; - case 6: return "FIN Wait 2"; - case 7: return "Close Wait"; - case 8: return "Closing"; - case 9: return "Last ACK"; - case 10: return "Time Wait"; - default: return "UNKNOWN"; +const char * AsyncClient::stateToString() { + switch (state()) { + case 0: + return "Closed"; + case 1: + return "Listen"; + case 2: + return "SYN Sent"; + case 3: + return "SYN Received"; + case 4: + return "Established"; + case 5: + return "FIN Wait 1"; + case 6: + return "FIN Wait 2"; + case 7: + return "Close Wait"; + case 8: + return "Closing"; + case 9: + return "Last ACK"; + case 10: + return "Time Wait"; + default: + return "UNKNOWN"; } } @@ -1198,36 +1277,36 @@ const char * AsyncClient::stateToString(){ * Static Callbacks (LwIP C2C++ interconnect) * */ -void AsyncClient::_s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg){ - reinterpret_cast(arg)->_dns_found(ipaddr); +void AsyncClient::_s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) { + reinterpret_cast(arg)->_dns_found(ipaddr); } int8_t AsyncClient::_s_poll(void * arg, struct tcp_pcb * pcb) { - return reinterpret_cast(arg)->_poll(pcb); + return reinterpret_cast(arg)->_poll(pcb); } -int8_t AsyncClient::_s_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) { - return reinterpret_cast(arg)->_recv(pcb, pb, err); +int8_t AsyncClient::_s_recv(void * arg, struct tcp_pcb * pcb, struct pbuf * pb, int8_t err) { + return reinterpret_cast(arg)->_recv(pcb, pb, err); } int8_t AsyncClient::_s_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_fin(pcb, err); + return reinterpret_cast(arg)->_fin(pcb, err); } int8_t AsyncClient::_s_lwip_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_lwip_fin(pcb, err); + return reinterpret_cast(arg)->_lwip_fin(pcb, err); } int8_t AsyncClient::_s_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { - return reinterpret_cast(arg)->_sent(pcb, len); + return reinterpret_cast(arg)->_sent(pcb, len); } void AsyncClient::_s_error(void * arg, int8_t err) { - reinterpret_cast(arg)->_error(err); + reinterpret_cast(arg)->_error(err); } -int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err){ - return reinterpret_cast(arg)->_connected(pcb, err); +int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err) { + return reinterpret_cast(arg)->_connected(pcb, err); } /* @@ -1235,51 +1314,75 @@ int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err){ */ AsyncServer::AsyncServer(IPAddress addr, uint16_t port) -: _port(port) -, _addr(addr) -, _noDelay(false) -, _pcb(0) -, _connect_cb(0) -, _connect_cb_arg(0) -{} + : _port(port) + , _bind4(true) + , _addr(addr) + , _noDelay(false) + , _pcb(0) + , _connect_cb(0) + , _connect_cb_arg(0) { +} + +AsyncServer::AsyncServer(IPv6Address addr, uint16_t port) + : _port(port) + , _bind6(true) + , _addr6(addr) + , _noDelay(false) + , _pcb(0) + , _connect_cb(0) + , _connect_cb_arg(0) { +} AsyncServer::AsyncServer(uint16_t port) -: _port(port) -, _addr((uint32_t) IPADDR_ANY) -, _noDelay(false) -, _pcb(0) -, _connect_cb(0) -, _connect_cb_arg(0) -{} + : _port(port) + , _bind4(true) + , _bind6(true) + , _addr((uint32_t)IPADDR_ANY) + , _addr6() + , _noDelay(false) + , _pcb(0) + , _connect_cb(0) + , _connect_cb_arg(0) { +} -AsyncServer::~AsyncServer(){ +AsyncServer::~AsyncServer() { end(); } -void AsyncServer::onClient(AcConnectHandler cb, void* arg){ - _connect_cb = cb; +void AsyncServer::onClient(AcConnectHandler cb, void * arg) { + _connect_cb = cb; _connect_cb_arg = arg; } -void AsyncServer::begin(){ - if(_pcb) { +void AsyncServer::begin() { + if (_pcb) { return; } - if(!_start_async_task()){ + if (!_start_async_task()) { log_e("failed to start task"); return; } - int8_t err; - _pcb = tcp_new_ip_type(IPADDR_TYPE_V4); - if (!_pcb){ + int8_t err, bind_type; + + if (_bind4 && _bind6) { + bind_type = IPADDR_TYPE_ANY; + } else if (_bind6) { + bind_type = IPADDR_TYPE_V6; + } else { + bind_type = IPADDR_TYPE_V4; + } + + _pcb = tcp_new_ip_type(bind_type); + if (!_pcb) { log_e("_pcb == NULL"); return; } ip_addr_t local_addr; - local_addr.type = IPADDR_TYPE_V4; - local_addr.u_addr.ip4.addr = (uint32_t) _addr; + local_addr.type = bind_type; + local_addr.u_addr.ip4.addr = (uint32_t)_addr; + memcpy(local_addr.u_addr.ip6.addr, static_cast(_addr6), sizeof(uint32_t) * 4); err = _tcp_bind(_pcb, &local_addr, _port); if (err != ERR_OK) { @@ -1289,20 +1392,20 @@ void AsyncServer::begin(){ } static uint8_t backlog = 5; - _pcb = _tcp_listen_with_backlog(_pcb, backlog); + _pcb = _tcp_listen_with_backlog(_pcb, backlog); if (!_pcb) { log_e("listen_pcb == NULL"); return; } - tcp_arg(_pcb, (void*) this); + tcp_arg(_pcb, (void *)this); tcp_accept(_pcb, &_s_accept); } -void AsyncServer::end(){ - if(_pcb){ +void AsyncServer::end() { + if (_pcb) { tcp_arg(_pcb, NULL); tcp_accept(_pcb, NULL); - if(tcp_close(_pcb) != ERR_OK){ + if (tcp_close(_pcb) != ERR_OK) { _tcp_abort(_pcb, -1); } _pcb = NULL; @@ -1310,48 +1413,48 @@ void AsyncServer::end(){ } //runs on LwIP thread -int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err){ +int8_t AsyncServer::_accept(tcp_pcb * pcb, int8_t err) { //ets_printf("+A: 0x%08x\n", pcb); - if(_connect_cb){ - AsyncClient *c = new AsyncClient(pcb); - if(c){ + if (_connect_cb) { + AsyncClient * c = new AsyncClient(pcb); + if (c) { c->setNoDelay(_noDelay); return _tcp_accept(this, c); } } - if(tcp_close(pcb) != ERR_OK){ + if (tcp_close(pcb) != ERR_OK) { tcp_abort(pcb); } log_e("FAIL"); return ERR_OK; } -int8_t AsyncServer::_accepted(AsyncClient* client){ - if(_connect_cb){ +int8_t AsyncServer::_accepted(AsyncClient * client) { + if (_connect_cb) { _connect_cb(_connect_cb_arg, client); } return ERR_OK; } -void AsyncServer::setNoDelay(bool nodelay){ +void AsyncServer::setNoDelay(bool nodelay) { _noDelay = nodelay; } -bool AsyncServer::getNoDelay(){ +bool AsyncServer::getNoDelay() { return _noDelay; } -uint8_t AsyncServer::status(){ +uint8_t AsyncServer::status() { if (!_pcb) { return 0; } return _pcb->state; } -int8_t AsyncServer::_s_accept(void * arg, tcp_pcb * pcb, int8_t err){ - return reinterpret_cast(arg)->_accept(pcb, err); +int8_t AsyncServer::_s_accept(void * arg, tcp_pcb * pcb, int8_t err) { + return reinterpret_cast(arg)->_accept(pcb, err); } -int8_t AsyncServer::_s_accepted(void *arg, AsyncClient* client){ - return reinterpret_cast(arg)->_accepted(client); -} +int8_t AsyncServer::_s_accepted(void * arg, AsyncClient * client) { + return reinterpret_cast(arg)->_accepted(client); +} \ No newline at end of file diff --git a/lib/AsyncTCP/src/AsyncTCP.h b/lib/AsyncTCP/src/AsyncTCP.h index ac87dedac..855f2f7f1 100644 --- a/lib/AsyncTCP/src/AsyncTCP.h +++ b/lib/AsyncTCP/src/AsyncTCP.h @@ -23,17 +23,20 @@ #define ASYNCTCP_H_ #include "IPAddress.h" +#include "IPv6Address.h" #include "sdkconfig.h" #include extern "C" { - #include "freertos/semphr.h" - #include "lwip/pbuf.h" +#include "freertos/semphr.h" +#include "lwip/pbuf.h" +#include "lwip/ip_addr.h" +#include "lwip/ip6_addr.h" } //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 +#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event #endif class AsyncClient; @@ -42,127 +45,138 @@ class AsyncClient; #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; +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); + AsyncClient & operator=(const AsyncClient & other); + AsyncClient & operator+=(const AsyncClient & other); - bool operator==(const AsyncClient &other); + bool operator==(const AsyncClient & other); - bool operator!=(const AsyncClient &other) { - return !(*this == 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(); + bool connect(IPAddress ip, uint16_t port); + bool connect(IPv6Address 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 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 + 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 + 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 + 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 + 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 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(); + uint32_t getRemoteAddress(); + ip6_addr_t getRemoteAddress6(); + uint16_t getRemotePort(); + uint32_t getLocalAddress(); + ip6_addr_t getLocalAddress6(); + uint16_t getLocalPort(); //compatibility - IPAddress remoteIP(); - uint16_t remotePort(); - IPAddress localIP(); - uint16_t localPort(); + IPAddress remoteIP(); + IPv6Address remoteIP6(); + uint16_t remotePort(); + IPAddress localIP(); + IPv6Address localIP6(); + 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 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 + 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); + 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; } + int8_t _recv(tcp_pcb * pcb, pbuf * pb, int8_t err); + tcp_pcb * pcb() { + return _pcb; + } protected: - tcp_pcb* _pcb; - int8_t _closed_slot; + bool _connect(ip_addr_t addr, uint16_t port); + + tcp_pcb * _pcb; + int8_t _closed_slot; AcConnectHandler _connect_cb; - void* _connect_cb_arg; + 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; + 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; + void * _timeout_cb_arg; AcConnectHandler _poll_cb; - void* _poll_cb_arg; + void * _poll_cb_arg; - bool _pcb_busy; + bool _pcb_busy; uint32_t _pcb_sent_at; - bool _ack_pcb; + bool _ack_pcb; uint32_t _rx_ack_len; uint32_t _rx_last_packet; uint32_t _rx_since_timeout; @@ -170,48 +184,52 @@ class AsyncClient { uint16_t _connect_port; int8_t _close(); - void _free_closed_slot(); - void _allocate_closed_slot(); - 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); + void _free_closed_slot(); + void _allocate_closed_slot(); + 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; + AsyncClient * prev; + AsyncClient * next; }; class AsyncServer { public: AsyncServer(IPAddress addr, uint16_t port); + AsyncServer(IPv6Address addr, uint16_t port); AsyncServer(uint16_t port); ~AsyncServer(); - void onClient(AcConnectHandler cb, void* arg); - void begin(); - void end(); - void setNoDelay(bool nodelay); - bool getNoDelay(); + 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); + static int8_t _s_accept(void * arg, tcp_pcb * newpcb, int8_t err); + static int8_t _s_accepted(void * arg, AsyncClient * client); protected: - uint16_t _port; - IPAddress _addr; - bool _noDelay; - tcp_pcb* _pcb; + uint16_t _port; + bool _bind4 = false; + bool _bind6 = false; + IPAddress _addr; + IPv6Address _addr6; + bool _noDelay; + tcp_pcb * _pcb; AcConnectHandler _connect_cb; - void* _connect_cb_arg; + void * _connect_cb_arg; - int8_t _accept(tcp_pcb* newpcb, int8_t err); - int8_t _accepted(AsyncClient* client); + int8_t _accept(tcp_pcb * newpcb, int8_t err); + int8_t _accepted(AsyncClient * client); }; -#endif /* ASYNCTCP_H_ */ +#endif /* ASYNCTCP_H_ */ \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/WebResponses.cpp b/lib/ESPAsyncWebServer/WebResponses.cpp index adeab988b..6e24444b7 100644 --- a/lib/ESPAsyncWebServer/WebResponses.cpp +++ b/lib/ESPAsyncWebServer/WebResponses.cpp @@ -321,7 +321,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u free(buf); return 0; } - outLen = sprintf_P((char*)buf+headLen, PSTR("%x"), readLen) + headLen; + outLen = snprintf_P((char*)buf+headLen, sizeof(buf)-headLen-2, PSTR("%x"), readLen) + headLen; while(outLen < headLen + 4) buf[outLen++] = ' '; buf[outLen++] = '\r'; buf[outLen++] = '\n'; diff --git a/lib/async-mqtt-client/LICENSE b/lib/async-mqtt-client/LICENSE index a6183c687..a3ee21720 100644 --- a/lib/async-mqtt-client/LICENSE +++ b/lib/async-mqtt-client/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Marvin Roger +Copyright (c) 2015-2021 Marvin Roger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/async-mqtt-client/src/AsyncMqttClient.cpp b/lib/async-mqtt-client/src/AsyncMqttClient.cpp index 2b57425b4..98f0e5f29 100644 --- a/lib/async-mqtt-client/src/AsyncMqttClient.cpp +++ b/lib/async-mqtt-client/src/AsyncMqttClient.cpp @@ -6,14 +6,16 @@ AsyncMqttClient::AsyncMqttClient() , _tail(nullptr) , _sent(0) , _state(DISCONNECTED) -, _tlsBadFingerprint(false) +, _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) , _lastClientActivity(0) , _lastServerActivity(0) , _lastPingRequestTime(0) , _generatedClientId{0} , _ip() +, _ipv6() , _host(nullptr) , _useIp(false) +, _useIpv6(false) #if ASYNC_TCP_SSL_ENABLED , _secure(false) #endif @@ -111,16 +113,33 @@ AsyncMqttClient& AsyncMqttClient::setWill(const char* topic, uint8_t qos, bool r } AsyncMqttClient& AsyncMqttClient::setServer(IPAddress ip, uint16_t port) { - _useIp = true; - _ip = ip; - _port = port; + _useIp = true; + _useIpv6 = false; + _ip = ip; + _port = port; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::setServer(IPv6Address ipv6, uint16_t port) { + _useIpv6 = true; + _useIp = false; + _ipv6 = ipv6; + _port = port; return *this; } AsyncMqttClient& AsyncMqttClient::setServer(const char* host, uint16_t port) { - _useIp = false; - _host = host; - _port = port; + _port = port; + _useIp = false; + _useIpv6 = false; + _host = host; + if (_ipv6.fromString(host)) { + _useIpv6 = true; + _useIp = false; + } else if (_ip.fromString(host)) { + _useIpv6 = false; + _useIp = true; + } return *this; } @@ -175,11 +194,12 @@ void AsyncMqttClient::_freeCurrentParsedPacket() { void AsyncMqttClient::_clear() { _lastPingRequestTime = 0; - _tlsBadFingerprint = false; _freeCurrentParsedPacket(); _clearQueue(true); // keep session data for now _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE; + + _client.setRxTimeout(0); } /* TCP */ @@ -198,7 +218,7 @@ void AsyncMqttClient::_onConnect() { } if (!sslFoundFingerprint) { - _tlsBadFingerprint = true; + _disconnectReason = AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT; _client.close(true); return; } @@ -222,17 +242,10 @@ void AsyncMqttClient::_onConnect() { void AsyncMqttClient::_onDisconnect() { log_i("TCP disconn"); _state = DISCONNECTED; - AsyncMqttClientDisconnectReason reason; - - if (_tlsBadFingerprint) { - reason = AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT; - } else { - reason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED; - } _clear(); - for (auto callback : _onDisconnectUserCallbacks) callback(reason); + for (auto callback : _onDisconnectUserCallbacks) callback(_disconnectReason); } /* @@ -267,6 +280,7 @@ void AsyncMqttClient::_onData(char* data, size_t len) { case AsyncMqttClientInternals::PacketType.CONNACK: log_i("rcv CONNACK"); _currentParsedPacket = new AsyncMqttClientInternals::ConnAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onConnAck, this, std::placeholders::_1, std::placeholders::_2)); + _client.setRxTimeout(0); break; case AsyncMqttClientInternals::PacketType.PINGRESP: log_i("rcv PINGRESP"); @@ -519,6 +533,8 @@ void AsyncMqttClient::_onConnAck(bool sessionPresent, uint8_t connectReturnCode) for (auto callback : _onConnectUserCallbacks) callback(sessionPresent); } else { // Callbacks are handled by the onDisconnect function which is called from the AsyncTcp lib + _disconnectReason = static_cast(connectReturnCode); + return; } _handleQueue(); // send any remaining data from continued session } @@ -688,6 +704,9 @@ void AsyncMqttClient::connect() { if (_state != DISCONNECTED) return; log_i("CONNECTING"); _state = CONNECTING; + _disconnectReason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED; // reset any previous + + _client.setRxTimeout(_keepAlive); #if ASYNC_TCP_SSL_ENABLED if (_useIp) { @@ -698,6 +717,8 @@ void AsyncMqttClient::connect() { #else if (_useIp) { _client.connect(_ip, _port); + } else if (_useIpv6) { + _client.connect(_ipv6, _port); } else { _client.connect(_host, _port); } @@ -744,6 +765,12 @@ uint16_t AsyncMqttClient::publish(const char* topic, uint8_t qos, bool retain, c return msg->packetId(); } +bool AsyncMqttClient::clearQueue() { + if (_state != DISCONNECTED) return false; + _clearQueue(false); + return true; +} + const char* AsyncMqttClient::getClientId() const { return _clientId; } diff --git a/lib/async-mqtt-client/src/AsyncMqttClient.hpp b/lib/async-mqtt-client/src/AsyncMqttClient.hpp index 9ae566223..fc2462601 100644 --- a/lib/async-mqtt-client/src/AsyncMqttClient.hpp +++ b/lib/async-mqtt-client/src/AsyncMqttClient.hpp @@ -62,6 +62,7 @@ class AsyncMqttClient { 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); AsyncMqttClient& setServer(IPAddress ip, uint16_t port); + AsyncMqttClient& setServer(IPv6Address ipv6, uint16_t port); AsyncMqttClient& setServer(const char* host, uint16_t port); #if ASYNC_TCP_SSL_ENABLED AsyncMqttClient& setSecure(bool secure); @@ -81,6 +82,7 @@ class AsyncMqttClient { uint16_t subscribe(const char* topic, uint8_t qos); uint16_t unsubscribe(const char* topic); 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); + bool clearQueue(); // Not MQTT compliant! const char* getClientId() const; @@ -95,15 +97,17 @@ class AsyncMqttClient { DISCONNECTING, DISCONNECTED } _state; - bool _tlsBadFingerprint; + AsyncMqttClientDisconnectReason _disconnectReason; uint32_t _lastClientActivity; uint32_t _lastServerActivity; uint32_t _lastPingRequestTime; char _generatedClientId[18 + 1]; // esp8266-abc123 and esp32-abcdef123456 IPAddress _ip; + IPv6Address _ipv6; const char* _host; bool _useIp; + bool _useIpv6; #if ASYNC_TCP_SSL_ENABLED bool _secure; #endif diff --git a/lib/async-mqtt-client/src/AsyncMqttClient/DisconnectReasons.hpp b/lib/async-mqtt-client/src/AsyncMqttClient/DisconnectReasons.hpp index f4cbda8af..a15114000 100644 --- a/lib/async-mqtt-client/src/AsyncMqttClient/DisconnectReasons.hpp +++ b/lib/async-mqtt-client/src/AsyncMqttClient/DisconnectReasons.hpp @@ -1,6 +1,6 @@ #pragma once -enum class AsyncMqttClientDisconnectReason : int8_t { +enum class AsyncMqttClientDisconnectReason : uint8_t { TCP_DISCONNECTED = 0, MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, diff --git a/lib/async-mqtt-client/src/AsyncMqttClient/Packets/Out/PubAck.cpp b/lib/async-mqtt-client/src/AsyncMqttClient/Packets/Out/PubAck.cpp index 3352bd0d2..634607ba5 100644 --- a/lib/async-mqtt-client/src/AsyncMqttClient/Packets/Out/PubAck.cpp +++ b/lib/async-mqtt-client/src/AsyncMqttClient/Packets/Out/PubAck.cpp @@ -10,7 +10,6 @@ PubAckOutPacket::PubAckOutPacket(PendingAck pendingAck) { _packetId = pendingAck.packetId; _data[2] = pendingAck.packetId >> 8; _data[3] = pendingAck.packetId & 0xFF; - // _released = false; if (packetType() == AsyncMqttClientInternals::PacketType.PUBREL || packetType() == AsyncMqttClientInternals::PacketType.PUBREC) { _released = false; diff --git a/lib/framework/APSettingsService.cpp b/lib/framework/APSettingsService.cpp index c79f18af1..f5261c20e 100644 --- a/lib/framework/APSettingsService.cpp +++ b/lib/framework/APSettingsService.cpp @@ -47,6 +47,7 @@ void APSettingsService::manageAP() { void APSettingsService::startAP() { WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask); + esp_wifi_set_bandwidth(ESP_IF_WIFI_AP, WIFI_BW_HT20); WiFi.softAP(_state.ssid.c_str(), _state.password.c_str()); if (!_dnsServer) { IPAddress apIp = WiFi.softAPIP(); diff --git a/lib/framework/MqttSettingsService.cpp b/lib/framework/MqttSettingsService.cpp index 67ce8b1f6..04efccd7b 100644 --- a/lib/framework/MqttSettingsService.cpp +++ b/lib/framework/MqttSettingsService.cpp @@ -84,7 +84,7 @@ void MqttSettingsService::onMqttConnect(bool sessionPresent) { } void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { - // emsesp::EMSESP::logger().info(F("Disconnected from MQTT reason: %s"), (uint8_t)reason); + // emsesp::EMSESP::logger().info(F("Disconnected from MQTT reason: %d"), (uint8_t)reason); _disconnectReason = reason; _disconnectedAt = uuid::get_uptime(); } @@ -101,6 +101,7 @@ void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { switch (event) { case SYSTEM_EVENT_STA_GOT_IP: case SYSTEM_EVENT_ETH_GOT_IP: + case SYSTEM_EVENT_GOT_IP6: if (_state.enabled) { // emsesp::EMSESP::logger().info(F("Network connection found, starting MQTT client")); onConfigUpdated(); @@ -121,14 +122,13 @@ void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { } void MqttSettingsService::configureMqtt() { - // disconnect if currently connected - _mqttClient.disconnect(); - // only connect if WiFi is connected and MQTT is enabled if (_state.enabled && emsesp::EMSESP::system_.network_connected()) { + _mqttClient.disconnect(); _mqttClient.setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port); if (_state.username.length() > 0) { - _mqttClient.setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername), retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword)); + _mqttClient.setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername), + retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword)); } else { _mqttClient.setCredentials(retainCstr(nullptr, &_retainedUsername), retainCstr(nullptr, &_retainedPassword)); } @@ -138,8 +138,6 @@ void MqttSettingsService::configureMqtt() { _mqttClient.setMaxTopicLength(_state.maxTopicLength); _mqttClient.connect(); } - - emsesp::EMSESP::dallassensor_.reload(); // added by Proddy for EMS-ESP } void MqttSettings::read(MqttSettings & settings, JsonObject & root) { @@ -163,8 +161,6 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) { root["publish_time_sensor"] = settings.publish_time_sensor; root["mqtt_qos"] = settings.mqtt_qos; root["mqtt_retain"] = settings.mqtt_retain; - root["dallas_format"] = settings.dallas_format; - root["bool_format"] = settings.bool_format; root["ha_climate_format"] = settings.ha_climate_format; root["ha_enabled"] = settings.ha_enabled; root["nested_format"] = settings.nested_format; @@ -195,20 +191,17 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME; - newSettings.dallas_format = root["dallas_format"] | EMSESP_DEFAULT_DALLAS_FORMAT; - newSettings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT; newSettings.ha_climate_format = root["ha_climate_format"] | EMSESP_DEFAULT_HA_CLIMATE_FORMAT; newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED; newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT; newSettings.subscribe_format = root["subscribe_format"] | EMSESP_DEFAULT_SUBSCRIBE_FORMAT; - if (newSettings.mqtt_qos != settings.mqtt_qos) { - emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos); + if (newSettings.enabled != settings.enabled) { changed = true; } - if (newSettings.dallas_format != settings.dallas_format) { - emsesp::EMSESP::mqtt_.dallas_format(newSettings.dallas_format); + if (newSettings.mqtt_qos != settings.mqtt_qos) { + emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos); changed = true; } diff --git a/lib/framework/MqttSettingsService.h b/lib/framework/MqttSettingsService.h index 0c2d53cda..14d51c8b4 100644 --- a/lib/framework/MqttSettingsService.h +++ b/lib/framework/MqttSettingsService.h @@ -9,7 +9,7 @@ #include -#define MQTT_RECONNECTION_DELAY 1000 +#define MQTT_RECONNECTION_DELAY 2000 // 2 seconds #define MQTT_SETTINGS_FILE "/config/mqttSettings.json" #define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings" @@ -86,8 +86,6 @@ class MqttSettings { uint16_t publish_time_sensor; uint8_t mqtt_qos; bool mqtt_retain; - uint8_t dallas_format; - uint8_t bool_format; uint8_t ha_climate_format; bool ha_enabled; uint8_t nested_format; diff --git a/lib/framework/NetworkSettingsService.cpp b/lib/framework/NetworkSettingsService.cpp index 587bf3906..8adf34f6c 100644 --- a/lib/framework/NetworkSettingsService.cpp +++ b/lib/framework/NetworkSettingsService.cpp @@ -62,6 +62,19 @@ void NetworkSettingsService::manageSTA() { } WiFi.setHostname(_state.hostname.c_str()); // set hostname + // www.esp32.com/viewtopic.php?t=12055 + read([&](NetworkSettings & networkSettings) { + if (networkSettings.bandwidth20) { + esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, WIFI_BW_HT20); + } else { + esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, WIFI_BW_HT40); + } + esp_wifi_set_max_tx_power(networkSettings.tx_power * 4); + if (networkSettings.nosleep) { + WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE + } + + }); WiFi.begin(_state.ssid.c_str(), _state.password.c_str()); // attempt to connect to the network } } diff --git a/lib/framework/NetworkSettingsService.h b/lib/framework/NetworkSettingsService.h index 64e11f498..ce3b9b189 100644 --- a/lib/framework/NetworkSettingsService.h +++ b/lib/framework/NetworkSettingsService.h @@ -6,7 +6,10 @@ #include #include +#ifndef EMSESP_STANDALONE +#include #include +#endif #define NETWORK_SETTINGS_FILE "/config/networkSettings.json" #define NETWORK_SETTINGS_SERVICE_PATH "/rest/networkSettings" @@ -27,10 +30,14 @@ class NetworkSettings { public: // core wifi configuration - String ssid; - String password; - String hostname; - bool staticIPConfig; + String ssid; + String password; + String hostname; + bool staticIPConfig; + bool enableIPv6; + bool bandwidth20; + int8_t tx_power; + bool nosleep; // optional configuration for static IP address IPAddress localIP; @@ -45,6 +52,10 @@ class NetworkSettings { root["password"] = settings.password; root["hostname"] = settings.hostname; root["static_ip_config"] = settings.staticIPConfig; + root["enableIPv6"] = settings.enableIPv6; + root["bandwidth20"] = settings.bandwidth20; + root["tx_power"] = settings.tx_power; + root["nosleep"] = settings.nosleep; // extended settings JsonUtils::writeIP(root, "local_ip", settings.localIP); @@ -55,10 +66,14 @@ class NetworkSettings { } static StateUpdateResult update(JsonObject & root, NetworkSettings & settings) { - settings.ssid = root["ssid"] | FACTORY_WIFI_SSID; - settings.password = root["password"] | FACTORY_WIFI_PASSWORD; - settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME; - settings.staticIPConfig = root["static_ip_config"] | false; + settings.ssid = root["ssid"] | FACTORY_WIFI_SSID; + settings.password = root["password"] | FACTORY_WIFI_PASSWORD; + settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME; + settings.staticIPConfig = root["static_ip_config"] | false; + settings.enableIPv6 = root["enableIPv6"] | false; + settings.bandwidth20 = root["bandwidth20"] | false; + settings.tx_power = root["tx_power"] | 20; + settings.nosleep = root["nosleep"] | false; // extended settings JsonUtils::readIP(root, "local_ip", settings.localIP); diff --git a/lib/framework/NetworkStatus.cpp b/lib/framework/NetworkStatus.cpp index cb0369d0e..fb369386c 100644 --- a/lib/framework/NetworkStatus.cpp +++ b/lib/framework/NetworkStatus.cpp @@ -25,6 +25,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { // for Wifi if (wifi_status == WL_CONNECTED) { root["local_ip"] = WiFi.localIP().toString(); + root["local_ipv6"] = WiFi.localIPv6().toString(); root["mac_address"] = WiFi.macAddress(); root["rssi"] = WiFi.RSSI(); root["ssid"] = WiFi.SSID(); @@ -47,6 +48,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { } else if (ethernet_connected) { // Ethernet root["local_ip"] = ETH.localIP().toString(); + root["local_ipv6"] = ETH.localIPv6().toString(); root["mac_address"] = ETH.macAddress(); root["subnet_mask"] = ETH.subnetMask().toString(); root["gateway_ip"] = ETH.gatewayIP().toString(); diff --git a/lib/framework/StatefulService.h b/lib/framework/StatefulService.h index 541488c62..0a6d4725e 100644 --- a/lib/framework/StatefulService.h +++ b/lib/framework/StatefulService.h @@ -10,7 +10,7 @@ #include #ifndef DEFAULT_BUFFER_SIZE -#define DEFAULT_BUFFER_SIZE 1024 +#define DEFAULT_BUFFER_SIZE 2048 #endif enum class StateUpdateResult { diff --git a/lib/uuid-syslog/src/syslog.cpp b/lib/uuid-syslog/src/syslog.cpp index b5913d1a6..8d8b18625 100644 --- a/lib/uuid-syslog/src/syslog.cpp +++ b/lib/uuid-syslog/src/syslog.cpp @@ -18,9 +18,6 @@ #include "uuid/syslog.h" -#include -#include -#include #include "../../../src/emsesp.h" @@ -153,16 +150,38 @@ void SyslogService::maximum_log_messages(size_t count) { } std::pair SyslogService::destination() const { - return std::make_pair(host_, port_); + return std::make_pair(ip_, port_); } void SyslogService::destination(IPAddress host, uint16_t port) { - host_ = host; + ip_ = host; port_ = port; - if ((uint32_t)host_ == (uint32_t)0) { + if ((uint32_t)ip_ == (uint32_t)0) { started_ = false; remove_queued_messages(log_level()); + host_.clear(); + } +} + +void SyslogService::destination(const char * host, uint16_t port) { + if (host == nullptr || host[0] == '\0') { + started_ = false; + remove_queued_messages(log_level()); + ip_ = (IPAddress)(uint32_t)0; + host_.clear(); + return; + } + host_ = host; + port_ = port; + if (ip_.fromString(host)) { + host_.clear(); + if ((uint32_t)ip_ == (uint32_t)0) { + started_ = false; + remove_queued_messages(log_level()); + } + } else { + ip_ = (IPAddress)(uint32_t)0; } } @@ -257,12 +276,15 @@ void SyslogService::loop() { } bool SyslogService::can_transmit() { + if (!host_.empty() && (uint32_t)ip_ == 0) { + WiFi.hostByName(host_.c_str(), ip_); + } #if UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (host_.isV4() && (uint32_t)host_ == (uint32_t)0) { + if (ip_.isV4() && (uint32_t)ip_ == (uint32_t)0) { return false; } #else - if ((uint32_t)host_ == (uint32_t)0) { + if ((uint32_t)ip_ == (uint32_t)0) { return false; } #endif @@ -276,14 +298,14 @@ bool SyslogService::can_transmit() { #if UUID_SYSLOG_ARP_CHECK #if UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (host_.isV4()) + if (ip_.isV4()) #endif { message_delay = 10; } #endif #if UUID_SYSLOG_NDP_CHECK && UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (host_.isV6()) { + if (ip_.isV6()) { message_delay = 10; } #endif @@ -294,12 +316,12 @@ bool SyslogService::can_transmit() { #if UUID_SYSLOG_ARP_CHECK #if UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (host_.isV4()) + if (ip_.isV4()) #endif { ip4_addr_t ipaddr; - ip4_addr_set_u32(&ipaddr, (uint32_t)host_); + ip4_addr_set_u32(&ipaddr, (uint32_t)ip_); if (!ip4_addr_isloopback(&ipaddr) && !ip4_addr_ismulticast(&ipaddr) && !ip4_addr_isbroadcast(&ipaddr, netif_default)) { struct eth_addr * eth_ret = nullptr; @@ -326,10 +348,10 @@ bool SyslogService::can_transmit() { #endif #if UUID_SYSLOG_NDP_CHECK && UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (host_.isV6()) { + if (ip_.isV6()) { ip6_addr_t ip6addr; - IP6_ADDR(&ip6addr, host_.raw6()[0], host_.raw6()[1], host_.raw6()[2], host_.raw6()[3]); + IP6_ADDR(&ip6addr, ip_.raw6()[0], ip_.raw6()[1], ip_.raw6()[2], ip_.raw6()[3]); ip6_addr_assign_zone(&ip6addr, IP6_UNICAST, netif_default); if (!ip6_addr_isloopback(&ip6addr) && !ip6_addr_ismulticast(&ip6addr)) { @@ -400,7 +422,7 @@ bool SyslogService::transmit(const QueuedLogMessage & message) { tzm = diff < 0 ? (0 - diff) % 60 : diff % 60; } - if (udp_.beginPacket(host_, port_) != 1) { + if (udp_.beginPacket(ip_, port_) != 1) { last_transmit_ = uuid::get_uptime_ms(); return false; } diff --git a/lib/uuid-syslog/src/uuid/syslog.h b/lib/uuid-syslog/src/uuid/syslog.h index 70c94cdd1..7d1d3299e 100644 --- a/lib/uuid-syslog/src/uuid/syslog.h +++ b/lib/uuid-syslog/src/uuid/syslog.h @@ -129,6 +129,7 @@ class SyslogService : public uuid::log::Handler { * @since 2.0.0 */ void destination(IPAddress host, uint16_t port = DEFAULT_PORT); + void destination(const char * host, uint16_t port = DEFAULT_PORT); /** * Get local hostname. @@ -183,6 +184,20 @@ class SyslogService : public uuid::log::Handler { */ virtual void operator<<(std::shared_ptr message); + /** + * added MichaelDvP + * query status variables + */ + size_t queued() { + return log_messages_.size(); + } + bool started() { + return started_; + } + IPAddress ip() { + return ip_; + } + private: /** * Log message that has been queued. @@ -244,7 +259,8 @@ class SyslogService : public uuid::log::Handler { bool started_ = false; /*!< Flag to indicate that messages have started being transmitted. @since 1.0.0 */ WiFiUDP udp_; /*!< UDP client. @since 1.0.0 */ - IPAddress host_; /*!< Host to send messages to. @since 1.0.0 */ + IPAddress ip_; /*!< Host-IP to send messages to. @since 1.0.0 */ + std::string host_; /*!< Host to send messages to. */ uint16_t port_ = DEFAULT_PORT; /*!< Port to send messages to. @since 1.0.0 */ uint64_t last_transmit_ = 0; /*!< Last transmit time. @since 1.0.0 */ std::string hostname_{'-'}; /*!< Local hostname. @since 1.0.0 */ diff --git a/lib_standalone/Arduino.h b/lib_standalone/Arduino.h index 1884c238b..2b1f3f3bb 100644 --- a/lib_standalone/Arduino.h +++ b/lib_standalone/Arduino.h @@ -26,6 +26,8 @@ #include #include // for count_if +#include "WString.h" + #include #define IPAddress std::string @@ -129,7 +131,12 @@ class Print { size_t println(unsigned long value) { return print(std::to_string(value).c_str()) + println(); } + virtual void flush(){}; + + size_t print(const String & str) { + return print(str.c_str()); + } }; class Stream : public Print { @@ -206,6 +213,6 @@ void yield(void); void setup(void); void loop(void); -#include "WString.h" + #endif diff --git a/lib_standalone/ESP8266React.h b/lib_standalone/ESP8266React.h index d6913e319..908b776b6 100644 --- a/lib_standalone/ESP8266React.h +++ b/lib_standalone/ESP8266React.h @@ -26,13 +26,15 @@ class DummySettings { bool shower_alert = false; bool hide_led = false; bool notoken_api = false; + uint8_t bool_format = 1; // on off + uint8_t enum_format = 1; + uint8_t dallas_format = 1; // MQTT uint16_t publish_time = 10; // seconds uint8_t mqtt_qos = 0; bool mqtt_retain = false; bool enabled = true; - uint8_t dallas_format = 1; uint8_t nested_format = 1; uint8_t ha_climate_format = 1; bool ha_enabled = true; @@ -56,7 +58,7 @@ class DummySettings { uint16_t publish_time_mixer = 10; uint16_t publish_time_other = 10; uint16_t publish_time_sensor = 10; - uint8_t bool_format = 1; // on off + bool enableIPv6 = false; #define FACTORY_MQTT_MAX_TOPIC_LENGTH 128 @@ -79,6 +81,7 @@ class DummySettingsService : public StatefulService { #define NetworkSettings DummySettings #define SecuritySettings DummySettings #define MqttSettings DummySettings +#define NTPSettings DummySettings class ESP8266React { public: @@ -109,6 +112,10 @@ class ESP8266React { return &_settings; } + StatefulService * getNTPSettingsService() { + return &_settings; + } + private: DummySettingsService _settings; SecuritySettingsService _securitySettingsService; diff --git a/lib_standalone/StatefulService.h b/lib_standalone/StatefulService.h index 41db7d3d5..027cf911f 100644 --- a/lib_standalone/StatefulService.h +++ b/lib_standalone/StatefulService.h @@ -8,7 +8,7 @@ #include #ifndef DEFAULT_BUFFER_SIZE -#define DEFAULT_BUFFER_SIZE 1024 +#define DEFAULT_BUFFER_SIZE 2048 #endif enum class StateUpdateResult { diff --git a/lib_standalone/WString.h b/lib_standalone/WString.h index 98cb2dcbb..04cb3d2e5 100644 --- a/lib_standalone/WString.h +++ b/lib_standalone/WString.h @@ -45,6 +45,13 @@ class String { return _str == s; } + bool concat(const char * rhs) { + if (!rhs) { + return 0; + } + _str += rhs; + return 1; + } private: std::string _str; @@ -56,6 +63,10 @@ inline bool operator==(const std::string & lhs, const ::String & rhs) { return lhs == rhs.c_str(); } +inline bool operator!=(const ::String & lhs, const ::String & rhs) { + return lhs.c_str() != 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); diff --git a/mock-api/server.js b/mock-api/server.js index 7cda4f533..5ade146ce 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -18,7 +18,7 @@ const ES_ENDPOINT_ROOT = '/es/' const LOG_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'logSettings' const log_settings = { level: 6, - max_messages: 30, + max_messages: 50, } const FETCH_LOG_ENDPOINT = REST_ENDPOINT_ROOT + 'fetchLog' @@ -27,37 +27,43 @@ const fetch_log = { { t: '000+00:00:00.001', l: 3, + i: 1, n: 'system', m: 'this is message 3', }, { t: '000+00:00:00.002', l: 4, - n: 'system', + i: 2, + n: 'ntp', m: 'this is message 4', }, { t: '000+00:00:00.002', l: 5, - n: 'system', + i: 3, + n: 'mqtt', m: 'this is message 5', }, { t: '000+00:00:00.002', l: 6, - n: 'system', + i: 444, + n: 'command', m: 'this is message 6', }, { t: '000+00:00:00.002', l: 7, + i: 5555, n: 'emsesp', m: 'this is message 7', }, { t: '000+00:00:00.002', l: 8, - n: 'mqtt', + i: 666666, + n: 'thermostat', m: 'this is message 8', }, ], @@ -108,6 +114,8 @@ const network_settings = { ssid: 'myWifi', password: 'myPassword', hostname: 'ems-esp', + nosleep: true, + tx_power: 20, static_ip_config: false, } const network_status = { @@ -214,8 +222,6 @@ const mqtt_settings = { publish_time_sensor: 10, mqtt_qos: 0, mqtt_retain: false, - dallas_format: 1, - bool_format: 1, ha_climate_format: 1, ha_enabled: true, nested_format: 1, @@ -240,7 +246,7 @@ const UPLOAD_FIRMWARE_ENDPOINT = REST_ENDPOINT_ROOT + 'uploadFirmware' const SIGN_IN_ENDPOINT = REST_ENDPOINT_ROOT + 'signIn' const GENERATE_TOKEN_ENDPOINT = REST_ENDPOINT_ROOT + 'generateToken' const system_status = { - emsesp_version: '3.1 demo', + emsesp_version: '3.x demo', esp_platform: 'ESP32', max_alloc_heap: 113792, psram_size: 0, @@ -278,12 +284,13 @@ const generate_token = { token: '1234' } // EMS-ESP Project specific const EMSESP_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'emsespSettings' -const EMSESP_ALLDEVICES_ENDPOINT = REST_ENDPOINT_ROOT + 'allDevices' +const EMSESP_DATA_ENDPOINT = REST_ENDPOINT_ROOT + 'data' const EMSESP_SCANDEVICES_ENDPOINT = REST_ENDPOINT_ROOT + 'scanDevices' const EMSESP_DEVICEDATA_ENDPOINT = REST_ENDPOINT_ROOT + 'deviceData' const EMSESP_STATUS_ENDPOINT = REST_ENDPOINT_ROOT + 'emsespStatus' const EMSESP_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile' const WRITE_VALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeValue' +const WRITE_SENSOR_ENDPOINT = REST_ENDPOINT_ROOT + 'writeSensor' const emsesp_settings = { tx_mode: 1, tx_delay: 0, @@ -307,8 +314,11 @@ const emsesp_settings = { analog_enabled: false, pbutton_gpio: 0, board_profile: 'S32', + dallas_format: 1, + bool_format: 1, + enum_format: 1, } -const emsesp_alldevices = { +const emsesp_data = { devices: [ { id: 1, @@ -339,10 +349,11 @@ const emsesp_alldevices = { }, ], sensors: [ - { no: 1, id: '28-233D-9497-0C03', temp: '25.7' }, - { no: 2, id: '28-243D-7437-1E3A', temp: '26.1' }, + { no: 1, id: '28-233D-9497-0C03', temp: 25.7, offset: 1.2 }, + { no: 2, id: '28-243D-7437-1E3A', temp: 26.1, offset: 0 }, ], } + const emsesp_status = { status: 0, rx_received: 344, @@ -759,14 +770,22 @@ app.get(FETCH_LOG_ENDPOINT, (req, res) => { res.end(null, 'binary') }) app.get(LOG_SETTINGS_ENDPOINT, (req, res) => { + console.log( + 'Fetching log settings ' + + log_settings.level + + ',' + + log_settings.max_messages, + ) res.json(log_settings) }) app.post(LOG_SETTINGS_ENDPOINT, (req, res) => { - console.log('New log level is ' + req.body.level) - const data = { - level: req.body.level, - } - res.json(data) + console.log( + 'Setting new level=' + + req.body.level + + ' max_messages=' + + req.body.max_messages, + ) + res.sendStatus(200) }) // NETWORK @@ -869,8 +888,8 @@ app.get(EMSESP_SETTINGS_ENDPOINT, (req, res) => { app.post(EMSESP_SETTINGS_ENDPOINT, (req, res) => { res.json(emsesp_settings) }) -app.get(EMSESP_ALLDEVICES_ENDPOINT, (req, res) => { - res.json(emsesp_alldevices) +app.get(EMSESP_DATA_ENDPOINT, (req, res) => { + res.json(emsesp_data) }) app.post(EMSESP_SCANDEVICES_ENDPOINT, (req, res) => { res.sendStatus(200) @@ -880,17 +899,17 @@ app.get(EMSESP_STATUS_ENDPOINT, (req, res) => { }) app.post(EMSESP_DEVICEDATA_ENDPOINT, (req, res) => { const id = req.body.id - if (id == 1) { + if (id === 1) { const encoded = msgpack.encode(emsesp_devicedata_1) res.write(encoded, 'binary') res.end(null, 'binary') } - if (id == 2) { + if (id === 2) { const encoded = msgpack.encode(emsesp_devicedata_2) res.write(encoded, 'binary') res.end(null, 'binary') } - if (id == 3) { + if (id === 3) { const encoded = msgpack.encode(emsesp_devicedata_3) res.write(encoded, 'binary') res.end(null, 'binary') @@ -900,13 +919,19 @@ app.post(EMSESP_DEVICEDATA_ENDPOINT, (req, res) => { app.post(WRITE_VALUE_ENDPOINT, (req, res) => { const devicevalue = req.body.devicevalue const id = req.body.id - console.log(id) console.log(devicevalue) res.sendStatus(200) }) +app.post(WRITE_SENSOR_ENDPOINT, (req, res) => { + const sensor = req.body.sensor + console.log(sensor) + + res.sendStatus(200) +}) + app.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => { const board_profile = req.body.code diff --git a/pio_local.ini_example b/pio_local.ini_example index cb609ae4b..cd31cc22e 100644 --- a/pio_local.ini_example +++ b/pio_local.ini_example @@ -1,22 +1,31 @@ ; example custom platformio.ini file for EMS-ESP -[env] +[common] +; e.g. use build_flags = -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\" +debug_flags = -DEMSESP_WIFI_TWEAK -DEMSESP_DEBUG +; debug_flags = + +[env:esp32] +; if using OTA enter your details below upload_protocol = espota upload_flags = --port=8266 --auth=ems-esp-neo upload_port = 10.10.10.101 +; to prevent the web UI from building each time, uncomment this next line +; extra_scripts = -[common] -; options are EMSESP_DEBUG EMSESP_UART_DEBUG EMSESP_DEBUG_SENSOR -; plus all the settings in default_settings.h, e.g. -DEMSESP_DEFAULT_BOARD_PROFILE=\"NODEMCU\" -; debug_flags = -DEMSESP_DEBUG - -[env:esp32] +; pio run -e debug +; or from Visual Studio Code do PIO -> Project Tasks -> debug -> General -> Upload and Monitor +; options for debugging are: EMSESP_DEBUG EMSESP_UART_DEBUG EMSESP_DEBUG_SENSOR +[env:debug] +board = esp32dev +platform = espressif32 +board_build.partitions = esp32_partition_debug.csv +upload_protocol = esptool +build_type = debug monitor_filters = esp32_exception_decoder debug_tool = esp-prog debug_init_break = tbreak setup -extra_scripts = - ; to prevent the web UI from building each time, comment out this next line - ; pre:scripts/build_interface.py - +build_flags = ${factory_settings.build_flags} ${common.debug_flags} -DONEWIRE_CRC16=0 -DNO_GLOBAL_ARDUINOOTA -DARDUINOJSON_ENABLE_STD_STRING=1 -DESP32=1 -DARDUINO_ARCH_ESP32=1 +extra_scripts = pre:scripts/build_interface.py \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 66c02e074..88ac17869 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,10 +15,8 @@ core_build_flags = -D NDEBUG -D ARDUINO_ARCH_ESP32=1 -D ESP32=1 - ; -std=c++17 -std=gnu++17 core_unbuild_flags = - ; -std=gnu++11 build_flags = ${common.core_build_flags} @@ -30,9 +28,6 @@ build_flags = unbuild_flags = ${common.core_unbuild_flags} -; these are set in your pio_local.ini -debug_flags = - [env] framework = arduino monitor_speed = 115200 @@ -47,6 +42,7 @@ check_flags = clangtidy: --checks=-*,clang-analyzer-*,performance-* ; build for GitHub Actions CI +; the Web interface is built seperately [env:ci] extra_scripts = scripts/rename_fw.py board = esp32dev @@ -61,11 +57,6 @@ extra_scripts = scripts/rename_fw.py board = esp32dev platform = espressif32 -; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#idf-release/v4.2 -; toolchain-xtensa32 @ 2.80200.200226 -; toolchain-xtensa32 @ 5.100200.201223 -; toolchain-xtensa32 @ 2.80400.2020 -; platform = https://github.com/platformio/platform-espressif32.git board_build.partitions = esp32_partition_app1984k_spiffs64k.csv -build_flags = ${common.build_flags} ${common.debug_flags} +build_flags = ${common.build_flags} build_unflags = ${common.unbuild_flags} diff --git a/scripts/analyze_stackdmp.py b/scripts/analyze_stackdmp.py deleted file mode 100644 index a686a5446..000000000 --- a/scripts/analyze_stackdmp.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -from subprocess import call -import os - -# example stackdmp.txt would contain text like below copied & pasted from a 'crash dump' command: - -# >>>stack>>> -# 3fffff20: 3fff32f0 00000003 3fff3028 402101b2 -# 3fffff30: 3fffdad0 3fff3280 0000000d 402148aa -# 3fffff40: 3fffdad0 3fff3280 3fff326c 3fff32f0 -# 3fffff50: 0000000d 3fff326c 3fff3028 402103bd -# 3fffff60: 0000000d 3fff34cc 40211de4 3fff34cc -# 3fffff70: 3fff3028 3fff14c4 3fff301c 3fff34cc -# 3fffff80: 3fffdad0 3fff14c4 3fff3028 40210493 -# 3fffff90: 3fffdad0 00000000 3fff14c4 4020a738 -# 3fffffa0: 3fffdad0 00000000 3fff349c 40211e90 -# 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01 -# <<[0-9]*)\\):$") -COUNTER_REGEX = re.compile('^epc1=(?P0x[0-9a-f]+) epc2=(?P0x[0-9a-f]+) epc3=(?P0x[0-9a-f]+) ' - 'excvaddr=(?P0x[0-9a-f]+) depc=(?P0x[0-9a-f]+)$') -CTX_REGEX = re.compile("^ctx: (?P.+)$") -POINTER_REGEX = re.compile('^sp: (?P[0-9a-f]+) end: (?P[0-9a-f]+) offset: (?P[0-9a-f]+)$') -STACK_BEGIN = '>>>stack>>>' -STACK_END = '<<[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$') - -StackLine = namedtuple("StackLine", ["offset", "content"]) - - -class ExceptionDataParser(object): - def __init__(self): - self.exception = None - - self.epc1 = None - self.epc2 = None - self.epc3 = None - self.excvaddr = None - self.depc = None - - self.ctx = None - - self.sp = None - self.end = None - self.offset = None - - self.stack = [] - - def _parse_backtrace(self, line): - if line.startswith('Backtrace:'): - self.stack = [StackLine(offset=0, content=(addr,)) for addr in BACKTRACE_REGEX.findall(line)] - return None - return self._parse_backtrace - - def _parse_exception(self, line): - match = EXCEPTION_REGEX.match(line) - if match is not None: - self.exception = int(match.group('exc')) - return self._parse_counters - return self._parse_exception - - def _parse_counters(self, line): - match = COUNTER_REGEX.match(line) - if match is not None: - self.epc1 = match.group("epc1") - self.epc2 = match.group("epc2") - self.epc3 = match.group("epc3") - self.excvaddr = match.group("excvaddr") - self.depc = match.group("depc") - return self._parse_ctx - return self._parse_counters - - def _parse_ctx(self, line): - match = CTX_REGEX.match(line) - if match is not None: - self.ctx = match.group("ctx") - return self._parse_pointers - return self._parse_ctx - - def _parse_pointers(self, line): - match = POINTER_REGEX.match(line) - if match is not None: - self.sp = match.group("sp") - self.end = match.group("end") - self.offset = match.group("offset") - return self._parse_stack_begin - return self._parse_pointers - - def _parse_stack_begin(self, line): - if line == STACK_BEGIN: - return self._parse_stack_line - return self._parse_stack_begin - - def _parse_stack_line(self, line): - if line != STACK_END: - match = STACK_REGEX.match(line) - if match is not None: - self.stack.append(StackLine(offset=match.group("off"), - content=(match.group("c1"), match.group("c2"), match.group("c3"), - match.group("c4")))) - return self._parse_stack_line - return None - - def parse_file(self, file, platform, stack_only=False): - if platform == 'ESP32': - func = self._parse_backtrace - else: - func = self._parse_exception - if stack_only: - func = self._parse_stack_begin - - for line in file: - func = func(line.strip()) - if func is None: - break - - if func is not None: - print("ERROR: Parser not complete!") - sys.exit(1) - - -class AddressResolver(object): - def __init__(self, tool_path, elf_path): - self._tool = tool_path - self._elf = elf_path - self._address_map = {} - - def _lookup(self, addresses): - cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None] - - if sys.version_info[0] < 3: - output = subprocess.check_output(cmd) - else: - output = subprocess.check_output(cmd, encoding="utf-8") - - line_regex = re.compile("^(?P[0-9a-fx]+): (?P.+)$") - - last = None - for line in output.splitlines(): - line = line.strip() - match = line_regex.match(line) - - if match is None: - if last is not None and line.startswith('(inlined by)'): - line = line [12:].strip() - self._address_map[last] += ("\n \-> inlined by: " + line) - continue - - if match.group("result") == '?? ??:0': - continue - - self._address_map[match.group("addr")] = match.group("result") - last = match.group("addr") - - def fill(self, parser): - addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset] - for line in parser.stack: - addresses.extend(line.content) - - self._lookup(addresses) - - def _sanitize_addr(self, addr): - if addr.startswith("0x"): - addr = addr[2:] - - fill = "0" * (8 - len(addr)) - return "0x" + fill + addr - - def resolve_addr(self, addr): - out = self._sanitize_addr(addr) - - if out in self._address_map: - out += ": " + self._address_map[out] - - return out - - def resolve_stack_addr(self, addr, full=True): - addr = self._sanitize_addr(addr) - if addr in self._address_map: - return addr + ": " + self._address_map[addr] - - if full: - return "[DATA (0x" + addr + ")]" - - return None - - -def print_addr(name, value, resolver): - print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value))) - - -def print_stack_full(lines, resolver): - print("stack:") - for line in lines: - print(line.offset + ":") - for content in line.content: - print(" " + resolver.resolve_stack_addr(content)) - - -def print_stack(lines, resolver): - print("stack:") - for line in lines: - for content in line.content: - out = resolver.resolve_stack_addr(content, full=False) - if out is None: - continue - print(out) - - -def print_result(parser, resolver, platform, full=True, stack_only=False): - if platform == 'ESP8266' and not stack_only: - print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception])) - - print("") - print_addr("epc1", parser.epc1, resolver) - print_addr("epc2", parser.epc2, resolver) - print_addr("epc3", parser.epc3, resolver) - print_addr("excvaddr", parser.excvaddr, resolver) - print_addr("depc", parser.depc, resolver) - - print("") - print("ctx: " + parser.ctx) - - print("") - print_addr("sp", parser.sp, resolver) - print_addr("end", parser.end, resolver) - print_addr("offset", parser.offset, resolver) - - print("") - if full: - print_stack_full(parser.stack, resolver) - else: - print_stack(parser.stack, resolver) - - -def parse_args(): - parser = argparse.ArgumentParser(description="decode ESP Stacktraces.") - - parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(), - default="ESP8266") - parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain", - default="~/.platformio/packages/toolchain-xtensa/") - parser.add_argument("-e", "--elf", help="path to elf file", required=True) - parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true") - parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true") - parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-") - - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - - if args.file == "-": - file = sys.stdin - else: - if not os.path.exists(args.file): - print("ERROR: file " + args.file + " not found") - sys.exit(1) - file = open(args.file, "r") - - addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)), - "bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line") - if os.name == 'nt': - addr2line += '.exe' - if not os.path.exists(addr2line): - print("ERROR: addr2line not found (" + addr2line + ")") - - elf_file = os.path.abspath(os.path.expanduser(args.elf)) - if not os.path.exists(elf_file): - print("ERROR: elf file not found (" + elf_file + ")") - - parser = ExceptionDataParser() - resolver = AddressResolver(addr2line, elf_file) - - parser.parse_file(file, args.platform, args.stack_only) - resolver.fill(parser) - - print_result(parser, resolver, args.platform, args.full, args.stack_only) \ No newline at end of file diff --git a/scripts/decoder.sh b/scripts/decoder.sh deleted file mode 100644 index 93802af72..000000000 --- a/scripts/decoder.sh +++ /dev/null @@ -1,4 +0,0 @@ -# python decoder.py -s -e ../.pio/build/esp8266-local/firmware.elf stackdmp.txt - -python decoder.py -p ESP32 -e ../.pio/build/esp32-local/firmware.elf stackdmp.txt -t ~/.platformio/packages/toolchain-xtensa32 - diff --git a/scripts/partitions.bin b/scripts/partitions.bin index fa042d671..7ae174cb4 100644 Binary files a/scripts/partitions.bin and b/scripts/partitions.bin differ diff --git a/src/command.cpp b/src/command.cpp index 0123d30d1..c3ea653c0 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -28,90 +28,105 @@ std::vector Command::cmdfunctions_; // calls a command // id may be used to represent a heating circuit for example, it's optional -// returns false if error or not found -bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) { +// returns 0 if the command errored, 1 (TRUE) if ok, 2 if not found, 3 if error or 4 if not allowed +uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id) { int8_t id_new = id; char cmd_new[20] = {'\0'}; strlcpy(cmd_new, cmd, 20); + // find the command auto cf = find_command(device_type, cmd_new, id_new); if ((cf == nullptr) || (cf->cmdfunction_json_)) { LOG_WARNING(F("Command %s on %s not found"), cmd, EMSdevice::device_type_2_device_name(device_type).c_str()); - return false; // command not found, or requires a json + return CommandRet::NOT_FOUND; + } + + // check if we're allowed to call it + if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !authenticated) { + LOG_WARNING(F("Command %s on %s not permitted. requires admin."), cmd, EMSdevice::device_type_2_device_name(device_type).c_str()); + return CommandRet::NOT_ALLOWED; } -#ifdef EMSESP_DEBUG std::string dname = EMSdevice::device_type_2_device_name(device_type); if (value == nullptr) { - LOG_DEBUG(F("[DEBUG] Calling %s command '%s'"), dname.c_str(), cmd); + LOG_INFO(F("Calling %s command '%s'"), dname.c_str(), cmd); } else if (id == -1) { - LOG_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value); + LOG_INFO(F("Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value); } else { - LOG_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id); + LOG_INFO(F("Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id); } -#endif - return ((cf->cmdfunction_)(value, id_new)); + return ((cf->cmdfunction_)(value, id_new)) ? CommandRet::OK : CommandRet::ERROR; } // calls a command. Takes a json object for output. // id may be used to represent a heating circuit for example -// returns false if error or not found -bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json) { +// returns 0 if the command errored, 1 (TRUE) if ok, 2 if not found, 3 if error or 4 if not allowed +uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id, JsonObject & json) { int8_t id_new = id; char cmd_new[20] = {'\0'}; strlcpy(cmd_new, cmd, 20); auto cf = find_command(device_type, cmd_new, id_new); -#ifdef EMSESP_DEBUG + // check if we're allowed to call it + if (cf != nullptr) { + if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !authenticated && value != nullptr) { + LOG_WARNING(F("Command %s on %s not permitted. requires admin."), cmd, EMSdevice::device_type_2_device_name(device_type).c_str()); + return CommandRet::NOT_ALLOWED; // command not allowed + } + } + std::string dname = EMSdevice::device_type_2_device_name(device_type); if (value == nullptr) { - LOG_DEBUG(F("[DEBUG] Calling %s command '%s'"), dname.c_str(), cmd); + LOG_INFO(F("Calling %s command '%s'"), dname.c_str(), cmd); } else if (id == -1) { - LOG_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value); + LOG_INFO(F("Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value); } else { - LOG_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id); + LOG_INFO(F("Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id); } -#endif // check if json object is empty, if so quit if (json.isNull()) { - LOG_WARNING(F("Ignore call for command %s in %s because no json"), cmd, EMSdevice::device_type_2_device_name(device_type).c_str()); - return false; + LOG_WARNING(F("Ignore call for command %s in %s because it has no json body"), cmd, EMSdevice::device_type_2_device_name(device_type).c_str()); + return CommandRet::ERROR; } // this is for endpoints that don't have commands, i.e not writable (e.g. boiler/syspress) if (cf == nullptr) { - return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type); + return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type) ? CommandRet::OK : CommandRet::ERROR; } if (cf->cmdfunction_json_) { - return ((cf->cmdfunction_json_)(value, id_new, json)); + return ((cf->cmdfunction_json_)(value, id_new, json)) ? CommandRet::OK : CommandRet::ERROR; } else { if ((device_type != EMSdevice::DeviceType::SYSTEM) && (value == nullptr || strlen(value) == 0 || strcmp(value, "?") == 0 || strcmp(value, "*") == 0)) { - return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type); + return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type) ? CommandRet::OK : CommandRet::ERROR; } - return ((cf->cmdfunction_)(value, id_new)); + return ((cf->cmdfunction_)(value, id_new)) ? CommandRet::OK : CommandRet::ERROR; } } // strip prefixes, check, and find command Command::CmdFunction * Command::find_command(const uint8_t device_type, char * cmd, int8_t & id) { + // TODO special cases for id=0 and id=-1 will be removed in V3 API // no command for id0 if (id == 0) { return nullptr; } + // empty command is info with id0 if (cmd[0] == '\0') { strcpy(cmd, "info"); id = 0; } + // convert cmd to lowercase for (char * p = cmd; *p; p++) { *p = tolower(*p); } + // TODO hack for commands that could have hc or wwc prefixed. will be removed in new API V3 eventually // scan for prefix hc. for (uint8_t i = DeviceValueTAG::TAG_HC1; i <= DeviceValueTAG::TAG_HC4; i++) { const char * tag = EMSdevice::tag_to_string(i).c_str(); @@ -151,33 +166,39 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, char * c } // add a command to the list, which does not return json -void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, const __FlashStringHelper * description, uint8_t flag) { +// these commands are not callable directly via MQTT subscriptions either +void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_function_p cb, const __FlashStringHelper * description, uint8_t flags) { // if the command already exists for that device type don't add it if (find_command(device_type, uuid::read_flash_string(cmd).c_str()) != nullptr) { return; } - // if the description is empty, it's hidden which means it will not show up in Web or Console as an available command - bool hidden = (description == nullptr); + // if the description is empty, it's hidden which means it will not show up in Web API or Console as an available command + if (description == nullptr) { + flags |= CommandFlag::HIDDEN; + } - cmdfunctions_.emplace_back(device_type, flag, cmd, cb, nullptr, description, hidden); // callback for json is nullptr + cmdfunctions_.emplace_back(device_type, flags, cmd, cb, nullptr, description); // callback for json is nullptr // see if we need to subscribe if (Mqtt::enabled()) { - Mqtt::register_command(device_type, cmd, cb, flag); + Mqtt::register_command(device_type, cmd, cb, flags); } } -// add a command to the list, which does return json object as output -// flag is fixed -// optional parameter hidden for commands that will not show up on the Console -void Command::add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb, const __FlashStringHelper * description, bool hidden) { +// add a command to the list, which does return a json object as output +// flag is fixed to MqttSubFlag::FLAG_NOSUB +void Command::add_json(const uint8_t device_type, + const __FlashStringHelper * cmd, + const cmd_json_function_p cb, + const __FlashStringHelper * description, + uint8_t flags) { // if the command already exists for that device type don't add it if (find_command(device_type, uuid::read_flash_string(cmd).c_str()) != nullptr) { return; } - cmdfunctions_.emplace_back(device_type, MqttSubFlag::FLAG_NOSUB, cmd, nullptr, cb, description, hidden); // callback for json is included + cmdfunctions_.emplace_back(device_type, CommandFlag::MQTT_SUB_FLAG_NOSUB | flags, cmd, nullptr, cb, description); // callback for json is included } // see if a command exists for that device type @@ -213,7 +234,7 @@ bool Command::list(const uint8_t device_type, JsonObject & json) { // create a list of commands, sort them std::list sorted_cmds; for (const auto & cf : cmdfunctions_) { - if ((cf.device_type_ == device_type) && !cf.hidden_) { + if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) { sorted_cmds.push_back(uuid::read_flash_string(cf.cmd_)); } } @@ -221,7 +242,7 @@ bool Command::list(const uint8_t device_type, JsonObject & json) { for (auto & cl : sorted_cmds) { for (const auto & cf : cmdfunctions_) { - if ((cf.device_type_ == device_type) && !cf.hidden_ && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) { + if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) { json[cl] = cf.description_; } } @@ -240,7 +261,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo // create a list of commands, sort them std::list sorted_cmds; for (const auto & cf : cmdfunctions_) { - if ((cf.device_type_ == device_type) && !cf.hidden_) { + if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) { sorted_cmds.push_back(uuid::read_flash_string(cf.cmd_)); } } @@ -261,13 +282,13 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo for (auto & cl : sorted_cmds) { // find and print the description for (const auto & cf : cmdfunctions_) { - if ((cf.device_type_ == device_type) && !cf.hidden_ && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) { + if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) { uint8_t i = cl.length(); shell.print(" "); - if (cf.flag_ == FLAG_HC) { + if (cf.has_flags(MQTT_SUB_FLAG_HC)) { shell.print("[hc] "); i += 5; - } else if (cf.flag_ == FLAG_WWC) { + } else if (cf.has_flags(MQTT_SUB_FLAG_WWC)) { shell.print("[wwc] "); i += 6; } @@ -278,6 +299,11 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo } shell.print(COLOR_BRIGHT_CYAN); shell.print(uuid::read_flash_string(cf.description_)); + if (cf.has_flags(CommandFlag::ADMIN_ONLY)) { + shell.print(' '); + shell.print(COLOR_BRIGHT_RED); + shell.print('*'); + } shell.print(COLOR_RESET); } } @@ -337,7 +363,7 @@ void Command::show_devices(uuid::console::Shell & shell) { // output list of all commands to console // calls show with verbose mode set void Command::show_all(uuid::console::Shell & shell) { - shell.println(F("Available commands per device: ")); + shell.println(F("Available commands: ")); // show system first shell.print(COLOR_BOLD_ON); diff --git a/src/command.h b/src/command.h index 08ec8b5b3..69d988ccb 100644 --- a/src/command.h +++ b/src/command.h @@ -34,34 +34,65 @@ using uuid::console::Shell; namespace emsesp { -using cmdfunction_p = std::function; -using cmdfunction_json_p = std::function; +// mqtt flags for command subscriptions +enum CommandFlag : uint8_t { + MQTT_SUB_FLAG_NORMAL = 0, // 0 + MQTT_SUB_FLAG_HC = (1 << 0), // 1 + MQTT_SUB_FLAG_WWC = (1 << 1), // 2 + MQTT_SUB_FLAG_NOSUB = (1 << 2), // 4 + HIDDEN = (1 << 3), // 8 + ADMIN_ONLY = (1 << 4) // 16 + +}; + +// return status after calling a Command +enum CommandRet : uint8_t { + ERRORED = 0, + OK, // 1 or TRUE + NOT_FOUND, // 2 + ERROR, // 3 + NOT_ALLOWED // needs authentication + +}; + +using cmd_function_p = std::function; +using cmd_json_function_p = std::function; class Command { public: struct CmdFunction { uint8_t device_type_; // DeviceType:: - uint8_t flag_; // mqtt flags for command subscriptions + uint8_t flags_; // mqtt flags for command subscriptions const __FlashStringHelper * cmd_; - cmdfunction_p cmdfunction_; - cmdfunction_json_p cmdfunction_json_; + const cmd_function_p cmdfunction_; + const cmd_json_function_p cmdfunction_json_; const __FlashStringHelper * description_; - bool hidden_; // if its command not to be shown on the Console CmdFunction(const uint8_t device_type, - const uint8_t flag, + const uint8_t flags, const __FlashStringHelper * cmd, - cmdfunction_p cmdfunction, - cmdfunction_json_p cmdfunction_json, - const __FlashStringHelper * description, - bool hidden = false) + const cmd_function_p cmdfunction, + const cmd_json_function_p cmdfunction_json, + const __FlashStringHelper * description) : device_type_(device_type) - , flag_(flag) + , flags_(flags) , cmd_(cmd) , cmdfunction_(cmdfunction) , cmdfunction_json_(cmdfunction_json) - , description_(description) - , hidden_(hidden) { + , description_(description) { + } + + inline void add_flags(uint8_t flags) { + flags_ |= flags; + } + inline bool has_flags(uint8_t flags) const { + return (flags_ & flags) == flags; + } + inline void remove_flags(uint8_t flags) { + flags_ &= ~flags; + } + inline uint8_t flags() const { + return flags_; } }; @@ -69,11 +100,21 @@ class Command { return cmdfunctions_; } - static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json); - static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id = -1); - static void add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, const __FlashStringHelper * description, uint8_t flag = 0); - static void - add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb, const __FlashStringHelper * description, bool hidden = false); + static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id, JsonObject & json); + static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id = -1); + + static void add(const uint8_t device_type, + const __FlashStringHelper * cmd, + const cmd_function_p cb, + const __FlashStringHelper * description, + uint8_t flags = CommandFlag::MQTT_SUB_FLAG_NORMAL); + + static void add_json(const uint8_t device_type, + const __FlashStringHelper * cmd, + const cmd_json_function_p cb, + const __FlashStringHelper * description, + uint8_t flags = CommandFlag::MQTT_SUB_FLAG_NORMAL); + static void show_all(uuid::console::Shell & shell); static Command::CmdFunction * find_command(const uint8_t device_type, const char * cmd); static Command::CmdFunction * find_command(const uint8_t device_type, char * cmd, int8_t & id); diff --git a/src/console.cpp b/src/console.cpp index 06a0f3a53..997871c1c 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -376,15 +376,8 @@ void EMSESPShell::add_console_commands() { DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); JsonObject json = doc.to(); - bool ok = false; // validate that a command is present if (arguments.size() < 2) { - // // no cmd specified, default to empty command - // if (Command::call(device_type, "", "", -1, json)) { - // serializeJsonPretty(doc, shell); - // shell.println(); - // return; - // } shell.print(F("Missing command. Available commands are: ")); Command::show(shell, device_type, false); // non-verbose mode return; @@ -392,30 +385,36 @@ void EMSESPShell::add_console_commands() { const char * cmd = arguments[1].c_str(); + uint8_t cmd_return = CommandRet::OK; + if (arguments.size() == 2) { // no value specified, just the cmd - ok = Command::call(device_type, cmd, nullptr, -1, json); + cmd_return = Command::call(device_type, cmd, nullptr, true, -1, json); } else if (arguments.size() == 3) { if (strncmp(cmd, "info", 4) == 0) { // info has a id but no value - ok = Command::call(device_type, cmd, nullptr, atoi(arguments.back().c_str()), json); + cmd_return = Command::call(device_type, cmd, nullptr, true, atoi(arguments.back().c_str()), json); } else { - // has a value but no id - ok = Command::call(device_type, cmd, arguments.back().c_str(), -1, json); + // has a value but no id so use -1 + cmd_return = Command::call(device_type, cmd, arguments.back().c_str(), true, -1, json); } } else { // use value, which could be an id or hc - ok = Command::call(device_type, cmd, arguments[2].c_str(), atoi(arguments[3].c_str()), json); + cmd_return = Command::call(device_type, cmd, arguments[2].c_str(), true, atoi(arguments[3].c_str()), json); } - if (ok && json.size()) { + if (cmd_return == CommandRet::OK && json.size()) { serializeJsonPretty(doc, shell); shell.println(); return; - } else if (!ok) { - shell.println(F("Unknown command, value, or id.")); + } + + if (cmd_return == CommandRet::NOT_FOUND) { + shell.println(F("Unknown command")); shell.print(F("Available commands are: ")); Command::show(shell, device_type, false); // non-verbose mode + } else if (cmd_return != CommandRet::OK) { + shell.println(F("Bad syntax")); } }, [&](Shell & shell __attribute__((unused)), const std::vector & arguments) -> std::vector { @@ -711,6 +710,47 @@ void Console::load_system_commands(unsigned int context) { }); }); + EMSESPShell::commands->add_command(context, + CommandFlags::ADMIN, + flash_string_vector{F_(sensorname)}, + flash_string_vector{F_(sensorid_optional), F_(name_optional), F_(offset_optional)}, + [](Shell & shell, const std::vector & arguments) { + if (arguments.size() == 0) { + EMSESP::webSettingsService.read([&](WebSettings & settings) { + for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { + if (!settings.sensor[i].id.isEmpty()) { + shell.print(settings.sensor[i].id); + shell.print(" : "); + shell.print(settings.sensor[i].name); + shell.print(" : "); + char buf[10]; + shell.println(Helpers::render_value(buf, settings.sensor[i].offset, 10)); + } + } + }); + return; + } + if (arguments.size() == 1) { + EMSESP::dallassensor_.update(arguments.front().c_str(), "", 0); + // shell.println(EMSESP::dallassensor_.get_name(arguments.front().c_str())); + return; + } + int16_t offset = 0; + float val; + if (arguments.size() == 2) { + if (Helpers::value2float(arguments.back().c_str(), val)) { + offset = (10 * val); + EMSESP::dallassensor_.update(arguments.front().c_str(), "", offset); + return; + } + } else if (arguments.size() == 3) { + if (Helpers::value2float(arguments.back().c_str(), val)) { + offset = (10 * val); + } + } + EMSESP::dallassensor_.update(arguments.front().c_str(), arguments[1].c_str(), offset); + }); + EMSESPShell::commands ->add_command(context, CommandFlags::ADMIN, diff --git a/src/dallassensor.cpp b/src/dallassensor.cpp index fe237aeac..32f69839a 100644 --- a/src/dallassensor.cpp +++ b/src/dallassensor.cpp @@ -41,12 +41,12 @@ void DallasSensor::start() { bus_.begin(dallas_gpio_); #endif // API calls - Command::add_with_json( + Command::add_json( EMSdevice::DeviceType::DALLASSENSOR, F_(info), [&](const char * value, const int8_t id, JsonObject & json) { return command_info(value, id, json); }, F_(info_cmd)); - Command::add_with_json( + Command::add_json( EMSdevice::DeviceType::DALLASSENSOR, F_(commands), [&](const char * value, const int8_t id, JsonObject & json) { return command_commands(value, id, json); }, @@ -57,8 +57,9 @@ void DallasSensor::start() { // load the MQTT settings void DallasSensor::reload() { EMSESP::webSettingsService.read([&](WebSettings & settings) { - dallas_gpio_ = settings.dallas_gpio; - parasite_ = settings.dallas_parasite; + dallas_gpio_ = settings.dallas_gpio; + parasite_ = settings.dallas_parasite; + dallas_format_ = settings.dallas_format; }); if (Mqtt::ha_enabled()) { @@ -139,7 +140,11 @@ void DallasSensor::loop() { bool found = false; for (auto & sensor : sensors_) { if (sensor.id() == get_id(addr)) { - changed_ |= (t != sensor.temperature_c); + t += sensor.offset(); + if (t != sensor.temperature_c) { + sensor.temperature_c = t; + changed_ |= true; + } sensor.temperature_c = t; sensor.read = true; found = true; @@ -149,7 +154,7 @@ void DallasSensor::loop() { // add new sensor if (!found && (sensors_.size() < (MAX_SENSORS - 1))) { sensors_.emplace_back(addr); - sensors_.back().temperature_c = t; + sensors_.back().temperature_c = t + sensors_.back().offset(); sensors_.back().read = true; changed_ = true; } @@ -183,7 +188,7 @@ void DallasSensor::loop() { scancnt_ = 0; } else if (scancnt_ == SCAN_START + 1) { // startup firstscan_ = sensors_.size(); - LOG_DEBUG(F("Adding %d dallassensor(s) from first scan"), firstscan_); + LOG_DEBUG(F("Adding %d dallas sensor(s) from first scan"), firstscan_); } else if ((scancnt_ <= 0) && (firstscan_ != sensors_.size())) { // check 2 times for no change of sensor # scancnt_ = SCAN_START; sensors_.clear(); // restart scaning and clear to get correct numbering @@ -293,7 +298,7 @@ uint64_t DallasSensor::Sensor::id() const { return id_; } -std::string DallasSensor::Sensor::to_string() const { +std::string DallasSensor::Sensor::id_string() const { std::string str(20, '\0'); snprintf_P(&str[0], str.capacity() + 1, @@ -305,6 +310,133 @@ std::string DallasSensor::Sensor::to_string() const { return str; } +std::string DallasSensor::Sensor::to_string(const bool name) const { + std::string str = id_string(); + EMSESP::webSettingsService.read([&](WebSettings & settings) { + if (settings.dallas_format == Dallas_Format::NAME || name) { + for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { + if (strcmp(settings.sensor[i].id.c_str(), str.c_str()) == 0) { + str = settings.sensor[i].name.c_str(); + } + } + } + }); + + return str; +} + +int16_t DallasSensor::Sensor::offset() const { + std::string str = id_string(); + int16_t offset = 0; // default value + EMSESP::webSettingsService.read([&](WebSettings & settings) { + for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { + if (strcmp(settings.sensor[i].id.c_str(), str.c_str()) == 0) { + offset = settings.sensor[i].offset; + } + } + }); + + return offset; +} + +// if HA enabled with MQTT Discovery, delete the old config entry by sending an empty topic +// if we're using the name in the MQTT topic name (Dallas format = NAME) +void DallasSensor::delete_ha_config(uint8_t index, const char * name) { + if (Mqtt::ha_enabled() && (dallas_format_ == Dallas_Format::NAME)) { + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; + // use '_' as HA doesn't like '-' in the topic name + std::string topicname = name; + std::replace(topicname.begin(), topicname.end(), '-', '_'); + + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/dallassensor_%s/config"), Mqtt::base().c_str(), topicname.c_str()); + Mqtt::publish(topic); + registered_ha_[index] = false; // forces a recreate of the HA config topic + } +} + +// update dallas information like name and offset +bool DallasSensor::update(const char * idstr, const char * name, int16_t offset) { + bool ok = false; + char id[20]; + strlcpy(id, idstr, sizeof(id)); + + // check for number and convert to id + if (strlen(id) > 0 && strlen(id) <= 2 && id[0] >= '1' && id[0] <= '9') { + uint8_t no = atoi(idstr) - 1; + if (no < sensors_.size()) { + strlcpy(id, sensors_[no].id_string().c_str(), sizeof(id)); + } + } + + // check valid id + if (strlen(id) != 17 || id[2] != '-' || id[7] != '-' || id[12] != '-') { + LOG_WARNING(F("Invalid sensor id: %s"), id); + return ok; + } + + EMSESP::webSettingsService.update( + [&](WebSettings & settings) { + // check for new name of stored id + for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { + if (strcmp(id, settings.sensor[i].id.c_str()) == 0) { + if (strlen(name) == 0 && offset == 0) { // delete entry if name and offset is empty + LOG_INFO(F("Deleting entry for sensor %s"), id); + delete_ha_config(i, settings.sensor[i].name.c_str()); + settings.sensor[i].id = ""; + settings.sensor[i].name = ""; + settings.sensor[i].offset = 0; + } else { + char result[10]; + LOG_INFO(F("Renaming sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10)); + delete_ha_config(i, settings.sensor[i].name.c_str()); // remove old name in HA + settings.sensor[i].name = (strlen(name) == 0) ? id : name; + settings.sensor[i].offset = offset; + } + ok = true; + return StateUpdateResult::CHANGED; + } + } + + // check for free place + for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { + if (settings.sensor[i].id.isEmpty()) { + settings.sensor[i].id = id; + settings.sensor[i].name = (strlen(name) == 0) ? id : name; + settings.sensor[i].offset = offset; + char result[10]; + LOG_INFO(F("Adding sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10)); + ok = true; + return StateUpdateResult::CHANGED; + } + } + + // check if there is a unused id and overwrite it + for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { + bool found = false; + for (const auto & sensor : sensors_) { + if (strcmp(sensor.id_string().c_str(), settings.sensor[i].id.c_str()) == 0) { + found = true; + } + } + if (!found) { + char result[10]; + LOG_INFO(F("Renaming sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10)); + delete_ha_config(i, settings.sensor[i].name.c_str()); // remove old name in HA + settings.sensor[i].id = id; + settings.sensor[i].name = (strlen(name) == 0) ? id : name; + settings.sensor[i].offset = offset; + ok = true; + return StateUpdateResult::CHANGED; + } + } + + LOG_ERROR(F("No more empty sensor slots, remove one first")); + return StateUpdateResult::UNCHANGED; + }, + "local"); + return ok; +} + // check to see if values have been updated bool DallasSensor::updated_values() { if (changed_) { @@ -338,10 +470,10 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject dataSensor["temp"] = (float)(sensor.temperature_c) / 10; } } else { // show according to format - if (Mqtt::dallas_format() == Mqtt::Dallas_Format::SENSORID && Helpers::hasValue(sensor.temperature_c)) { - json[sensor.to_string()] = (float)(sensor.temperature_c) / 10; - } else if (Helpers::hasValue(sensor.temperature_c)) { + if (dallas_format_ == Dallas_Format::NUMBER && Helpers::hasValue(sensor.temperature_c)) { json[sensorID] = (float)(sensor.temperature_c) / 10; + } else if (Helpers::hasValue(sensor.temperature_c)) { + json[sensor.to_string()] = (float)(sensor.temperature_c) / 10; } } } @@ -360,29 +492,22 @@ void DallasSensor::publish_values(const bool force) { DynamicJsonDocument doc(100 * num_sensors); uint8_t sensor_no = 1; - // dallas format is overriden when using Home Assistant - // uint8_t dallas_format = Mqtt::ha_enabled() ? Mqtt::Dallas_Format::NUMBER : Mqtt::dallas_format(); - uint8_t dallas_format = Mqtt::dallas_format(); - for (const auto & sensor : sensors_) { char sensorID[10]; // sensor{1-n} snprintf_P(sensorID, 10, PSTR("sensor%d"), sensor_no); - if (dallas_format == Mqtt::Dallas_Format::SENSORID) { - // e.g. dallassensor_data = {"28-EA41-9497-0E03":23.3,"28-233D-9497-0C03":24.0} - if (Helpers::hasValue(sensor.temperature_c)) { - doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10; - } - } else if (dallas_format == Mqtt::Dallas_Format::NUMBER) { + if (dallas_format_ == Dallas_Format::NUMBER) { // e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03","temp":23.3},"sensor2":{"id":"28-233D-9497-0C03","temp":24.0}} JsonObject dataSensor = doc.createNestedObject(sensorID); dataSensor["id"] = sensor.to_string(); if (Helpers::hasValue(sensor.temperature_c)) { dataSensor["temp"] = (float)(sensor.temperature_c) / 10; } + } else if (Helpers::hasValue(sensor.temperature_c)) { + doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10; } // create the HA MQTT config - // to e.g. homeassistant/sensor/ems-esp/dallas_28-233D-9497-0C03/config + // to e.g. homeassistant/sensor/ems-esp/dallassensor_28-233D-9497-0C03/config if (Mqtt::ha_enabled()) { if (!(registered_ha_[sensor_no - 1]) || force) { StaticJsonDocument config; @@ -395,7 +520,7 @@ void DallasSensor::publish_values(const bool force) { config["unit_of_meas"] = FJSON("°C"); char str[50]; - if (dallas_format == Mqtt::Dallas_Format::SENSORID) { + if (dallas_format_ != Dallas_Format::NUMBER) { snprintf_P(str, sizeof(str), PSTR("{{value_json['%s']}}"), sensor.to_string().c_str()); } else { snprintf_P(str, sizeof(str), PSTR("{{value_json.sensor%d.temp}}"), sensor_no); @@ -403,14 +528,14 @@ void DallasSensor::publish_values(const bool force) { config["val_tpl"] = str; // name as sensor number not the long unique ID - if (dallas_format == Mqtt::Dallas_Format::SENSORID) { + if (dallas_format_ != Dallas_Format::NUMBER) { snprintf_P(str, sizeof(str), PSTR("Dallas Sensor %s"), sensor.to_string().c_str()); } else { snprintf_P(str, sizeof(str), PSTR("Dallas Sensor %d"), sensor_no); } config["name"] = str; - snprintf_P(str, sizeof(str), PSTR("dallas_%s"), sensor.to_string().c_str()); + snprintf_P(str, sizeof(str), PSTR("dallasensor_%s"), sensor.to_string().c_str()); config["uniq_id"] = str; JsonObject dev = config.createNestedObject("dev"); @@ -421,13 +546,13 @@ void DallasSensor::publish_values(const bool force) { ids.add("ems-esp-dallas"); // Different ids as the other portions of the EMS-ESP char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; - if (dallas_format == Mqtt::Dallas_Format::SENSORID) { + if (dallas_format_ == Dallas_Format::NUMBER) { + snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallassensor_%d/config"), Mqtt::base().c_str(), sensor_no); + } else { // use '_' as HA doesn't like '-' in the topic name std::string topicname = sensor.to_string(); std::replace(topicname.begin(), topicname.end(), '-', '_'); - snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallas_sensor%s/config"), Mqtt::base().c_str(), topicname.c_str()); - } else { - snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallas_sensor%d/config"), Mqtt::base().c_str(), sensor_no); + snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallassensor_%s/config"), Mqtt::base().c_str(), topicname.c_str()); } Mqtt::publish_ha(topic, config.as()); diff --git a/src/dallassensor.h b/src/dallassensor.h index c5b75a926..7e46ab78d 100644 --- a/src/dallassensor.h +++ b/src/dallassensor.h @@ -36,6 +36,8 @@ namespace emsesp { +enum Dallas_Format : uint8_t { SENSORID = 1, NUMBER, NAME }; + class DallasSensor { public: class Sensor { @@ -44,7 +46,9 @@ class DallasSensor { ~Sensor() = default; uint64_t id() const; - std::string to_string() const; + std::string id_string() const; + std::string to_string(const bool name = false) const; + int16_t offset() const; int16_t temperature_c = EMS_VALUE_SHORT_NOTSET; bool read = false; @@ -76,6 +80,16 @@ class DallasSensor { return (dallas_gpio_ != 0); } + uint8_t dallas_format() { + return dallas_format_; + } + + void dallas_format(uint8_t dallas_format) { + dallas_format_ = dallas_format; + } + + bool update(const char * idstr, const char * name, int16_t offset); + private: static constexpr uint8_t MAX_SENSORS = 20; @@ -119,21 +133,23 @@ class DallasSensor { bool command_info(const char * value, const int8_t id, JsonObject & json); bool command_commands(const char * value, const int8_t id, JsonObject & json); + void delete_ha_config(uint8_t index, const char * name); + uint32_t last_activity_ = uuid::get_uptime(); - uint32_t last_publish_ = uuid::get_uptime(); State state_ = State::IDLE; std::vector sensors_; bool registered_ha_[MAX_SENSORS]; - int8_t scancnt_ = SCAN_START; - uint8_t firstscan_ = 0; - uint8_t dallas_gpio_ = 0; - bool parasite_ = false; - bool changed_ = false; - uint32_t sensorfails_ = 0; - uint32_t sensorreads_ = 0; - int8_t scanretry_ = 0; + int8_t scancnt_ = SCAN_START; + uint8_t firstscan_ = 0; + uint8_t dallas_gpio_ = 0; + bool parasite_ = false; + bool changed_ = false; + uint32_t sensorfails_ = 0; + uint32_t sensorreads_ = 0; + int8_t scanretry_ = 0; + uint8_t dallas_format_ = 0; }; } // namespace emsesp diff --git a/src/default_settings.h b/src/default_settings.h index 7f8dfd6ec..a18142b35 100644 --- a/src/default_settings.h +++ b/src/default_settings.h @@ -84,6 +84,10 @@ #define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off #endif +#ifndef EMSESP_DEFAULT_ENUM_FORMAT +#define EMSESP_DEFAULT_ENUM_FORMAT 1 // Text +#endif + #ifndef EMSESP_DEFAULT_ANALOG_ENABLED #define EMSESP_DEFAULT_ANALOG_ENABLED false #endif @@ -156,4 +160,8 @@ #define EMSESP_DEFAULT_SOLAR_MAXFLOW 30 #endif +#ifndef EMSESP_DEFAULT_SENSOR_NAME +#define EMSESP_DEFAULT_SENSOR_NAME "" +#endif + #endif diff --git a/src/device_library.h b/src/device_library.h index b3cf33d41..3a3c92233 100644 --- a/src/device_library.h +++ b/src/device_library.h @@ -32,7 +32,7 @@ {122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {131, DeviceType::BOILER, F("GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, -{133, DeviceType::BOILER, F("GB125/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, +{133, DeviceType::BOILER, F("Logano GB125/KB195i/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {172, DeviceType::BOILER, F("Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP}, diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 5e4084018..d2710b230 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -84,17 +84,22 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_telegram_type(0x48D, F("HpPower"), false, MAKE_PF_CB(process_HpPower)); register_telegram_type(0x48F, F("HpOutdoor"), false, MAKE_PF_CB(process_HpOutdoor)); } + // MQTT commands for boiler topic - register_device_value( - TAG_BOILER_DATA, &dummybool_, DeviceValueType::BOOL, nullptr, FL_(wwtapactivated), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_tapwarmwater_activated)); - register_device_value(TAG_BOILER_DATA, &dummy8u_, DeviceValueType::ENUM, FL_(enum_reset), FL_(reset), DeviceValueUOM::NONE, MAKE_CF_CB(set_reset)); - // add values - // reserve_device_values(90); - - // main - boiler_data topic register_device_value(TAG_BOILER_DATA, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE); - id_ = product_id; + id_ = product_id; // note, must set the value after it has been initialized to have affect + + // first commands + register_device_value(TAG_BOILER_DATA, + &wWTapActivated_, + DeviceValueType::CMD, + FL_(enum_bool), + FL_(wwtapactivated), + DeviceValueUOM::BOOLEAN, + MAKE_CF_CB(set_tapwarmwater_activated)); + // reset is a command, so uses a dummy variable which is unused. It will not be shown in MQTT, Web or Console + register_device_value(TAG_BOILER_DATA, &dummy8u_, DeviceValueType::CMD, FL_(enum_reset), FL_(reset), DeviceValueUOM::LIST, MAKE_CF_CB(set_reset)); register_device_value(TAG_BOILER_DATA, &heatingActive_, DeviceValueType::BOOL, nullptr, FL_(heatingActive), DeviceValueUOM::BOOLEAN); register_device_value(TAG_BOILER_DATA, &tapwaterActive_, DeviceValueType::BOOL, nullptr, FL_(tapwaterActive), DeviceValueUOM::BOOLEAN); @@ -136,15 +141,17 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value(TAG_BOILER_DATA, &serviceCode_, DeviceValueType::TEXT, nullptr, FL_(serviceCode), DeviceValueUOM::NONE); register_device_value(TAG_BOILER_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, nullptr, FL_(serviceCodeNumber), DeviceValueUOM::NONE); register_device_value(TAG_BOILER_DATA, &maintenanceMessage_, DeviceValueType::TEXT, nullptr, FL_(maintenanceMessage), DeviceValueUOM::NONE); - register_device_value(TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::TEXT, nullptr, FL_(maintenanceDate), DeviceValueUOM::NONE); register_device_value(TAG_BOILER_DATA, &maintenanceType_, DeviceValueType::ENUM, FL_(enum_off_time_date), FL_(maintenanceType), - DeviceValueUOM::NONE, + DeviceValueUOM::LIST, MAKE_CF_CB(set_maintenance)); - register_device_value(TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, FL_(maintenanceTime), DeviceValueUOM::HOURS); + register_device_value( + TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, FL_(maintenanceTime), DeviceValueUOM::HOURS, MAKE_CF_CB(set_maintenancetime)); + register_device_value( + TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::TEXT, nullptr, FL_(maintenanceDate), DeviceValueUOM::NONE, MAKE_CF_CB(set_maintenancedate)); // heatpump info if (model() == EMS_DEVICE_FLAG_HEATPUMP) { register_device_value(TAG_BOILER_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), FL_(upTimeControl), DeviceValueUOM::MINUTES); @@ -184,14 +191,16 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value(TAG_BOILER_DATA_WW, &wWSetTemp_, DeviceValueType::UINT, nullptr, FL_(wWSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_warmwater_temp)); register_device_value(TAG_BOILER_DATA_WW, &wWType_, DeviceValueType::ENUM, FL_(enum_flow), FL_(wWType), DeviceValueUOM::NONE); register_device_value( - TAG_BOILER_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), FL_(wWComfort), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_mode)); + TAG_BOILER_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), FL_(wWComfort), DeviceValueUOM::LIST, MAKE_CF_CB(set_warmwater_mode)); register_device_value( TAG_BOILER_DATA_WW, &wWFlowTempOffset_, DeviceValueType::UINT, nullptr, FL_(wWFlowTempOffset), DeviceValueUOM::NONE, MAKE_CF_CB(set_wWFlowTempOffset)); register_device_value( TAG_BOILER_DATA_WW, &wWMaxPower_, DeviceValueType::UINT, nullptr, FL_(wWMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_warmwater_maxpower)); register_device_value( TAG_BOILER_DATA_WW, &wWCircPump_, DeviceValueType::BOOL, nullptr, FL_(wWCircPump), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_circulation_pump)); - register_device_value(TAG_BOILER_DATA_WW, &wWChargeType_, DeviceValueType::ENUM, FL_(enum_charge), FL_(wWChargeType), DeviceValueUOM::NONE); + register_device_value(TAG_BOILER_DATA_WW, &wWChargeType_, DeviceValueType::ENUM, FL_(enum_charge), FL_(wWChargeType), DeviceValueUOM::LIST); + register_device_value(TAG_BOILER_DATA_WW, &wWHystOn_, DeviceValueType::INT, nullptr, FL_(wWHystOn), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_hyst_on)); + register_device_value(TAG_BOILER_DATA_WW, &wWHystOff_, DeviceValueType::INT, nullptr, FL_(wWHystOff), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_hyst_off)); register_device_value(TAG_BOILER_DATA_WW, &wWDisinfectionTemp_, DeviceValueType::UINT, @@ -204,7 +213,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const DeviceValueType::ENUM, FL_(enum_freq), FL_(wWCircMode), - DeviceValueUOM::NONE, + DeviceValueUOM::LIST, MAKE_CF_CB(set_warmwater_circulation_mode)); register_device_value(TAG_BOILER_DATA_WW, &wWCirc_, DeviceValueType::BOOL, nullptr, FL_(wWCirc), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_circulation)); register_device_value(TAG_BOILER_DATA_WW, &wWCurTemp_, DeviceValueType::USHORT, FL_(div10), FL_(wWCurTemp), DeviceValueUOM::DEGREES); @@ -289,6 +298,11 @@ void Boiler::check_active(const bool force) { Mqtt::publish(F_(heating_active), Helpers::render_boolean(s, b)); } + // check if we can use tapactivated in flow systems + if ((wWType_ == 1) && !Helpers::hasValue(wWTapActivated_, EMS_VALUE_BOOL)) { + wWTapActivated_ = 1; + } + // check if tap water is active, bits 1 and 4 must be set // also check if there is a flowsensor and flow-type static bool flowsensor = false; @@ -315,8 +329,8 @@ void Boiler::process_UBAParameterWW(std::shared_ptr telegram) { // has_update(telegram->read_bitvalue(wwEquipt_,0,3)); // 8=boiler has ww has_update(telegram->read_value(wWActivated_, 1)); // 0xFF means on has_update(telegram->read_value(wWSelTemp_, 2)); - // has_update(telegram->read_value(wW?_, 3)); // Hyst on (default -5) - // has_update(telegram->read_value(wW?_, 4)); // (0xFF) Maybe: Hyst off -1? + has_update(telegram->read_value(wWHystOn_, 3)); // Hyst on (default -5) + has_update(telegram->read_value(wWHystOff_, 4)); // Hyst off (default -1) has_update(telegram->read_value(wWFlowTempOffset_, 5)); // default 40 has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on has_update(telegram->read_value(wWCircMode_, 7)); // 1=1x3min 6=6x3min 7=continuous @@ -454,9 +468,6 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr telegram 17)); // can be 0 if no sensor, handled in export_values has_update(telegram->read_value(sysPress_, 21)); - //has_update(telegram->read_value(temperatur_, 13)); // unknown temperature - //has_update(telegram->read_value(temperatur_, 27)); // unknown temperature - // read 3 char service code / installation status as appears on the display if ((telegram->message_length > 3) && (telegram->offset == 0)) { serviceCode_[0] = (serviceCode_[0] == '~') ? 0xF0 : serviceCode_[0]; @@ -526,7 +537,7 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr telegram /* * UBAParametersPlus - type 0xE6 - * parameters originaly taken from + * parameters originally taken from * https://github.com/Th3M3/buderus_ems-wiki/blob/master/Einstellungen%20des%20Regelger%C3%A4ts%20MC110.md * 88 0B E6 00 01 46 00 00 46 0A 00 01 06 FA 0A 01 02 64 01 00 00 1E 00 3C 01 00 00 00 01 00 9A * from: issue #732 @@ -540,7 +551,7 @@ void Boiler::process_UBAParametersPlus(std::shared_ptr telegram) has_update(telegram->read_value(boilHystOff_, 8)); has_update(telegram->read_value(boilHystOn_, 9)); has_update(telegram->read_value(burnMinPeriod_, 10)); - // has_update(telegram->read_value(pumpType_, 11)); // guess, RC300 manual: powercontroled, pressurcontrolled 1-4? + // has_update(telegram->read_value(pumpType_, 11)); // guess, RC300 manual: power controlled, pressure controlled 1-4? // has_update(telegram->read_value(pumpDelay_, 12)); // guess // has_update(telegram->read_value(pumpModMax_, 13)); // guess // has_update(telegram->read_value(pumpModMin_, 14)); // guess @@ -554,6 +565,8 @@ void Boiler::process_UBAParameterWWPlus(std::shared_ptr telegram 11)); // 1=1x3min... 6=6x3min, 7=continuous // has_update(telegram->read_value(wWDisinfectTemp_, 12)); // settings, status in E9 // has_update(telegram->read_value(wWSelTemp_, 6)); // settings, status in E9 + has_update(telegram->read_value(wWHystOn_, 7)); + has_update(telegram->read_value(wWHystOff_, 8)); } // 0xE9 - WW monitor ems+ @@ -902,6 +915,42 @@ bool Boiler::set_max_power(const char * value, const int8_t id) { return true; } +// set ww on hysteresis +bool Boiler::set_ww_hyst_on(const char * value, const int8_t id) { + int v = 0; + if (!Helpers::value2number(value, v)) { + LOG_WARNING(F("Set ww on hysteresis: Invalid value")); + return false; + } + + LOG_INFO(F("Setting ww on hysteresis on to %d C"), v); + if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) { + write_command(EMS_TYPE_UBAParameterWWPlus, 7, v, EMS_TYPE_UBAParameterWWPlus); + } else { + write_command(EMS_TYPE_UBAParameterWW, 3, v, EMS_TYPE_UBAParameterWW); + } + + return true; +} + +// set ww off hysteresis +bool Boiler::set_ww_hyst_off(const char * value, const int8_t id) { + int v = 0; + if (!Helpers::value2number(value, v)) { + LOG_WARNING(F("Set ww off hysteresis: Invalid value")); + return false; + } + + LOG_INFO(F("Setting ww off hysteresis off to %d C"), v); + if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) { + write_command(EMS_TYPE_UBAParameterWWPlus, 8, v, EMS_TYPE_UBAParameterWWPlus); + } else { + write_command(EMS_TYPE_UBAParameterWW, 4, v, EMS_TYPE_UBAParameterWW); + } + + return true; +} + // set warm water max power bool Boiler::set_warmwater_maxpower(const char * value, const int8_t id) { int v = 0; @@ -1063,17 +1112,12 @@ bool Boiler::set_warmwater_activated(const char * value, const int8_t id) { LOG_INFO(F("Setting boiler warm water active %s"), v ? "on" : "off"); // https://github.com/emsesp/EMS-ESP/issues/268 - uint8_t n; - if (EMSbus::is_ht3()) { - n = (v ? 0x08 : 0x00); // 0x08 is on, 0x00 is off - } else { - n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off - } + // 08 for HT3 seems to be wrong, see https://github.com/emsesp/EMS-ESP32/issues/89 if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) { write_command(EMS_TYPE_UBAParameterWWPlus, 1, v ? 1 : 0, EMS_TYPE_UBAParameterWWPlus); } else { - write_command(EMS_TYPE_UBAParameterWW, 1, n, 0x34); + write_command(EMS_TYPE_UBAParameterWW, 1, v ? 0xFF : 0, EMS_TYPE_UBAParameterWW); } return true; @@ -1098,14 +1142,16 @@ bool Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) { // a setting of 0x00 puts it back into normal operating mode // when in test mode we're able to mess around with the 3-way valve settings if (!v) { - // on + // DHW off message_data[0] = 0x5A; // test mode on message_data[1] = 0x00; // burner output 0% message_data[3] = 0x64; // boiler pump capacity 100% message_data[4] = 0xFF; // 3-way valve hot water only + wWTapActivated_ = 0; } else { // get out of test mode. Send all zeros. // telegram: 0B 08 1D 00 00 + wWTapActivated_ = 1; } write_command(EMS_TYPE_UBAFunctionTest, 0, message_data, sizeof(message_data), 0); @@ -1268,5 +1314,40 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) { LOG_WARNING(F("Setting maintenance: wrong format")); return false; } +//maintenance +bool Boiler::set_maintenancetime(const char * value, const int8_t id) { + int hrs; + if (Helpers::value2number(value, hrs)) { + if (hrs > 99 && hrs < 25600) { + LOG_INFO(F("Setting maintenance time %d hours"), hrs); + uint8_t data[2] = {1, (uint8_t)(hrs / 100)}; + write_command(0x15, 0, data, 2, 0x15); + return true; + } + } + LOG_WARNING(F("Setting maintenance: wrong format")); + return false; +} + +//maintenance +bool Boiler::set_maintenancedate(const char * value, const int8_t id) { + if (strlen(value) == 10) { // date + uint8_t day = (value[0] - '0') * 10 + (value[1] - '0'); + uint8_t month = (value[3] - '0') * 10 + (value[4] - '0'); + uint8_t year = (uint8_t)(Helpers::atoint(&value[6]) - 2000); + if (day > 0 && day < 32 && month > 0 && month < 13) { + LOG_INFO(F("Setting maintenance date to %02d.%02d.%04d"), day, month, year + 2000); + uint8_t data[5] = {2, (uint8_t)(maintenanceTime_ / 100), day, month, year}; + write_command(0x15, 0, data, 5, 0x15); + } else { + LOG_WARNING(F("Setting maintenance: wrong format %d.%d.%d"), day, month, year + 2000); + return false; + } + return true; + } + + LOG_WARNING(F("Setting maintenance: wrong format")); + return false; +} } // namespace emsesp diff --git a/src/devices/boiler.h b/src/devices/boiler.h index deeb3585c..766e7ebd8 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -49,7 +49,7 @@ class Boiler : public EMSdevice { static constexpr uint8_t EMS_TYPE_UBAParameters = 0x16; static constexpr uint8_t EMS_TYPE_UBAParametersPlus = 0xE6; static constexpr uint8_t EMS_TYPE_UBAParameterWWPlus = 0xEA; - static constexpr uint16_t EMS_TYPE_UBAInfomration = 0x495; + static constexpr uint16_t EMS_TYPE_UBAInformation = 0x495; static constexpr uint16_t EMS_TYPE_UBAEnergySupplied = 0x494; static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344 @@ -83,6 +83,9 @@ class Boiler : public EMSdevice { uint32_t wWStarts_; // Warm Water # starts uint32_t wWStarts2_; // Warm water control starts uint32_t wWWorkM_; // Warm Water # minutes + int8_t wWHystOn_; + int8_t wWHystOff_; + uint8_t wWTapActivated_; // maintenance-mode to switch DHW off uint16_t mixerTemp_; // mixing temperature uint16_t tankMiddleTemp_; // Tank middle temperature (TS3) @@ -220,6 +223,10 @@ class Boiler : public EMSdevice { bool set_pump_delay(const char * value, const int8_t id); bool set_reset(const char * value, const int8_t id); bool set_maintenance(const char * value, const int8_t id); + bool set_maintenancetime(const char * value, const int8_t id); + bool set_maintenancedate(const char * value, const int8_t id); + bool set_ww_hyst_on(const char * value, const int8_t id); + bool set_ww_hyst_off(const char * value, const int8_t id); }; } // namespace emsesp diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index 2edb644d3..2339c61c8 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -91,11 +91,8 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s TAG_NONE, &solarPumpTurnonDiff_, DeviceValueType::UINT, nullptr, FL_(solarPumpTurnonDiff), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_TurnonDiff)); register_device_value( TAG_NONE, &solarPumpTurnoffDiff_, DeviceValueType::UINT, nullptr, FL_(solarPumpTurnoffDiff), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_TurnoffDiff)); - register_device_value( - TAG_NONE, &collectorMaxTemp_, DeviceValueType::UINT, nullptr, FL_(collectorMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_CollectorMaxTemp)); - register_device_value( - TAG_NONE, &collectorMinTemp_, DeviceValueType::UINT, nullptr, FL_(collectorMinTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_CollectorMinTemp)); register_device_value(TAG_NONE, &collectorShutdown_, DeviceValueType::BOOL, nullptr, FL_(collectorShutdown), DeviceValueUOM::BOOLEAN); + register_device_value(TAG_NONE, &tankHeated_, DeviceValueType::BOOL, nullptr, FL_(tankHeated), DeviceValueUOM::BOOLEAN); register_device_value(TAG_NONE, &solarPower_, DeviceValueType::ULONG, nullptr, FL_(solarPower), DeviceValueUOM::W); register_device_value(TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH); register_device_value(TAG_NONE, &maxFlow_, DeviceValueType::UINT, FL_(div10), FL_(maxFlow), DeviceValueUOM::LMIN, MAKE_CF_CB(set_SM10MaxFlow)); @@ -143,7 +140,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s // telegram 0x035A register_device_value( - TAG_NONE, &solarPumpMode_, DeviceValueType::ENUM, FL_(enum_solarmode), FL_(solarPumpMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_solarMode)); + TAG_NONE, &solarPumpMode_, DeviceValueType::ENUM, FL_(enum_solarmode), FL_(solarPumpMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_solarMode)); register_device_value(TAG_NONE, &solarPumpKick_, DeviceValueType::BOOL, @@ -180,7 +177,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s DeviceValueType::ENUM, FL_(enum_collectortype), FL_(collector1Type), - DeviceValueUOM::NONE, + DeviceValueUOM::LIST, MAKE_CF_CB(set_collector1Type)); // Type of collector field 1, 01=flat, 02=vacuum } } @@ -219,12 +216,6 @@ bool Solar::publish_ha_config() { // Solar(0x30) -> All(0x00), (0x96), data: FF 18 19 0A 02 5A 27 0A 05 2D 1E 0F 64 28 0A void Solar::process_SM10Config(std::shared_ptr telegram) { has_update(telegram->read_value(solarIsEnabled_, 0)); // FF on - uint8_t colmax = collectorMaxTemp_ / 10; - has_update(telegram->read_value(colmax, 3)); - collectorMaxTemp_ = colmax * 10; - uint8_t colmin = collectorMinTemp_ / 10; - has_update(telegram->read_value(colmin, 4)); - collectorMinTemp_ = colmin * 10; has_update(telegram->read_value(solarPumpMinMod_, 2)); has_update(telegram->read_value(solarPumpTurnonDiff_, 7)); has_update(telegram->read_value(solarPumpTurnoffDiff_, 8)); @@ -237,7 +228,7 @@ void Solar::process_SM10Monitor(std::shared_ptr telegram) { uint8_t solarpumpmod = solarPumpModulation_; has_update(telegram->read_bitvalue(collectorShutdown_, 0, 3)); - // has_update(telegram->read_bitvalue(tankHeated_, 0, x)); // tank full, to be determined + has_update(telegram->read_bitvalue(tankHeated_, 0, 2)); // tankMaxTemp reached has_update(telegram->read_value(collectorTemp_, 2)); // collector temp from SM10, is *10 has_update(telegram->read_value(tankBottomTemp_, 5)); // tank bottom temp from SM10, is *10 has_update(telegram->read_value(solarPumpModulation_, 4)); // modulation solar pump @@ -315,9 +306,12 @@ void Solar::process_SM100SolarCircuitConfig(std::shared_ptr tele * e.g. B0 0B F9 00 00 02 5A 00 00 6E */ void Solar::process_SM100ParamCfg(std::shared_ptr telegram) { - uint16_t t_id; - uint8_t of; - int32_t min, def, max, cur; + uint16_t t_id = EMS_VALUE_USHORT_NOTSET; + uint8_t of = EMS_VALUE_UINT_NOTSET; + int32_t min = EMS_VALUE_USHORT_NOTSET; + int32_t def = EMS_VALUE_USHORT_NOTSET; + int32_t max = EMS_VALUE_USHORT_NOTSET; + int32_t cur = EMS_VALUE_USHORT_NOTSET; has_update(telegram->read_value(t_id, 1)); has_update(telegram->read_value(of, 3)); has_update(telegram->read_value(min, 5)); @@ -346,7 +340,7 @@ void Solar::process_SM100Monitor(std::shared_ptr telegram) { has_update(telegram->read_value(heatExchangerTemp_, 20)); // is *10 - TS6: Heat exchanger temperature sensor } -// SM100wwTemperatur - 0x07D6 +// SM100wwTemperature - 0x07D6 // Solar Module(0x2A) -> (0x00), (0x7D6), data: 01 C1 00 00 02 5B 01 AF 01 AD 80 00 01 90 void Solar::process_SM100wwTemperature(std::shared_ptr telegram) { has_update(telegram->read_value(wwTemp_1_, 0)); @@ -426,9 +420,9 @@ void Solar::process_SM100Status2(std::shared_ptr telegram) { * e.g. B0 0B FF 00 02 80 50 64 00 00 29 01 00 00 01 */ void Solar::process_SM100CollectorConfig(std::shared_ptr telegram) { - has_update(telegram->read_value(climateZone_, 0, 1)); - has_update(telegram->read_value(collector1Area_, 3, 2)); - has_update(telegram->read_value(collector1Type_, 5, 1)); + has_update(telegram->read_value(climateZone_, 0)); + has_update(telegram->read_value(collector1Area_, 3)); + has_update(telegram->read_enumvalue(collector1Type_, 5, 1)); } /* @@ -685,7 +679,7 @@ bool Solar::set_collector1Type(const char * value, const int8_t id) { if (!Helpers::value2enum(value, num, FL_(enum_collectortype))) { return false; } - write_command(0x380, 5, num, 0x380); + write_command(0x380, 5, num + 1, 0x380); return true; } diff --git a/src/devices/solar.h b/src/devices/solar.h index 502cd249e..b0a68d0ea 100644 --- a/src/devices/solar.h +++ b/src/devices/solar.h @@ -87,7 +87,7 @@ class Solar : public EMSdevice { // SM10Config - 0x96 uint8_t wwMinTemp_; - uint8_t maxFlow_; // set this to caltulate power + uint8_t maxFlow_; // set this to calculate power uint32_t solarPower_; // calculated from maxFlow std::deque energy; diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index f80bcf45f..03425e53e 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -455,13 +455,19 @@ bool Thermostat::thermostat_ha_cmd(const char * message, uint8_t hc_num) { } // check for mode first, which is a string - if (!set_mode(message, hc_num)) { + if (message[0] >= 'A') { + if (set_mode(message, hc_num)) { + return true; + } + } + if ((message[0] >= '0' && message[0] <= '9') || message[0] == '-') { // otherwise handle as a numerical temperature value and set the setpoint temp float f = strtof((char *)message, 0); set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num); + return true; } - return true; + return false; } // decodes the thermostat mode for the heating circuit based on the thermostat type @@ -696,6 +702,8 @@ void Thermostat::process_JunkersSet(std::shared_ptr telegram) { has_update(telegram->read_value(hc->daytemp, 17)); // is * 2 has_update(telegram->read_value(hc->nighttemp, 16)); // is * 2 has_update(telegram->read_value(hc->nofrosttemp, 15)); // is * 2 + has_update(telegram->read_enumvalue(hc->mode, 14, 1)); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto + hc->hamode = hc->mode ? hc->mode - 1 : 0; // set special HA mode: off, on, auto } // type 0x0179, ff @@ -708,6 +716,8 @@ void Thermostat::process_JunkersSet2(std::shared_ptr telegram) { has_update(telegram->read_value(hc->daytemp, 7)); // is * 2 has_update(telegram->read_value(hc->nighttemp, 6)); // is * 2 has_update(telegram->read_value(hc->nofrosttemp, 5)); // is * 2 + has_update(telegram->read_enumvalue(hc->mode, 4, 1)); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto + hc->hamode = hc->mode ? hc->mode - 1 : 0; // set special HA mode: off, on, auto } // type 0xA3 - for external temp settings from the the RC* thermostats (e.g. RC35) @@ -773,9 +783,7 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr telegram has_update(telegram->read_value(hc->curr_roomTemp, 4)); // value is * 10 has_update(telegram->read_value(hc->setpoint_roomTemp, 2)); // value is * 10 - has_update(telegram->read_value(hc->modetype, 0)); // 1 = nofrost, 2 = eco, 3 = heat - has_update(telegram->read_value(hc->mode, 1)); // 1 = manual, 2 = auto - hc->hamode = hc->mode; // set special HA mode + has_update(telegram->read_enumvalue(hc->modetype, 0, 1)); // 1 = nofrost, 2 = eco, 3 = heat } // type 0x02A5 - data from Worchester CRF200 @@ -917,7 +925,7 @@ void Thermostat::process_RC300OutdoorTemp(std::shared_ptr telegr // 0x240 RC300 parameter void Thermostat::process_RC300Settings(std::shared_ptr telegram) { - has_update(telegram->read_value(ibaBuildingType_, 9)); // 1=light, 2=medium, 3=heavy + has_update(telegram->read_enumvalue(ibaBuildingType_, 9, 1)); // 1=light, 2=medium, 3=heavy has_update(telegram->read_value(ibaMinExtTemperature_, 10)); } @@ -1096,10 +1104,10 @@ void Thermostat::process_RCErrorMessage(std::shared_ptr telegram // data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr if (telegram->message_data[4] & 0x80) { // valid date char code[3]; - uint16_t codeNo; - code[0] = telegram->message_data[0]; - code[1] = telegram->message_data[1]; - code[2] = 0; + uint16_t codeNo = EMS_VALUE_USHORT_NOTSET; + code[0] = telegram->message_data[0]; + code[1] = telegram->message_data[1]; + code[2] = 0; telegram->read_value(codeNo, 2); uint16_t year = (telegram->message_data[4] & 0x7F) + 2000; uint8_t month = telegram->message_data[5]; @@ -1199,11 +1207,11 @@ bool Thermostat::set_building(const char * value, const int8_t id) { if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { if (Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType))) { LOG_INFO(F("Setting building to %s"), value); - write_command(0x240, 9, bd, 0x240); + write_command(0x240, 9, bd + 1, 0x240); return true; } } else { - if (Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType2))) { + if (Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType))) { LOG_INFO(F("Setting building to %s"), value); write_command(EMS_TYPE_IBASettings, 6, bd, EMS_TYPE_IBASettings); return true; @@ -1467,13 +1475,39 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) { // sets the thermostat working mode, where mode is a string // converts string mode to HeatingCircuit::Mode bool Thermostat::set_mode(const char * value, const int8_t id) { - // quit if its numerical, as it could be mistaken as a temperature value - if (value[0] < 'A') { + std::string mode(20, '\0'); + if (strlen(value) >= 20) { + LOG_WARNING(F("Set mode: Invalid mode")); return false; } - std::string mode(10, '\0'); - if (!Helpers::value2string(value, mode)) { + if (value[0] >= '0' && value[0] <= '9') { + uint8_t num = value[0] - '0'; + switch (model()) { + case EMSdevice::EMS_DEVICE_FLAG_RC20: + case EMSdevice::EMS_DEVICE_FLAG_RC20_N: + mode = uuid::read_flash_string(FL_(enum_mode2)[num]); + break; + case EMSdevice::EMS_DEVICE_FLAG_RC30: + case EMSdevice::EMS_DEVICE_FLAG_RC35: + case EMSdevice::EMS_DEVICE_FLAG_RC30_N: + mode = uuid::read_flash_string(FL_(enum_mode3)[num]); + break; + case EMSdevice::EMS_DEVICE_FLAG_RC300: + case EMSdevice::EMS_DEVICE_FLAG_RC100: + mode = uuid::read_flash_string(FL_(enum_mode)[num]); + break; + case EMSdevice::EMS_DEVICE_FLAG_JUNKERS: + mode = uuid::read_flash_string(FL_(enum_mode4)[num]); + break; + case EMSdevice::EMS_DEVICE_FLAG_CRF: + mode = uuid::read_flash_string(FL_(enum_mode5)[num]); + break; + default: + LOG_WARNING(F("Set mode: Invalid mode")); + return false; + } + } else if (!Helpers::value2string(value, mode)) { LOG_WARNING(F("Set mode: Invalid mode")); return false; } @@ -1583,7 +1617,7 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) { } else { offset = EMS_OFFSET_JunkersSetMessage_set_mode; } - validate_typeid = monitor_typeids[hc_p]; + validate_typeid = set_typeids[hc_p]; if (mode == HeatingCircuit::Mode::NOFROST) { set_mode_value = 0x01; } else if (mode == HeatingCircuit::Mode::ECO || (mode == HeatingCircuit::Mode::NIGHT)) { @@ -2129,7 +2163,7 @@ void Thermostat::register_device_values() { case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC300: register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); - register_device_value(TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), FL_(floordrystatus), DeviceValueUOM::NONE); + register_device_value(TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), FL_(floordrystatus), DeviceValueUOM::LIST); register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp2_, DeviceValueType::SHORT, FL_(div10), FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES); register_device_value(TAG_THERMOSTAT_DATA, &floordrytemp_, DeviceValueType::UINT, nullptr, FL_(floordrytemp), DeviceValueUOM::DEGREES); register_device_value(TAG_THERMOSTAT_DATA, @@ -2137,14 +2171,21 @@ void Thermostat::register_device_values() { DeviceValueType::ENUM, FL_(enum_ibaBuildingType), FL_(ibaBuildingType), - DeviceValueUOM::NONE, + DeviceValueUOM::LIST, MAKE_CF_CB(set_building)); + register_device_value(TAG_THERMOSTAT_DATA, + &ibaMinExtTemperature_, + DeviceValueType::INT, + nullptr, + FL_(ibaMinExtTemperature), + DeviceValueUOM::DEGREES, + MAKE_CF_CB(set_minexttemp)); register_device_value(TAG_THERMOSTAT_DATA, &wwSetTemp_, DeviceValueType::UINT, nullptr, FL_(wwSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemp)); - register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); + register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), FL_(wwMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwmode)); register_device_value( TAG_THERMOSTAT_DATA, &wwSetTempLow_, DeviceValueType::UINT, nullptr, FL_(wwSetTempLow), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemplow)); register_device_value( - TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode)); + TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wWCircMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwcircmode)); register_device_value(TAG_THERMOSTAT_DATA, &wwExtra1_, DeviceValueType::UINT, nullptr, FL_(wwExtra1), DeviceValueUOM::DEGREES); register_device_value(TAG_THERMOSTAT_DATA, &wwExtra2_, DeviceValueType::UINT, nullptr, FL_(wwExtra2), DeviceValueUOM::DEGREES); break; @@ -2154,8 +2195,8 @@ void Thermostat::register_device_values() { break; case EMS_DEVICE_FLAG_RC30_N: register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime - register_device_value(TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), FL_(ibaMainDisplay), DeviceValueUOM::NONE); - register_device_value(TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::NONE); + register_device_value(TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), FL_(ibaMainDisplay), DeviceValueUOM::LIST); + register_device_value(TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::LIST); register_device_value(TAG_THERMOSTAT_DATA, &ibaClockOffset_, DeviceValueType::UINT, @@ -2180,13 +2221,13 @@ void Thermostat::register_device_values() { register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, - FL_(enum_ibaBuildingType2), + FL_(enum_ibaBuildingType), FL_(ibaBuildingType), - DeviceValueUOM::NONE, + DeviceValueUOM::LIST, MAKE_CF_CB(set_building)); - register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); + register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwmode)); register_device_value( - TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode)); + TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwcircmode)); break; case EMS_DEVICE_FLAG_RC35: register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); @@ -2210,13 +2251,13 @@ void Thermostat::register_device_values() { register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, - FL_(enum_ibaBuildingType2), + FL_(enum_ibaBuildingType), FL_(ibaBuildingType), - DeviceValueUOM::NONE, + DeviceValueUOM::LIST, MAKE_CF_CB(set_building)); - register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); + register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwmode)); register_device_value( - TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode)); + TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwcircmode)); break; case EMS_DEVICE_FLAG_JUNKERS: register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); @@ -2290,8 +2331,8 @@ void Thermostat::register_device_values_hc(std::shared_ptrmode, DeviceValueType::ENUM, FL_(enum_mode), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); - register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype), FL_(modetype), DeviceValueUOM::NONE); + register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode)); + register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype), FL_(modetype), DeviceValueUOM::LIST); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(ecotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ecotemp)); register_device_value(tag, &hc->manualtemp, DeviceValueType::UINT, FL_(div2), FL_(manualtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_manualtemp)); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(comforttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_comforttemp)); @@ -2303,34 +2344,34 @@ void Thermostat::register_device_values_hc(std::shared_ptrroominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfluence)); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp)); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES); - register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE); + register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::LIST); register_device_value( - tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summersetmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_summermode)); + tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summersetmode), DeviceValueUOM::LIST, MAKE_CF_CB(set_summermode)); register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::BOOLEAN); register_device_value( - tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode)); + tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::LIST, MAKE_CF_CB(set_controlmode)); register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp)); break; case EMS_DEVICE_FLAG_CRF: - register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::NONE); - register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype5), FL_(modetype), DeviceValueUOM::NONE); + register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::LIST); + register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype5), FL_(modetype), DeviceValueUOM::LIST); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES); break; case EMS_DEVICE_FLAG_RC20: - register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); + register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode)); break; case EMS_DEVICE_FLAG_RC20_N: - register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); - register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype2), FL_(modetype), DeviceValueUOM::NONE); + register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode)); + register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype2), FL_(modetype), DeviceValueUOM::LIST); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); break; case EMS_DEVICE_FLAG_RC30_N: case EMS_DEVICE_FLAG_RC35: - register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); - register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::NONE); + register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode)); + register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::LIST); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, nullptr, FL_(designtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_designtemp)); @@ -2345,22 +2386,22 @@ void Thermostat::register_device_values_hc(std::shared_ptrminflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); register_device_value(tag, &hc->flowtempoffset, DeviceValueType::UINT, nullptr, FL_(flowtempoffset), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flowtempoffset)); - register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE); - register_device_value(tag, &hc->reducemode, DeviceValueType::ENUM, FL_(enum_reducemode), FL_(reducemode), DeviceValueUOM::NONE, MAKE_CF_CB(set_reducemode)); + register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::LIST); + register_device_value(tag, &hc->reducemode, DeviceValueType::ENUM, FL_(enum_reducemode), FL_(reducemode), DeviceValueUOM::LIST, MAKE_CF_CB(set_reducemode)); register_device_value( - tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode)); - register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control)); + tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::LIST, MAKE_CF_CB(set_controlmode)); + register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control), FL_(control), DeviceValueUOM::LIST, MAKE_CF_CB(set_control)); register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->pause, DeviceValueType::UINT, nullptr, FL_(pause), DeviceValueUOM::HOURS, MAKE_CF_CB(set_pause)); register_device_value(tag, &hc->party, DeviceValueType::UINT, nullptr, FL_(party), DeviceValueUOM::HOURS, MAKE_CF_CB(set_party)); register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp)); register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, nullptr, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp)); register_device_value(tag, &hc->remotetemp, DeviceValueType::SHORT, FL_(div10), FL_(remotetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_remotetemp)); - register_device_value(tag, &dummychar_, DeviceValueType::TEXT, nullptr, FL_(switchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime)); + register_device_value(tag, &dummy_, DeviceValueType::CMD, nullptr, FL_(switchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime)); break; case EMS_DEVICE_FLAG_JUNKERS: - register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode4), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); - register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype4), FL_(modetype), DeviceValueUOM::NONE); + register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode4), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode)); + register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype4), FL_(modetype), DeviceValueUOM::LIST); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(heattemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_heattemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(ecotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ecotemp)); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, FL_(div2), FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp)); diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index 9a76726cc..edf994582 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -146,7 +146,7 @@ class Thermostat : public EMSdevice { char errorCode_[15]; // code from 0xA2 as string i.e. "A22(816)" uint16_t errorNumber_; // used internally to build error code char lastCode_[30]; // error log - char dummychar_[5]; // for commands with no output + uint8_t dummy_; // for commands with no output // Installation parameters uint8_t ibaMainDisplay_; // 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 diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 05509f774..069bb035a 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -40,7 +40,8 @@ static const __FlashStringHelper * DeviceValueUOM_s[] __attribute__((__aligned__ F_(seconds), F_(dbm), F_(num), - F_(bool) + F_(bool), + F_(blank) }; @@ -421,12 +422,12 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) { Mqtt::show_topic_handlers(shell, device_type_); } -void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) { +void EMSdevice::register_mqtt_topic(const std::string & topic, const mqtt_sub_function_p f) { Mqtt::subscribe(device_type_, topic, f); } // register a callback function for a specific telegram type -void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p f) { +void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p f) { telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f); } @@ -437,7 +438,7 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __ // type: one of DeviceValueType // options: options for enum or a divider for int (e.g. F("10")) // short_name: used in Mqtt as keys -// full name: used in Web and Console unless empty (nullptr) +// full_name: used in Web and Console unless empty (nullptr) // uom: unit of measure from DeviceValueUOM void EMSdevice::register_device_value(uint8_t tag, void * value_p, @@ -479,24 +480,29 @@ void EMSdevice::register_device_value(uint8_t tag, } // function with min and max values +// adds a new command to the command list void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, - cmdfunction_p f, + const cmd_function_p f, int32_t min, uint32_t max) { register_device_value(tag, value_p, type, options, name[0], name[1], uom, (f != nullptr), min, max); - if (f != nullptr) { - if (tag >= TAG_HC1 && tag <= TAG_HC4) { - Command::add(device_type_, name[0], f, name[1], FLAG_HC); - } else if (tag >= TAG_WWC1 && tag <= TAG_WWC4) { - Command::add(device_type_, name[0], f, name[1], FLAG_WWC); - } else { - Command::add(device_type_, name[0], f, name[1], 0); - } + + // add a new command if it has a function attached + if (f == nullptr) { + return; + } + + if (tag >= TAG_HC1 && tag <= TAG_HC4) { + Command::add(device_type_, name[0], f, name[1], CommandFlag::MQTT_SUB_FLAG_HC | CommandFlag::ADMIN_ONLY); + } else if (tag >= TAG_WWC1 && tag <= TAG_WWC4) { + Command::add(device_type_, name[0], f, name[1], CommandFlag::MQTT_SUB_FLAG_WWC | CommandFlag::ADMIN_ONLY); + } else { + Command::add(device_type_, name[0], f, name[1], CommandFlag::MQTT_SUB_FLAG_NORMAL | CommandFlag::ADMIN_ONLY); } } @@ -507,7 +513,7 @@ void EMSdevice::register_device_value(uint8_t tag, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, - cmdfunction_p f) { + const cmd_function_p f) { register_device_value(tag, value_p, type, options, name, uom, f, 0, 0); } @@ -556,8 +562,8 @@ void EMSdevice::generate_values_json_web(JsonObject & json) { JsonArray data = json.createNestedArray("data"); for (const auto & dv : devicevalues_) { - // ignore if full_name empty - if (dv.full_name != nullptr) { + // ignore if full_name empty and also commands + if ((dv.full_name != nullptr) && (dv.type != DeviceValueType::CMD)) { JsonObject obj; // create the object, if needed // handle Booleans (true, false) @@ -580,6 +586,12 @@ void EMSdevice::generate_values_json_web(JsonObject & json) { } } + // // handle commands without value + // else if (dv.type == DeviceValueType::CMD) { + // obj = data.createNestedObject(); + // obj["v"] = ""; + // } + else { // handle Integers and Floats // If a divider is specified, do the division to 2 decimals places and send back as double/float @@ -634,6 +646,16 @@ void EMSdevice::generate_values_json_web(JsonObject & json) { } else { obj["c"] = ""; } + + // add enum and text option settings + if ((dv.uom == DeviceValueUOM::LIST) && dv.has_cmd) { + JsonArray l = obj.createNestedArray("l"); + for (uint8_t i = 0; i < dv.options_size; i++) { + if (!uuid::read_flash_string(dv.options[i]).empty()) { + l.add(uuid::read_flash_string(dv.options[i])); + } + } + } } } } @@ -679,8 +701,8 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t switch (dv.type) { case DeviceValueType::ENUM: { - if (Helpers::hasValue((uint8_t)(*(uint8_t *)(dv.value_p)))) { - if (Mqtt::bool_format() == BOOL_FORMAT_10) { + if (*(uint8_t *)(dv.value_p) < dv.options_size) { + if (EMSESP::enum_format() == ENUM_FORMAT_NUMBER) { json[value] = (uint8_t)(*(uint8_t *)(dv.value_p)); } else { json[value] = dv.options[*(uint8_t *)(dv.value_p)]; // text @@ -753,7 +775,16 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t case DeviceValueType::BOOL: { if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { - json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false; + uint8_t bool_format = EMSESP::bool_format(); + if (bool_format == BOOL_FORMAT_ONOFF) { + json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? F_(on) : F_(off); + } else if (bool_format == BOOL_FORMAT_ONOFF_CAP) { + json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? F_(ON) : F_(OFF); + } else if (bool_format == BOOL_FORMAT_TRUEFALSE) { + json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false; + } else { + json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? 1 : 0; + } } json[type] = F("boolean"); break; @@ -775,13 +806,17 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t json[type] = F_(text); break; + case DeviceValueType::CMD: + json[type] = F_(command); + break; + default: json[type] = F_(unknown); break; } // add uom if it's not a " " (single space) - if ((dv.uom != DeviceValueUOM::NONE) && (dv.uom != DeviceValueUOM::NUM) && (dv.uom != DeviceValueUOM::BOOLEAN)) { + if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") { json["unit"] = EMSdevice::uom_to_string(dv.uom); } @@ -841,24 +876,17 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { // see how to render the value depending on the setting // when in console mode we always use on and off - if ((Mqtt::bool_format() == BOOL_FORMAT_ONOFF) || console) { - // on or off as strings + uint8_t bool_format = EMSESP::bool_format(); + if ((bool_format == BOOL_FORMAT_ONOFF) || console) { json[name] = *(uint8_t *)(dv.value_p) ? F_(on) : F_(off); - has_value = true; - } else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF_CAP) { - // on or off as strings + } else if (bool_format == BOOL_FORMAT_ONOFF_CAP) { json[name] = *(uint8_t *)(dv.value_p) ? F_(ON) : F_(OFF); - has_value = true; - } else if (Mqtt::bool_format() == BOOL_FORMAT_TRUEFALSE) { - // true or false values (as real booleans, not strings) + } else if (bool_format == BOOL_FORMAT_TRUEFALSE) { json[name] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false; - has_value = true; } else { - // numerical 1 or 0 json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0; - has_value = true; } - + has_value = true; } // handle TEXT strings @@ -870,7 +898,8 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter // handle ENUMs else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) { if (*(uint8_t *)(dv.value_p) < dv.options_size) { - if (Mqtt::bool_format() == BOOL_FORMAT_10) { + // check for numeric enum-format, but "hamode" always as text + if ((EMSESP::enum_format() == ENUM_FORMAT_NUMBER) && (dv.short_name != FL_(hamode)[0])) { json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)); } else { json[name] = dv.options[*(uint8_t *)(dv.value_p)]; @@ -960,7 +989,7 @@ void EMSdevice::publish_mqtt_ha_sensor() { void EMSdevice::ha_config_clear() { for (auto & dv : devicevalues_) { - // dv.ha &= ~DeviceValueHA::HA_DONE; // repubish all with values + // dv.ha &= ~DeviceValueHA::HA_DONE; // republish all with values dv.ha = DeviceValueHA::HA_NONE; // also wait for new value } ha_config_done(false); diff --git a/src/emsdevice.h b/src/emsdevice.h index bd414aa0d..38cc4f25c 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -39,7 +39,8 @@ enum DeviceValueType : uint8_t { ULONG, TIME, // same as ULONG (32 bits) ENUM, - TEXT + TEXT, + CMD // special for commands only }; @@ -64,7 +65,8 @@ enum DeviceValueUOM : uint8_t { SECONDS, // 13 DBM, // 14 NUM, // 15 - BOOLEAN // 16 + BOOLEAN, // 16 + LIST // 17 }; @@ -118,9 +120,6 @@ enum DeviceValueTAG : uint8_t { }; -// mqtt flags for command subscriptions -enum MqttSubFlag : uint8_t { FLAG_NORMAL = 0, FLAG_HC, FLAG_WWC, FLAG_NOSUB }; - // mqtt-HA flags enum DeviceValueHA : uint8_t { HA_NONE = 0, HA_VALUE, HA_DONE }; @@ -168,6 +167,7 @@ class EMSdevice { return ((device_id & 0x7F) == (device_id_ & 0x7F)); } + // flags inline void add_flags(uint8_t flags) { flags_ |= flags; } @@ -239,7 +239,7 @@ class EMSdevice { 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); + void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p cb); bool handle_telegram(std::shared_ptr telegram); std::string get_value_uom(const char * key); @@ -263,7 +263,7 @@ class EMSdevice { const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, - cmdfunction_p f, + const cmd_function_p f, int32_t min, uint32_t max); void register_device_value(uint8_t tag, @@ -272,22 +272,21 @@ class EMSdevice { const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, - cmdfunction_p f); + const cmd_function_p f); void register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom); - // void register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, int32_t min, uint32_t max); void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value); + void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0); - void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f); - // void register_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag = 0); + void register_mqtt_topic(const std::string & topic, const mqtt_sub_function_p f); void publish_mqtt_ha_sensor(); @@ -403,7 +402,7 @@ class EMSdevice { bool fetch_; // if this type_id be queried automatically process_function_p process_function_; - TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p process_function) + TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p process_function) : telegram_type_id_(telegram_type_id) , telegram_type_name_(telegram_type_name) , fetch_(fetch) diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 9fda46f59..6eb36ab0e 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -37,10 +37,10 @@ ESP8266React EMSESP::esp8266React(&webServer, &LITTLEFS); WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LITTLEFS, EMSESP::esp8266React.getSecurityManager()); #endif -WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager()); -WebDevicesService EMSESP::webDevicesService = WebDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager()); -WebAPIService EMSESP::webAPIService = WebAPIService(&webServer, EMSESP::esp8266React.getSecurityManager()); -WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP::esp8266React.getSecurityManager()); +WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager()); +WebDataService EMSESP::webDataService = WebDataService(&webServer, EMSESP::esp8266React.getSecurityManager()); +WebAPIService EMSESP::webAPIService = WebAPIService(&webServer, EMSESP::esp8266React.getSecurityManager()); +WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP::esp8266React.getSecurityManager()); using DeviceFlags = EMSdevice; using DeviceType = EMSdevice::DeviceType; @@ -75,6 +75,9 @@ uint8_t EMSESP::publish_all_idx_ = 0; uint8_t EMSESP::unique_id_count_ = 0; bool EMSESP::trace_raw_ = false; uint64_t EMSESP::tx_delay_ = 0; +uint8_t EMSESP::bool_format_ = 1; +uint8_t EMSESP::enum_format_ = 1; +uint16_t EMSESP::wait_validate_ = 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 @@ -361,8 +364,13 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) { shell.printfln(F("Dallas temperature sensors:")); uint8_t i = 1; char s[7]; + char s2[7]; for (const auto & device : sensor_devices()) { - shell.printfln(F(" Sensor %d, ID: %s, Temperature: %s °C"), i++, device.to_string().c_str(), Helpers::render_value(s, device.temperature_c, 10)); + shell.printfln(F(" Sensor %d, ID: %s, Temperature: %s °C (offset %s)"), + i++, + device.to_string().c_str(), + Helpers::render_value(s, device.temperature_c, 10), + Helpers::render_value(s2, device.offset(), 10)); } shell.println(); } @@ -548,7 +556,7 @@ void EMSESP::publish_response(std::shared_ptr telegram) { doc["dest"] = Helpers::hextoa(buffer, telegram->dest); doc["type"] = Helpers::hextoa(buffer, telegram->type_id); doc["offset"] = Helpers::hextoa(buffer, telegram->offset); - strcpy(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str()); + strcpy(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length - 1).c_str()); // exclude CRC doc["data"] = buffer; if (telegram->message_length <= 4) { @@ -613,10 +621,10 @@ std::string EMSESP::pretty_telegram(std::shared_ptr telegram) { uint8_t offset = telegram->offset; // find name for src and dest by looking up known devices - std::string src_name; - std::string dest_name; - std::string type_name; - std::string direction; + std::string src_name(""); + std::string dest_name(""); + std::string type_name(""); + std::string direction(""); for (const auto & emsdevice : emsdevices) { if (emsdevice) { // get src & dest @@ -774,7 +782,7 @@ void EMSESP::process_version(std::shared_ptr telegram) { bool EMSESP::process_telegram(std::shared_ptr telegram) { // if watching or reading... if ((telegram->type_id == read_id_) && (telegram->dest == txservice_.ems_bus_id())) { - LOG_NOTICE(pretty_telegram(telegram).c_str()); + LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str()); publish_response(telegram); if (!read_next_) { read_id_ = WATCH_ID_NONE; @@ -783,12 +791,12 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { } else if (watch() == WATCH_ON) { if ((watch_id_ == WATCH_ID_NONE) || (telegram->type_id == watch_id_) || ((watch_id_ < 0x80) && ((telegram->src == watch_id_) || (telegram->dest == watch_id_)))) { - LOG_NOTICE(pretty_telegram(telegram).c_str()); + LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str()); } else if (!trace_raw_) { - LOG_TRACE(pretty_telegram(telegram).c_str()); + LOG_TRACE(F("%s"), pretty_telegram(telegram).c_str()); } } else if (!trace_raw_) { - LOG_TRACE(pretty_telegram(telegram).c_str()); + LOG_TRACE(F("%s"), pretty_telegram(telegram).c_str()); } // only process broadcast telegrams or ones sent to us on request @@ -832,6 +840,9 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too } } + if (wait_validate_ == telegram->type_id) { + wait_validate_ = 0; + } break; } } @@ -840,7 +851,7 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { if (!found) { LOG_DEBUG(F("No telegram type handler found for ID 0x%02X (src 0x%02X)"), telegram->type_id, telegram->src); if (watch() == WATCH_UNKNOWN) { - LOG_NOTICE(pretty_telegram(telegram).c_str()); + LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str()); } if (first_scan_done_ && !knowndevice && (telegram->src != EMSbus::ems_bus_id()) && (telegram->src != 0x0B) && (telegram->src != 0x0C) && (telegram->src != 0x0D)) { @@ -975,18 +986,18 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: return true; } - Command::add_with_json( + Command::add_json( device_type, F_(info), [device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id, true); }, F_(info_cmd)); - Command::add_with_json( + Command::add_json( device_type, F("info_short"), [device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id, false); }, nullptr, - true); // this command is hidden - Command::add_with_json( + CommandFlag::HIDDEN); // this command is hidden + Command::add_json( device_type, F_(commands), [device_type](const char * value, const int8_t id, JsonObject & json) { return command_commands(device_type, json, id); }, @@ -1198,6 +1209,7 @@ void EMSESP::start() { shower_.start(); // initialize shower timer and shower alert dallassensor_.start(); // dallas external sensors webServer.begin(); // start web server + webLogService.start(); // start web log service emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem frag issues @@ -1231,7 +1243,8 @@ void EMSESP::loop() { console_.loop(); // telnet/serial console - // delay(1); // helps telnet catch up. don't think its needed in ESP32 3.1.0 + // https://github.com/emsesp/EMS-ESP32/issues/78#issuecomment-877599145 + delay(1); // helps telnet catch up. don't think its needed in ESP32 3.1.0 } } // namespace emsesp diff --git a/src/emsesp.h b/src/emsesp.h index c262663ea..47ef4858f 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -36,7 +36,7 @@ #include #include "web/WebStatusService.h" -#include "web/WebDevicesService.h" +#include "web/WebDataService.h" #include "web/WebSettingsService.h" #include "web/WebAPIService.h" #include "web/WebLogService.h" @@ -65,8 +65,8 @@ #define EMSESP_JSON_SIZE_XXLARGE_DYN 8192 // for extra very very large json docs, using DynamicJsonDocument // helpers for callback functions -#define MAKE_PF_CB(__f) [&](std::shared_ptr t) { __f(t); } // for process function callbacks to register_telegram_type() -#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id) { return __f(value, id); } // for command function callbacks to register_mqtt_cmd() +#define MAKE_PF_CB(__f) [&](std::shared_ptr t) { __f(t); } // for Process Function callbacks to EMSDevice::process_function_p +#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id) { return __f(value, id); } // for Command Function callbacks Command::cmd_function_p namespace emsesp { @@ -144,6 +144,22 @@ class EMSESP { return (dallassensor_.dallas_enabled()); } + static uint8_t bool_format() { + return bool_format_; + } + + static void bool_format(uint8_t format) { + bool_format_ = format; + } + + static uint8_t enum_format() { + return enum_format_; + } + + static void enum_format(uint8_t format) { + enum_format_ = format; + } + enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW, WATCH_UNKNOWN }; static void watch_id(uint16_t id); static uint16_t watch_id() { @@ -162,6 +178,12 @@ class EMSESP { static void set_read_id(uint16_t id) { read_id_ = id; } + static bool wait_validate() { + return (wait_validate_ != 0); + } + static void wait_validate(uint16_t wait) { + wait_validate_ = wait; + } enum Bus_status : uint8_t { BUS_STATUS_CONNECTED = 0, BUS_STATUS_TX_ERRORS, BUS_STATUS_OFFLINE }; static uint8_t bus_status(); @@ -204,7 +226,7 @@ class EMSESP { static ESP8266React esp8266React; static WebSettingsService webSettingsService; static WebStatusService webStatusService; - static WebDevicesService webDevicesService; + static WebDataService webDataService; static WebAPIService webAPIService; static WebLogService webLogService; @@ -246,6 +268,9 @@ class EMSESP { static uint8_t unique_id_count_; static bool trace_raw_; static uint64_t tx_delay_; + static uint8_t bool_format_; + static uint8_t enum_format_; + static uint16_t wait_validate_; }; } // namespace emsesp diff --git a/src/helpers.cpp b/src/helpers.cpp index 1474fe9a3..949326686 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -17,7 +17,7 @@ */ #include "helpers.h" -#include "mqtt.h" +#include "emsesp.h" namespace emsesp { @@ -36,14 +36,14 @@ char * Helpers::hextoa(char * result, const uint8_t value) { #ifdef EMSESP_STANDALONE // special function to work outside of ESP's libraries char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) { - unsigned long t = 0, res = 0; - unsigned long tmp = value; - int count = 0; - if (NULL == ptr) { return NULL; } + unsigned long t = 0; + unsigned long tmp = value; + int count = 0; + if (tmp == 0) { count++; } @@ -57,6 +57,8 @@ char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) { *ptr = '\0'; + unsigned long res = 0; + do { res = value - base * (t = value / base); if (res < 10) { @@ -124,7 +126,7 @@ char * Helpers::smallitoa(char * result, const uint16_t value) { // work out how to display booleans char * Helpers::render_boolean(char * result, bool value) { - uint8_t bool_format_ = Mqtt::bool_format(); + uint8_t bool_format_ = EMSESP::bool_format(); if (bool_format_ == BOOL_FORMAT_ONOFF) { strlcpy(result, value ? uuid::read_flash_string(F_(on)).c_str() : uuid::read_flash_string(F_(off)).c_str(), 5); } else if (bool_format_ == BOOL_FORMAT_ONOFF_CAP) { @@ -417,12 +419,15 @@ bool Helpers::value2number(const char * v, int & value) { // checks if we can convert a char string to a float value bool Helpers::value2float(const char * v, float & value) { + value = 0; if ((v == nullptr) || (strlen(v) == 0)) { - value = 0; return false; } - value = atof((char *)v); - return true; + if (v[0] == '-' || v[0] == '.' || (v[0] >= '0' && v[0] <= '9')) { + value = atof((char *)v); + return true; + } + return false; } // https://stackoverflow.com/questions/313970/how-to-convert-stdstring-to-lower-case diff --git a/src/locale_EN.h b/src/locale_EN.h index 2ea1b96a1..0e7f9d454 100644 --- a/src/locale_EN.h +++ b/src/locale_EN.h @@ -71,6 +71,7 @@ MAKE_PSTR_WORD(pin) MAKE_PSTR_WORD(publish) MAKE_PSTR_WORD(timeout) MAKE_PSTR_WORD(board_profile) +MAKE_PSTR_WORD(sensorname) // for commands MAKE_PSTR_WORD(call) @@ -124,6 +125,7 @@ MAKE_PSTR(invalid_watch, "Invalid watch type") MAKE_PSTR(data_mandatory, "\"XX XX ...\"") MAKE_PSTR(asterisks, "********") MAKE_PSTR(n_mandatory, "") +MAKE_PSTR(sensorid_optional, "[sensor ID]") MAKE_PSTR(id_optional, "[id|hc]") MAKE_PSTR(data_optional, "[data]") MAKE_PSTR(offset_optional, "[offset]") @@ -173,8 +175,9 @@ MAKE_PSTR(w, "W") MAKE_PSTR(kb, "KB") MAKE_PSTR(seconds, "seconds") MAKE_PSTR(dbm, "dBm") -MAKE_PSTR(num, " ") // this is hack so HA renders numbers correctly -MAKE_PSTR(bool, " ") // this is hack so HA renders numbers correctly +MAKE_PSTR(num, " ") // this is hack so HA renders numbers correctly +MAKE_PSTR(bool, " ") // this is hack so HA renders numbers correctly +MAKE_PSTR(blank, " ") // this is hack so HA renders numbers correctly // TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp // use empty string if want to suppress showing tags @@ -242,13 +245,13 @@ MAKE_PSTR_LIST(enum_charge, F_(chargepump), F_(3wayvalve)) MAKE_PSTR_LIST(enum_comfort, F_(hot), F_(eco), F_(intelligent)) MAKE_PSTR_LIST(enum_flow, F_(off), F_(flow), F_(bufferedflow), F_(buffer), F_(layeredbuffer)) MAKE_PSTR_LIST(enum_reset, F_(maintenance), F_(error)) +MAKE_PSTR_LIST(enum_bool, F_(off), F_(on)) // thermostat MAKE_PSTR_WORD(light) MAKE_PSTR_WORD(medium) MAKE_PSTR_WORD(heavy) MAKE_PSTR_WORD(own_prog) -MAKE_PSTR(blank, "") MAKE_PSTR_WORD(start) MAKE_PSTR_WORD(heat) MAKE_PSTR_WORD(hold) @@ -309,25 +312,24 @@ MAKE_PSTR_LIST(enum_ibaMainDisplay, F_(smoke_temperature)) MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian)) MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end)) -MAKE_PSTR_LIST(enum_ibaBuildingType, F_(blank), F_(light), F_(medium), F_(heavy)) // RC300 +MAKE_PSTR_LIST(enum_ibaBuildingType, F_(light), F_(medium), F_(heavy)) // RC300 MAKE_PSTR_LIST(enum_wwMode, F_(off), F_(low), F_(high), F_(auto), F_(own_prog)) MAKE_PSTR_LIST(enum_wwCircMode, F_(off), F_(on), F_(auto), F_(own_prog)) -MAKE_PSTR_LIST(enum_ibaBuildingType2, F_(light), F_(medium), F_(heavy)) // RC30, RC35 MAKE_PSTR_LIST(enum_wwMode2, F_(off), F_(on), F_(auto)) MAKE_PSTR_LIST(enum_wwCircMode2, F_(off), F_(on), F_(auto)) MAKE_PSTR_LIST(enum_heatingtype, F_(off), F_(radiator), F_(convector), F_(floor)) MAKE_PSTR_LIST(enum_summermode, F_(summer), F_(auto), F_(winter)) -MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto)) // RC100, RC300, RC310 -MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto)) // RC20 -MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto)) // RC35, RC30 -MAKE_PSTR_LIST(enum_mode4, F_(blank), F_(manual), F_(auto), F_(holiday)) // JUNKERS -MAKE_PSTR_LIST(enum_mode5, F_(auto), F_(off)) // CRF +MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto)) // RC100, RC300, RC310 +MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto)) // RC20 +MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto)) // RC35, RC30 +MAKE_PSTR_LIST(enum_mode4, F_(nofrost), F_(eco), F_(heat), F_(auto)) // JUNKERS +MAKE_PSTR_LIST(enum_mode5, F_(auto), F_(off)) // CRF MAKE_PSTR_LIST(enum_modetype, F_(eco), F_(comfort)) MAKE_PSTR_LIST(enum_modetype2, F_(day)) MAKE_PSTR_LIST(enum_modetype3, F_(night), F_(day)) -MAKE_PSTR_LIST(enum_modetype4, F_(blank), F_(nofrost), F_(eco), F_(heat)) +MAKE_PSTR_LIST(enum_modetype4, F_(nofrost), F_(eco), F_(heat)) MAKE_PSTR_LIST(enum_modetype5, F_(off), F_(on)) MAKE_PSTR_LIST(enum_reducemode, F_(nofrost), F_(reduce), F_(room), F_(outdoor)) @@ -341,7 +343,7 @@ MAKE_PSTR_LIST(enum_hamode, F_(off), F_(heat), F_(auto), F_(heat), F_(off), F_(h // solar list MAKE_PSTR_LIST(enum_solarmode, F_(constant), F("pwm"), F("analog")) -MAKE_PSTR_LIST(enum_collectortype, F_(blank), F("flat"), F("vacuum")) +MAKE_PSTR_LIST(enum_collectortype, F("flat"), F("vacuum")) // MQTT topics and full text for values and commands MAKE_PSTR(homeassistant, "homeassistant/") @@ -465,10 +467,13 @@ MAKE_PSTR_LIST(tankMiddleTemp, F("tankmiddletemp"), F("ww tank middle temperatur MAKE_PSTR_LIST(wWStarts, F("wwstarts"), F("ww # starts")) MAKE_PSTR_LIST(wWStarts2, F("wwstarts2"), F("ww # control starts")) MAKE_PSTR_LIST(wWWorkM, F("wwworkm"), F("ww active time")) +MAKE_PSTR_LIST(wWHystOn, F("wwhyston"), F("ww hysteresis on temperature")) +MAKE_PSTR_LIST(wWHystOff, F("wwhystoff"), F("ww hysteresis off temperature")) // thermostat +// extra commands +MAKE_PSTR_LIST(switchtime, F("switchtime"), F("single program switchtime")) // extra commands, with no long name so they don't show up in WebUI -MAKE_PSTR_LIST(switchtime, F("switchtime")) MAKE_PSTR_LIST(temp, F("temp")) MAKE_PSTR_LIST(hatemp, F("hatemp")) MAKE_PSTR_LIST(hamode, F("hamode")) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index df8687fb5..61aeb1969 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -35,8 +35,6 @@ uint32_t Mqtt::publish_time_mixer_; uint32_t Mqtt::publish_time_sensor_; uint32_t Mqtt::publish_time_other_; bool Mqtt::mqtt_enabled_; -uint8_t Mqtt::dallas_format_; -uint8_t Mqtt::bool_format_; uint8_t Mqtt::ha_climate_format_; bool Mqtt::ha_enabled_; uint8_t Mqtt::nested_format_; @@ -56,11 +54,7 @@ uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON}; // subscribe to an MQTT topic, and store the associated callback function // only if it already hasn't been added -void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb) { - if (!enabled()) { - return; - } - +void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_sub_function_p cb) { // check if we already have the topic subscribed, if so don't add it again if (!mqtt_subfunctions_.empty()) { for (auto & mqtt_subfunction : mqtt_subfunctions_) { @@ -74,22 +68,22 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_ } } - LOG_DEBUG(F("Subscribing MQTT topic %s for device type %s"), topic.c_str(), EMSdevice::device_type_2_device_name(device_type).c_str()); - - // add to MQTT queue as a subscribe operation - auto message = queue_subscribe_message(topic); - - if (message == nullptr) { - return; - } - // register in our libary with the callback function. // We store the original topic without base mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(cb)); + + if (!enabled()) { + return; + } + + LOG_DEBUG(F("Subscribing MQTT topic %s for device type %s"), topic.c_str(), EMSdevice::device_type_2_device_name(device_type).c_str()); + + // add to MQTT queue as a subscribe operation + queue_subscribe_message(topic); } // subscribe to the command topic if it doesn't exist yet -void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flag) { +void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flags) { std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type); // thermostat, boiler, etc... // see if we have already a handler for the device type (boiler, thermostat). If not add it @@ -107,10 +101,14 @@ void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_2_device_name(device_type).c_str()); } + if (!enabled()) { + return; + } + // register the individual commands too (e.g. ems-esp/boiler/wwonetime) // https://github.com/emsesp/EMS-ESP32/issues/31 - std::string topic(MQTT_TOPIC_MAX_SIZE, '\0'); - if (subscribe_format_ == 2 && flag == MqttSubFlag::FLAG_HC) { + if (subscribe_format_ == Subscribe_Format::INDIVIDUAL_ALL_HC && ((flags & CommandFlag::MQTT_SUB_FLAG_HC) == CommandFlag::MQTT_SUB_FLAG_HC)) { + std::string topic(MQTT_TOPIC_MAX_SIZE, '\0'); topic = cmd_topic + "/hc1/" + uuid::read_flash_string(cmd); queue_subscribe_message(topic); topic = cmd_topic + "/hc2/" + uuid::read_flash_string(cmd); @@ -119,7 +117,8 @@ void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper queue_subscribe_message(topic); topic = cmd_topic + "/hc4/" + uuid::read_flash_string(cmd); queue_subscribe_message(topic); - } else if (subscribe_format_ && flag != MqttSubFlag::FLAG_NOSUB) { + } else if (subscribe_format_ != Subscribe_Format::GENERAL && ((flags & CommandFlag::MQTT_SUB_FLAG_NOSUB) == CommandFlag::MQTT_SUB_FLAG_NOSUB)) { + std::string topic(MQTT_TOPIC_MAX_SIZE, '\0'); topic = cmd_topic + "/" + uuid::read_flash_string(cmd); queue_subscribe_message(topic); } @@ -127,7 +126,7 @@ void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper // 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_subfunction_p cb) { +void Mqtt::subscribe(const std::string & topic, mqtt_sub_function_p cb) { subscribe(0, topic, cb); // no device_id needed if generic to EMS-ESP } @@ -142,7 +141,7 @@ void Mqtt::resubscribe() { } for (const auto & cf : Command::commands()) { std::string topic(MQTT_TOPIC_MAX_SIZE, '\0'); - if (subscribe_format_ == 2 && cf.flag_ == MqttSubFlag::FLAG_HC) { + if (subscribe_format_ == Subscribe_Format::INDIVIDUAL_ALL_HC && cf.has_flags(CommandFlag::MQTT_SUB_FLAG_HC)) { topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc1/" + uuid::read_flash_string(cf.cmd_); queue_subscribe_message(topic); topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc2/" + uuid::read_flash_string(cf.cmd_); @@ -151,7 +150,7 @@ void Mqtt::resubscribe() { queue_subscribe_message(topic); topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc4/" + uuid::read_flash_string(cf.cmd_); queue_subscribe_message(topic); - } else if (subscribe_format_ && cf.flag_ != MqttSubFlag::FLAG_NOSUB) { + } else if (subscribe_format_ != Subscribe_Format::GENERAL && !cf.has_flags(CommandFlag::MQTT_SUB_FLAG_NOSUB)) { topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/" + uuid::read_flash_string(cf.cmd_); queue_subscribe_message(topic); } @@ -227,7 +226,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) { shell.printfln(F(" %s/%s"), mqtt_base_.c_str(), mqtt_subfunction.topic_.c_str()); } for (const auto & cf : Command::commands()) { - if (subscribe_format_ == 2 && cf.flag_ == MqttSubFlag::FLAG_HC) { + if (subscribe_format_ == 2 && cf.has_flags(CommandFlag::MQTT_SUB_FLAG_HC)) { shell.printfln(F(" %s/%s/hc1/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), @@ -244,7 +243,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) { mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str()); - } else if (subscribe_format_ && cf.flag_ != MqttSubFlag::FLAG_NOSUB) { + } else if (subscribe_format_ == 1 && !cf.has_flags(CommandFlag::MQTT_SUB_FLAG_NOSUB)) { shell.printfln(F(" %s/%s/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), @@ -348,8 +347,13 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len) } cmd_only++; // skip the / // LOG_INFO(F("devicetype= %d, topic = %s, cmd = %s, message = %s), mf.device_type_, topic, cmd_only, message); - if (!Command::call(mf.device_type_, cmd_only, message)) { - LOG_ERROR(F("No matching cmd (%s) in topic %s, or invalid data"), cmd_only, topic); + // call command, assume admin authentication is allowed + uint8_t cmd_return = Command::call(mf.device_type_, cmd_only, message, true); + if (cmd_return == CommandRet::NOT_FOUND) { + LOG_ERROR(F("No matching cmd (%s) in topic %s"), cmd_only, topic); + Mqtt::publish(F_(response), "unknown"); + } else if (cmd_return != CommandRet::OK) { + LOG_ERROR(F("Invalid data with cmd (%s) in topic %s"), cmd_only, topic); Mqtt::publish(F_(response), "unknown"); } return; @@ -378,29 +382,32 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len) n = doc["id"]; } - bool cmd_known = false; - JsonVariant data = doc["data"]; + uint8_t cmd_return = CommandRet::OK; + JsonVariant data = doc["data"]; if (data.is()) { - cmd_known = Command::call(mf.device_type_, command, data.as(), n); + cmd_return = Command::call(mf.device_type_, command, data.as(), true, n); } else if (data.is()) { char data_str[10]; - cmd_known = Command::call(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as()), n); + cmd_return = Command::call(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as()), true, n); } else if (data.is()) { char data_str[10]; - cmd_known = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as(), 2), n); + cmd_return = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as(), 2), true, n); } else if (data.isNull()) { DynamicJsonDocument resp(EMSESP_JSON_SIZE_XLARGE_DYN); JsonObject json = resp.to(); - cmd_known = Command::call(mf.device_type_, command, "", n, json); - if (cmd_known && json.size()) { + cmd_return = Command::call(mf.device_type_, command, "", true, n, json); + if (json.size()) { Mqtt::publish(F_(response), resp.as()); return; } } - if (!cmd_known) { - LOG_ERROR(F("No matching cmd (%s) or invalid data"), command); + if (cmd_return == CommandRet::NOT_FOUND) { + LOG_ERROR(F("No matching cmd (%s)"), command); + Mqtt::publish(F_(response), "unknown"); + } else if (cmd_return != CommandRet::OK) { + LOG_ERROR(F("Invalid data for cmd (%s)"), command); Mqtt::publish(F_(response), "unknown"); } @@ -484,8 +491,6 @@ void Mqtt::load_settings() { mqtt_enabled_ = mqttSettings.enabled; ha_enabled_ = mqttSettings.ha_enabled; ha_climate_format_ = mqttSettings.ha_climate_format; - dallas_format_ = mqttSettings.dallas_format; - bool_format_ = mqttSettings.bool_format; nested_format_ = mqttSettings.nested_format; subscribe_format_ = mqttSettings.subscribe_format; @@ -639,8 +644,14 @@ void Mqtt::on_connect() { #ifndef EMSESP_STANDALONE if (EMSESP::system_.ethernet_connected()) { doc["ip"] = ETH.localIP().toString(); + if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") { + doc["ipv6"] = ETH.localIPv6().toString(); + } } else { doc["ip"] = WiFi.localIP().toString(); + if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") { + doc["ipv6"] = WiFi.localIPv6().toString(); + } } #endif publish(F_(info), doc.as()); // topic called "info" @@ -654,12 +665,9 @@ void Mqtt::on_connect() { EMSESP::shower_.send_mqtt_stat(false); // Send shower_activated as false EMSESP::system_.send_heartbeat(); // send heatbeat - if (connectcount_ > 1) { - // we doing a re-connect from a TCP break - // only re-subscribe again to all MQTT topics - resubscribe(); - EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any - } + // re-subscribe to all MQTT topics + resubscribe(); + EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on @@ -887,7 +895,7 @@ void Mqtt::process_queue() { // else try and publish it uint16_t packet_id = mqttClient_->publish(topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_); - LOG_DEBUG(F("Publishing topic %s (#%02d, retain=%d, try#%d, size %d, pid %d)"), + LOG_DEBUG(F("Publishing topic %s (#%02d, retain=%d, retry=%d, size=%d, pid=%d)"), topic, mqtt_message.id_, message->retain, diff --git a/src/mqtt.h b/src/mqtt.h index bea1e2f2a..609e6d545 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -44,12 +44,10 @@ using uuid::console::Shell; // size of queue #define MAX_MQTT_MESSAGES 200 -enum { BOOL_FORMAT_ONOFF = 1, BOOL_FORMAT_TRUEFALSE, BOOL_FORMAT_10, BOOL_FORMAT_ONOFF_CAP }; // matches Web UI settings - namespace emsesp { -using mqtt_subfunction_p = std::function; -using cmdfunction_p = std::function; +using mqtt_sub_function_p = std::function; +using cmdfunction_p = std::function; struct MqttMessage { const uint8_t operation; @@ -83,15 +81,27 @@ class Mqtt { enum Operation { PUBLISH, SUBSCRIBE }; - enum Dallas_Format : uint8_t { SENSORID = 1, NUMBER }; - enum HA_Climate_Format : uint8_t { CURRENT = 1, SETPOINT, ZERO }; + enum HA_Climate_Format : uint8_t { + CURRENT = 1, // 1 + SETPOINT, // 2 + ZERO // 3 + + }; + + // subscribe_format + enum Subscribe_Format : uint8_t { + GENERAL = 0, // 0 + INDIVIDUAL_MAIN_HC, // 1 + INDIVIDUAL_ALL_HC // 2 + + }; static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength static void on_connect(); - static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb); - static void subscribe(const std::string & topic, mqtt_subfunction_p cb); + static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_sub_function_p cb); + static void subscribe(const std::string & topic, mqtt_sub_function_p cb); static void resubscribe(); static void publish(const std::string & topic, const std::string & payload); @@ -112,7 +122,7 @@ class Mqtt { const uint8_t device_type, const __FlashStringHelper * entity, const uint8_t uom = 0); - static void register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t tag = 0); + static void register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flags = 0); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_mqtt(uuid::console::Shell & shell); @@ -159,14 +169,6 @@ class Mqtt { return ha_climate_format_; } - static uint8_t dallas_format() { - return dallas_format_; - } - - static uint8_t bool_format() { - return bool_format_; - } - static uint8_t nested_format() { return nested_format_; } @@ -183,10 +185,6 @@ class Mqtt { ha_climate_format_ = ha_climate_format; } - static void dallas_format(uint8_t dallas_format) { - dallas_format_ = dallas_format; - } - static void ha_enabled(bool ha_enabled) { ha_enabled_ = ha_enabled; } @@ -241,11 +239,11 @@ class Mqtt { // function handlers for MQTT subscriptions struct MQTTSubFunction { - uint8_t device_type_; // which device type, from DeviceType:: - const std::string topic_; // short topic name - mqtt_subfunction_p mqtt_subfunction_; // can be empty + uint8_t device_type_; // which device type, from DeviceType:: + const std::string topic_; // short topic name + mqtt_sub_function_p mqtt_subfunction_; // can be empty - MQTTSubFunction(uint8_t device_type, const std::string && topic, mqtt_subfunction_p mqtt_subfunction) + MQTTSubFunction(uint8_t device_type, const std::string && topic, mqtt_sub_function_p mqtt_subfunction) : device_type_(device_type) , topic_(topic) , mqtt_subfunction_(mqtt_subfunction) { @@ -279,8 +277,6 @@ class Mqtt { static uint32_t publish_time_other_; static uint32_t publish_time_sensor_; static bool mqtt_enabled_; - static uint8_t dallas_format_; - static uint8_t bool_format_; static uint8_t ha_climate_format_; static bool ha_enabled_; static uint8_t nested_format_; diff --git a/src/shower.cpp b/src/shower.cpp index 6e0871e95..22322328b 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -118,8 +118,11 @@ void Shower::send_mqtt_stat(bool state, bool force) { doc["uniq_id"] = FJSON("shower_active"); doc["~"] = Mqtt::base(); // default ems-esp doc["stat_t"] = FJSON("~/shower_active"); - JsonObject dev = doc.createNestedObject("dev"); - JsonArray ids = dev.createNestedArray("ids"); + char result[10]; + doc[F("payload_on")] = Helpers::render_boolean(result, true); + doc[F("payload_off")] = Helpers::render_boolean(result, false); + JsonObject dev = doc.createNestedObject("dev"); + JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp"); char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; @@ -132,7 +135,7 @@ void Shower::send_mqtt_stat(bool state, bool force) { void Shower::shower_alert_stop() { if (doing_cold_shot_) { LOG_DEBUG(F("Shower Alert stopped")); - Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "true"); + (void)Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "true", true); // no need to check authentication doing_cold_shot_ = false; } } @@ -140,7 +143,7 @@ void Shower::shower_alert_stop() { void Shower::shower_alert_start() { if (shower_alert_) { LOG_DEBUG(F("Shower Alert started")); - Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false"); + (void)Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false", true); // no need to check authentication doing_cold_shot_ = true; alert_timer_start_ = uuid::get_uptime(); // timer starts now } @@ -151,19 +154,9 @@ void Shower::shower_alert_start() { void Shower::publish_values() { StaticJsonDocument doc; - if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF) { - doc["shower_timer"] = shower_timer_ ? "on" : "off"; - doc["shower_alert"] = shower_alert_ ? "on" : "off"; - } else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF_CAP) { - doc["shower_timer"] = shower_timer_ ? "ON" : "OFF"; - doc["shower_alert"] = shower_alert_ ? "ON" : "OFF"; - } else if (Mqtt::bool_format() == BOOL_FORMAT_TRUEFALSE) { - doc["shower_timer"] = shower_timer_; - doc["shower_alert"] = shower_alert_; - } else { - doc["shower_timer"] = shower_timer_ ? 1 : 0; - doc["shower_alert"] = shower_alert_ ? 1 : 0; - } + char result[10]; + doc["shower_timer"] = Helpers::render_boolean(result, shower_timer_); + doc["shower_alert"] = Helpers::render_boolean(result, shower_alert_); // only publish shower duration if there is a value if (duration_ > SHOWER_MIN_DURATION) { diff --git a/src/system.cpp b/src/system.cpp index 5368f1cae..bd03d2790 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -159,45 +159,37 @@ void System::format(uuid::console::Shell & shell) { } void System::syslog_start() { + bool was_enabled = syslog_enabled_; + EMSESP::webSettingsService.read([&](WebSettings & settings) { + syslog_enabled_ = settings.syslog_enabled; + syslog_level_ = settings.syslog_level; + syslog_mark_interval_ = settings.syslog_mark_interval; + syslog_host_ = settings.syslog_host; + syslog_port_ = settings.syslog_port; + }); +#ifndef EMSESP_STANDALONE if (syslog_enabled_) { -#ifndef EMSESP_STANDALONE - syslog_.start(); + // start & configure syslog + if (!was_enabled) { + syslog_.start(); + EMSESP::logger().info(F("Starting Syslog")); + } syslog_.log_level((uuid::log::Level)syslog_level_); -#endif - EMSESP::logger().info(F("Starting Syslog")); - } -} - -void System::syslog_init(bool refresh) { - if (refresh) { - get_settings(); - } - -#ifndef EMSESP_STANDALONE - // check for empty or invalid hostname - IPAddress addr; - if (!addr.fromString(syslog_host_.c_str())) { - syslog_enabled_ = false; - } - - // in case service is still running, this flushes the queue - // https://github.com/emsesp/EMS-ESP/issues/496 - if (!syslog_enabled_) { + syslog_.mark_interval(syslog_mark_interval_); + syslog_.destination(syslog_host_.c_str(), syslog_port_); + syslog_.hostname(hostname().c_str()); + } else if (was_enabled) { + // in case service is still running, this flushes the queue + // https://github.com/emsesp/EMS-ESP/issues/496 + EMSESP::logger().info(F("Stopping Syslog")); syslog_.log_level((uuid::log::Level)-1); syslog_.mark_interval(0); - syslog_.destination((IPAddress)((uint32_t)0)); - return; + syslog_.destination(""); } - - // start & configure syslog - syslog_.log_level((uuid::log::Level)syslog_level_); - syslog_.mark_interval(syslog_mark_interval_); - syslog_.destination(addr, syslog_port_); - syslog_.hostname(hostname().c_str()); #endif } -// read all the settings from the config files and store locally +// read all the settings except syslog from the config files and store locally void System::get_settings() { EMSESP::webSettingsService.read([&](WebSettings & settings) { // Button @@ -206,12 +198,8 @@ void System::get_settings() { // ADC analog_enabled_ = settings.analog_enabled; - // Syslog - syslog_enabled_ = settings.syslog_enabled; - syslog_level_ = settings.syslog_level; - syslog_mark_interval_ = settings.syslog_mark_interval; - syslog_host_ = settings.syslog_host; - syslog_port_ = settings.syslog_port; + // Sysclock + low_clock_ = settings.low_clock; // LED hide_led_ = settings.hide_led; @@ -240,12 +228,12 @@ void System::wifi_tweak() { // WIFI_POWER_2dBm = 8,// 2dBm // WIFI_POWER_MINUS_1dBm = -4// -1dBm wifi_power_t p1 = WiFi.getTxPower(); - (void)WiFi.setTxPower(WIFI_POWER_19_5dBm); + (void)WiFi.setTxPower(WIFI_POWER_17dBm); wifi_power_t p2 = WiFi.getTxPower(); bool s1 = WiFi.getSleep(); WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE bool s2 = WiFi.getSleep(); - LOG_DEBUG(F("Adjusting WiFi - Tx power %d->%d, Sleep %d->%d"), p1, p2, s1, s2); + LOG_DEBUG(F("[DEBUG] Adjusting WiFi - Tx power %d->%d, Sleep %d->%d"), p1, p2, s1, s2); #endif } @@ -275,6 +263,14 @@ void System::start(uint32_t heap_start) { // load in all the settings first get_settings(); +#ifndef EMSESP_STANDALONE + // disable bluetooth module + periph_module_disable(PERIPH_BT_MODULE); + if (low_clock_) { + setCpuFrequencyMhz(160); + } +#endif + EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { hostname(networkSettings.hostname.c_str()); // sets the hostname LOG_INFO(F("System name: %s"), hostname().c_str()); @@ -285,7 +281,7 @@ void System::start(uint32_t heap_start) { adc_init(false); // analog ADC button_init(false); // the special button network_init(false); // network - syslog_init(false); // init SysLog + syslog_start(); // start Syslog EMSESP::init_uart(); // start UART } @@ -296,12 +292,8 @@ void System::adc_init(bool refresh) { get_settings(); } #ifndef EMSESP_STANDALONE - // setCpuFrequencyMhz(160); // default is 240 - - // disable bluetooth & ADC + // disable ADC /* - btStop(); - esp_bt_controller_disable(); if (!analog_enabled_) { adc_power_release(); // turn off ADC to save power if not needed } @@ -415,11 +407,13 @@ void System::loop() { #ifndef EMSESP_STANDALONE #if defined(EMSESP_DEBUG) +/* static uint32_t last_memcheck_ = 0; if (currentMillis - last_memcheck_ > 10000) { // 10 seconds last_memcheck_ = currentMillis; show_mem("core"); } + */ #endif #endif @@ -501,10 +495,10 @@ void System::measure_analog() { if (!measure_last_ || (uint32_t)(uuid::get_uptime() - measure_last_) >= SYSTEM_MEASURE_ANALOG_INTERVAL) { measure_last_ = uuid::get_uptime(); -#if defined(ESP32) - uint16_t a = analogRead(36); +#if defined(EMSESP_STANDALONE) + uint16_t a = 0; #else - uint16_t a = 0; // standalone + uint16_t a = analogReadMilliVolts(ADC1_CHANNEL_0_GPIO_NUM); #endif static uint32_t sum_ = 0; @@ -615,13 +609,14 @@ void System::system_check() { // these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""} // no individual subscribe for pin command because id is needed void System::commands_init() { - Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin, F("set GPIO"), MqttSubFlag::FLAG_NOSUB); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, F("send a telegram")); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, F("force a MQTT publish")); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, F("refresh all EMS values")); - Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, F("system status")); - Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("list system settings")); - Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, F("list system commands")); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin, F("set GPIO"), CommandFlag::MQTT_SUB_FLAG_NOSUB | CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, F("send a telegram"), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, F("force a MQTT publish"), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, F("refresh all EMS values"), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, F("restarts EMS-ESP"), CommandFlag::ADMIN_ONLY); + Command::add_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, F("system status")); + Command::add_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("list system settings")); + Command::add_json(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, F("list system commands")); #if defined(EMSESP_DEBUG) Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test, F("run tests")); #endif @@ -667,7 +662,7 @@ void System::show_users(uuid::console::Shell & shell) { #ifndef EMSESP_STANDALONE EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) { - for (User user : securitySettings.users) { + for (const 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")); } }); @@ -708,6 +703,9 @@ void System::show_system(uuid::console::Shell & shell) { 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()); + if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") { + shell.printfln(F("IPv6 address: %s"), uuid::printable_to_string(WiFi.localIPv6()).c_str()); + } break; case WL_CONNECT_FAILED: @@ -738,6 +736,9 @@ void System::show_system(uuid::console::Shell & shell) { shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str()); shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(ETH.gatewayIP()).c_str()); shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(ETH.dnsIP()).c_str()); + if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") { + shell.printfln(F("IPv6 address: %s"), uuid::printable_to_string(ETH.localIPv6()).c_str()); + } } else { shell.printfln(F("Ethernet: disconnected")); } @@ -746,15 +747,17 @@ void System::show_system(uuid::console::Shell & shell) { if (!syslog_enabled_) { shell.printfln(F("Syslog: disabled")); } else { - shell.printfln(F("Syslog:")); + shell.printfln(F("Syslog: %s"), syslog_.started() ? "started" : "stopped"); shell.print(F(" ")); shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : uuid::read_flash_string(F_(unset)).c_str()); + shell.printfln(F(" IP: %s"), uuid::printable_to_string(syslog_.ip()).c_str()); shell.print(F(" ")); shell.printfln(F_(port_fmt), syslog_port_); shell.print(F(" ")); shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast(syslog_level_))); shell.print(F(" ")); shell.printfln(F_(mark_interval_fmt), syslog_mark_interval_); + shell.printfln(F(" Queued: %d"), syslog_.queued()); } #endif @@ -772,7 +775,7 @@ bool System::command_commands(const char * value, const int8_t id, JsonObject & } // export all settings to JSON text -// e.g. http://ems-esp/api?device=system&cmd=settings +// http://ems-esp/api/system/settings // value and id are ignored bool System::command_settings(const char * value, const int8_t id, JsonObject & json) { JsonObject node; @@ -780,11 +783,12 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject & node = json.createNestedObject("System"); node["version"] = EMSESP_APP_VERSION; + // hide ssid from this list EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { - node = json.createNestedObject("WIFI"); - // node["ssid"] = settings.ssid; // commented out - people don't like others to see this + node = json.createNestedObject("Network"); node["hostname"] = settings.hostname; node["static_ip_config"] = settings.staticIPConfig; + node["enableIPv6"] = settings.enableIPv6; JsonUtils::writeIP(node, "local_ip", settings.localIP); JsonUtils::writeIP(node, "gateway_ip", settings.gatewayIP); JsonUtils::writeIP(node, "subnet_mask", settings.subnetMask); @@ -820,12 +824,11 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject & node["publish_time_mixer"] = settings.publish_time_mixer; node["publish_time_other"] = settings.publish_time_other; node["publish_time_sensor"] = settings.publish_time_sensor; - node["dallas_format"] = settings.dallas_format; - node["bool_format"] = settings.bool_format; node["ha_climate_format"] = settings.ha_climate_format; node["ha_enabled"] = settings.ha_enabled; node["mqtt_qos"] = settings.mqtt_qos; node["mqtt_retain"] = settings.mqtt_retain; + node["subscribe_format"] = settings.subscribe_format; }); #ifndef EMSESP_STANDALONE @@ -864,6 +867,9 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject & node["led_gpio"] = settings.led_gpio; node["hide_led"] = settings.hide_led; node["notoken_api"] = settings.notoken_api; + node["dallas_format"] = settings.dallas_format; + node["bool_format"] = settings.bool_format; + node["enum_format"] = settings.enum_format; node["analog_enabled"] = settings.analog_enabled; node["pbutton_gpio"] = settings.pbutton_gpio; node["board_profile"] = settings.board_profile; @@ -872,15 +878,17 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject & return true; } -// export status information including some basic settings -// e.g. http://ems-esp/api?device=system&cmd=info +// export status information including the device information +// http://ems-esp/api/system/info bool System::command_info(const char * value, const int8_t id, JsonObject & json) { JsonObject node; node = json.createNestedObject("System"); - node["version"] = EMSESP_APP_VERSION; - node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); + node["version"] = EMSESP_APP_VERSION; + node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); + node["uptime_sec"] = uuid::get_uptime_sec(); + #ifndef EMSESP_STANDALONE node["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes #endif @@ -901,31 +909,37 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json } if (EMSESP::bus_status() != EMSESP::BUS_STATUS_OFFLINE) { - node["bus protocol"] = EMSbus::is_ht3() ? F("HT3") : F("Buderus"); - node["#telegrams received"] = EMSESP::rxservice_.telegram_count(); - node["#read requests sent"] = EMSESP::txservice_.telegram_read_count(); - node["#write requests sent"] = EMSESP::txservice_.telegram_write_count(); - node["#incomplete telegrams"] = EMSESP::rxservice_.telegram_error_count(); - node["#tx fails"] = EMSESP::txservice_.telegram_fail_count(); - node["rx line quality"] = EMSESP::rxservice_.quality(); - node["tx line quality"] = EMSESP::txservice_.quality(); + node["bus protocol"] = EMSbus::is_ht3() ? F("HT3") : F("Buderus"); + node["telegrams received"] = EMSESP::rxservice_.telegram_count(); + node["read requests sent"] = EMSESP::txservice_.telegram_read_count(); + node["write requests sent"] = EMSESP::txservice_.telegram_write_count(); + node["incomplete telegrams"] = EMSESP::rxservice_.telegram_error_count(); + node["tx fails"] = EMSESP::txservice_.telegram_fail_count(); + node["rx line quality"] = EMSESP::rxservice_.quality(); + node["tx line quality"] = EMSESP::txservice_.quality(); if (Mqtt::enabled()) { - node["#MQTT publishes"] = Mqtt::publish_count(); - node["#MQTT publish fails"] = Mqtt::publish_fails(); + node["MQTT publishes"] = Mqtt::publish_count(); + node["MQTT publish fails"] = Mqtt::publish_fails(); } if (EMSESP::dallas_enabled()) { - node["#dallas sensors"] = EMSESP::sensor_devices().size(); - node["#dallas reads"] = EMSESP::sensor_reads(); - node["#dallas fails"] = EMSESP::sensor_fails(); + node["dallas sensors"] = EMSESP::sensor_devices().size(); + node["dallas reads"] = EMSESP::sensor_reads(); + node["dallas fails"] = EMSESP::sensor_fails(); } +#ifndef EMSESP_STANDALONE + if (EMSESP::system_.syslog_enabled_) { + node["syslog_ip"] = syslog_.ip(); + node["syslog_started"] = syslog_.started(); + } +#endif } - JsonArray devices2 = json.createNestedArray("Devices"); - + // show EMS devices + JsonArray devices = json.createNestedArray("Devices"); for (const auto & device_class : EMSFactory::device_handlers()) { for (const auto & emsdevice : EMSESP::emsdevices) { if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { - JsonObject obj = devices2.createNestedObject(); + JsonObject obj = devices.createNestedObject(); obj["type"] = emsdevice->device_type_name(); obj["name"] = emsdevice->to_string(); char result[200]; @@ -933,11 +947,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json } } } - if (EMSESP::sensor_devices().size()) { - JsonObject obj = devices2.createNestedObject(); - obj["type"] = F_(Dallassensor); - obj["name"] = F_(Dallassensor); - } return true; } @@ -978,4 +987,12 @@ bool System::load_board_profile(std::vector & data, const std::string & return true; } +// restart command - perform a hard reset +bool System::command_restart(const char * value, const int8_t id) { +#ifndef EMSESP_STANDALONE + ESP.restart(); +#endif + return true; +} + } // namespace emsesp diff --git a/src/system.h b/src/system.h index 721f24cd5..bed600cca 100644 --- a/src/system.h +++ b/src/system.h @@ -52,13 +52,15 @@ class System { static bool command_send(const char * value, const int8_t id); static bool command_publish(const char * value, const int8_t id); static bool command_fetch(const char * value, const int8_t id); - static bool command_info(const char * value, const int8_t id, JsonObject & json); - static bool command_settings(const char * value, const int8_t id, JsonObject & json); - static bool command_commands(const char * value, const int8_t id, JsonObject & json); + static bool command_restart(const char * value, const int8_t id); #if defined(EMSESP_DEBUG) static bool command_test(const char * value, const int8_t id); #endif + static bool command_info(const char * value, const int8_t id, JsonObject & json); + static bool command_settings(const char * value, const int8_t id, JsonObject & json); + static bool command_commands(const char * value, const int8_t id, JsonObject & json); + void restart(); void format(uuid::console::Shell & shell); void upload_status(bool in_progress); @@ -72,7 +74,6 @@ class System { void send_heartbeat(); void led_init(bool refresh); - void syslog_init(bool refresh); void adc_init(bool refresh); void network_init(bool refresh); void button_init(bool refresh); @@ -81,6 +82,14 @@ class System { static bool is_valid_gpio(uint8_t pin); static bool load_board_profile(std::vector & data, const std::string & board_profile); + bool analog_enabled() { + return analog_enabled_; + } + + uint16_t analog() { + return analog_; + } + std::string hostname() { return hostname_; } @@ -97,9 +106,13 @@ class System { ethernet_connected_ = b; } + void network_connected(bool b) { + network_connected_ = b; + } + bool network_connected() { #ifndef EMSESP_STANDALONE - return (ethernet_connected_ || WiFi.isConnected()); + return network_connected_; #else return true; #endif @@ -147,14 +160,16 @@ class System { uint32_t last_system_check_ = 0; bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload bool ethernet_connected_ = false; + bool network_connected_ = false; uint16_t analog_; // settings std::string hostname_ = "ems-esp"; bool hide_led_; uint8_t led_gpio_; - bool syslog_enabled_; + bool syslog_enabled_ = false; bool analog_enabled_; + bool low_clock_; String board_profile_; uint8_t pbutton_gpio_; int8_t syslog_level_; diff --git a/src/telegram.cpp b/src/telegram.cpp index 1d5893b3a..5792cc4ba 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -126,14 +126,6 @@ std::string Telegram::to_string_message() const { // checks if we have an Rx telegram that needs processing void RxService::loop() { - /* - while (!rx_telegrams_.empty()) { - auto telegram = rx_telegrams_.pop().telegram_; - (void)EMSESP::process_telegram(telegram); // further process the telegram - increment_telegram_count(); // increase rx count - } - */ - while (!rx_telegrams_.empty()) { auto telegram = rx_telegrams_.front().telegram_; (void)EMSESP::process_telegram(telegram); // further process the telegram @@ -154,8 +146,12 @@ void RxService::add(uint8_t * data, uint8_t length) { // validate the CRC. if it fails then increment the number of corrupt/incomplete telegrams and only report to console/syslog uint8_t crc = calculate_crc(data, length - 1); if (data[length - 1] != crc) { - telegram_error_count_++; - LOG_ERROR(F("Rx: %s (CRC %02X != %02X)"), Helpers::data_to_hex(data, length).c_str(), data[length - 1], crc); + if ((data[0] & 0x7F) != ems_bus_id()) { // do not count echos as errors + telegram_error_count_++; + LOG_WARNING(F("Incomplete Rx: %s"), Helpers::data_to_hex(data, length - 1).c_str()); // exclude CRC + } else { + LOG_TRACE(F("Incomplete Rx: %s"), Helpers::data_to_hex(data, length - 1).c_str()); // exclude CRC + } return; } @@ -200,6 +196,7 @@ void RxService::add(uint8_t * data, uint8_t length) { } // if we're watching and "raw" print out actual telegram as bytes to the console + // including the CRC at the end if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) { uint16_t trace_watch_id = EMSESP::watch_id(); if ((trace_watch_id == WATCH_ID_NONE) || (type_id == trace_watch_id) @@ -363,7 +360,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) { LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"), (telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"), tx_telegram.id_, - Helpers::data_to_hex(telegram_raw, length).c_str()); + Helpers::data_to_hex(telegram_raw, length - 1).c_str()); // exclude the last CRC byte set_post_send_query(tx_telegram.validateid_); // send the telegram to the UART Tx @@ -427,6 +424,9 @@ void TxService::add(const uint8_t operation, } else { tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false, validateid); // add to back of queue } + if (validateid != 0) { + EMSESP::wait_validate(validateid); + } } // builds a Tx telegram and adds to queue @@ -503,6 +503,9 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt // tx_telegrams_.push_back(qtxt); // add to back of queue tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false, validate_id); // add to back of queue } + if (validate_id != 0) { + EMSESP::wait_validate(validate_id); + } } // send a Tx telegram to request data from an EMS device @@ -566,11 +569,12 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui if (++retry_count_ > MAXIMUM_TX_RETRIES) { reset_retry_count(); // give up increment_telegram_fail_count(); // another Tx fail + EMSESP::wait_validate(0); // do not wait for validation LOG_ERROR(F("Last Tx %s operation failed after %d retries. Ignoring request: %s"), (operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"), - MAXIMUM_TX_RETRIES), - telegram_last_->to_string().c_str(); + MAXIMUM_TX_RETRIES, + telegram_last_->to_string().c_str()); return; } @@ -579,7 +583,7 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui (operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"), retry_count_, telegram_last_->to_string().c_str(), - Helpers::data_to_hex(data, length).c_str()); + Helpers::data_to_hex(data, length - 1).c_str()); #endif // add to the top of the queue diff --git a/src/telegram.h b/src/telegram.h index df9401c87..d49f95a4b 100644 --- a/src/telegram.h +++ b/src/telegram.h @@ -92,10 +92,7 @@ class Telegram { } uint8_t val = value; value = (uint8_t)(((this->message_data[abs_index]) >> (bit)) & 0x01); - if (val != value) { - return true; - } - return false; + return (val != value); } // read a value from a telegram. We always store the value, regardless if its garbage @@ -116,10 +113,16 @@ class Telegram { for (uint8_t i = 0; i < num_bytes; i++) { value = (value << 8) + this->message_data[index - this->offset + i]; // shift by byte } - if (val != value) { - return true; + return (val != value); + } + + bool read_enumvalue(uint8_t & value, const uint8_t index, uint8_t start = 0) const { + if ((index < this->offset) || ((index - this->offset) >= this->message_length)) { + return false; } - return false; + uint8_t val = value; + value = this->message_data[index - this->offset] - start; + return (val != value); } private: diff --git a/src/test/test.cpp b/src/test/test.cpp index c2d35469d..019a56ef9 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -194,7 +194,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { // init stuff Mqtt::ha_enabled(true); - Mqtt::dallas_format(1); + EMSESP::dallassensor_.dallas_format(1); Mqtt::ha_climate_format(1); EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw @@ -352,7 +352,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (emsdevice) { doc.clear(); JsonObject json = doc.to(); - Command::call(emsdevice->device_type(), "info", nullptr, -1, json); + Command::call(emsdevice->device_type(), "info", nullptr, true, -1, json); Serial.print(COLOR_YELLOW); if (json.size() != 0) { @@ -424,7 +424,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { run_test("boiler"); // device type, command, data - Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false"); + Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false", true); } if (command == "fr120") { @@ -934,17 +934,41 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { shell.printfln(F("Testing RESTful API...")); Mqtt::ha_enabled(false); run_test("general"); - DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN); - JsonObject json = doc.to(); AsyncWebServerRequest request; + + // GET request.method(HTTP_GET); request.url("/api/thermostat/seltemp"); EMSESP::webAPIService.webAPIService_get(&request); request.url("/api/boiler/syspress"); EMSESP::webAPIService.webAPIService_get(&request); + + request.url("/api/system/commands"); + EMSESP::webAPIService.webAPIService_get(&request); + + request.url("/api/boiler/info"); + EMSESP::webAPIService.webAPIService_get(&request); + + // POST + request.method(HTTP_POST); + request.url("/api/system/commands"); + EMSESP::webAPIService.webAPIService_get(&request); + #endif } + + if (command == "crash") { + shell.printfln(F("Forcing a crash...")); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdiv-by-zero" +#pragma GCC diagnostic ignored "-Wunused-variable" + uint8_t a = 2 / 0; + shell.printfln(F("Testing %s"), a); + +#pragma GCC diagnostic pop + } } // simulates a telegram in the Rx queue, but without the CRC which is added automatically diff --git a/src/test/test.h b/src/test/test.h index 3707bbfa3..87e0c6bf0 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -39,6 +39,7 @@ namespace emsesp { // #define EMSESP_DEBUG_DEFAULT "shower_alert" // #define EMSESP_DEBUG_DEFAULT "310" #define EMSESP_DEBUG_DEFAULT "api" +// #define EMSESP_DEBUG_DEFAULT "crash" class Test { public: diff --git a/src/version.h b/src/version.h index 396c50343..a0c9fd620 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.1.1" +#define EMSESP_APP_VERSION "3.2.0" diff --git a/src/web/WebAPIService.cpp b/src/web/WebAPIService.cpp index 2cd541b3f..c2452c167 100644 --- a/src/web/WebAPIService.cpp +++ b/src/web/WebAPIService.cpp @@ -26,7 +26,7 @@ namespace emsesp { WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager) : _securityManager(securityManager) - , _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS + , _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS, must use 'Content-Type: application/json' in header server->on("/api", HTTP_GET, std::bind(&WebAPIService::webAPIService_get, this, _1)); // for GETS server->addHandler(&_apiHandler); } @@ -47,7 +47,7 @@ void WebAPIService::webAPIService_get(AsyncWebServerRequest * request) { // HTTP_POST | HTTP_PUT | HTTP_PATCH // POST/PUT /{device}[/{hc}][/{name}] void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json) { - // extra the params from the json body + // if no body then treat it as a secure GET if (not json.is()) { webAPIService_get(request); return; @@ -126,7 +126,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, std::string & device_ id_n = Helpers::atoint(request->getParam("hc")->value().c_str()); } } else { - // parse paths and json data + // parse paths and json data from the OpenAPI standard // /{device}[/{hc}][/{name}] // first param must be a valid device, which includes "system" device_s = p.paths().front(); @@ -158,35 +158,46 @@ void WebAPIService::parse(AsyncWebServerRequest * request, std::string & device_ // check that we have permissions first. We require authenticating on 1 or more of these conditions: // 1. any HTTP POSTs or PUTs - // 2. a HTTP GET which has a 'data' parameter which is not empty (to keep v2 compatibility) - auto method = request->method(); - bool have_data = !value_s.empty(); - bool admin_allowed; + // 2. an HTTP GET which has a 'data' parameter which is not empty (to keep v2 compatibility) + auto method = request->method(); + bool have_data = !value_s.empty(); + bool authenticated = false; EMSESP::webSettingsService.read([&](WebSettings & settings) { Authentication authentication = _securityManager->authenticateRequest(request); - admin_allowed = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication); + authenticated = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication); }); if ((method != HTTP_GET) || ((method == HTTP_GET) && have_data)) { - if (!admin_allowed) { + if (!authenticated) { send_message_response(request, 401, "Bad credentials"); // Unauthorized return; } } - // now we have all the parameters go and execute the command PrettyAsyncJsonResponse * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN); JsonObject json = response->getRoot(); - bool ok = Command::call(device_type, cmd_s.c_str(), (have_data ? value_s.c_str() : nullptr), id_n, json); + // now we have all the parameters go and execute the command + // the function will also determine if authentication is needed to execute its command + uint8_t cmd_reply = Command::call(device_type, cmd_s.c_str(), (have_data ? value_s.c_str() : nullptr), authenticated, id_n, json); // check for errors - if (!ok) { + if (cmd_reply == CommandRet::NOT_FOUND) { + delete response; + send_message_response(request, 400, "Command not found"); // Bad Request + return; + } else if (cmd_reply == CommandRet::NOT_ALLOWED) { + delete response; + send_message_response(request, 401, "Bad credentials"); // Unauthorized + return; + } else if (cmd_reply != CommandRet::OK) { + delete response; send_message_response(request, 400, "Problems parsing elements"); // Bad Request return; } if (!json.size()) { + delete response; send_message_response(request, 200, "OK"); // OK return; } diff --git a/src/web/WebDevicesService.cpp b/src/web/WebDataService.cpp similarity index 58% rename from src/web/WebDevicesService.cpp rename to src/web/WebDataService.cpp index f5ed5afab..631bc8984 100644 --- a/src/web/WebDevicesService.cpp +++ b/src/web/WebDataService.cpp @@ -22,17 +22,19 @@ namespace emsesp { using namespace std::placeholders; // for `_1` etc -WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager) +WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securityManager) : _device_dataHandler(DEVICE_DATA_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) + securityManager->wrapCallback(std::bind(&WebDataService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) , _writevalue_dataHandler(WRITE_VALUE_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebDevicesService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { - server->on(EMSESP_DEVICES_SERVICE_PATH, + securityManager->wrapCallback(std::bind(&WebDataService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) + , _writesensor_dataHandler(WRITE_SENSOR_SERVICE_PATH, + securityManager->wrapCallback(std::bind(&WebDataService::write_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { + server->on(EMSESP_DATA_SERVICE_PATH, HTTP_GET, - securityManager->wrapRequest(std::bind(&WebDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + securityManager->wrapRequest(std::bind(&WebDataService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); server->on(SCAN_DEVICES_SERVICE_PATH, HTTP_GET, - securityManager->wrapRequest(std::bind(&WebDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + securityManager->wrapRequest(std::bind(&WebDataService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); _device_dataHandler.setMethod(HTTP_POST); _device_dataHandler.setMaxContentLength(256); @@ -41,14 +43,18 @@ WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * _writevalue_dataHandler.setMethod(HTTP_POST); _writevalue_dataHandler.setMaxContentLength(256); server->addHandler(&_writevalue_dataHandler); + + _writesensor_dataHandler.setMethod(HTTP_POST); + _writesensor_dataHandler.setMaxContentLength(256); + server->addHandler(&_writesensor_dataHandler); } -void WebDevicesService::scan_devices(AsyncWebServerRequest * request) { +void WebDataService::scan_devices(AsyncWebServerRequest * request) { EMSESP::scan_devices(); request->send(200); } -void WebDevicesService::all_devices(AsyncWebServerRequest * request) { +void WebDataService::all_devices(AsyncWebServerRequest * request) { AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN); JsonObject root = response->getRoot(); @@ -69,27 +75,36 @@ void WebDevicesService::all_devices(AsyncWebServerRequest * request) { JsonArray sensors = root.createNestedArray("sensors"); if (EMSESP::have_sensors()) { uint8_t i = 1; - char s[8]; for (const auto & sensor : EMSESP::sensor_devices()) { JsonObject obj = sensors.createNestedObject(); obj["no"] = i++; - obj["id"] = sensor.to_string(); - obj["temp"] = Helpers::render_value(s, sensor.temperature_c, 10); + obj["id"] = sensor.to_string(true); + obj["temp"] = (float)(sensor.temperature_c) / 10; + obj["offset"] = (float)(sensor.offset()) / 10; } } + if (EMSESP::system_.analog_enabled()) { + root["analog"] = EMSESP::system_.analog(); + } + response->setLength(); request->send(response); } // The unique_id is the unique record ID from the Web table to identify which device to load // Compresses the JSON using MsgPack https://msgpack.org/index.html -void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) { +void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant & json) { if (json.is()) { MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN); for (const auto & emsdevice : EMSESP::emsdevices) { if (emsdevice) { if (emsdevice->unique_id() == json["id"]) { + // wait max 2.5 sec for updated data (post_send_delay is 2 sec) + for (uint16_t i = 0; i < 2500 && EMSESP::wait_validate(); i++) { + delay(1); + } + EMSESP::wait_validate(0); // reset in case of timeout #ifndef EMSESP_STANDALONE JsonObject root = response->getRoot(); emsdevice->generate_values_json_web(root); @@ -108,7 +123,8 @@ void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant } // takes a command and its data value from a specific Device, from the Web -void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant & json) { +// assumes the service has been checked for admin authentication +void WebDataService::write_value(AsyncWebServerRequest * request, JsonVariant & json) { if (json.is()) { JsonObject dv = json["devicevalue"]; uint8_t id = json["id"]; @@ -119,22 +135,22 @@ void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant if (emsdevice->unique_id() == id) { const char * cmd = dv["c"]; uint8_t device_type = emsdevice->device_type(); - bool ok = false; + uint8_t cmd_return = CommandRet::OK; char s[10]; // the data could be in any format, but we need string JsonVariant data = dv["v"]; if (data.is()) { - ok = Command::call(device_type, cmd, data.as()); + cmd_return = Command::call(device_type, cmd, data.as(), true); } else if (data.is()) { - ok = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 0)); + cmd_return = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 0), true); } else if (data.is()) { - ok = Command::call(device_type, cmd, Helpers::render_value(s, (float)data.as(), 1)); + cmd_return = Command::call(device_type, cmd, Helpers::render_value(s, (float)data.as(), 1), true); } else if (data.is()) { - ok = Command::call(device_type, cmd, data.as() ? "true" : "false"); + cmd_return = Command::call(device_type, cmd, data.as() ? "true" : "false", true); } // send "Write command sent to device" or "Write command failed" - AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204); + AsyncWebServerResponse * response = request->beginResponse((cmd_return == CommandRet::OK) ? 200 : 204); request->send(response); return; } @@ -146,4 +162,27 @@ void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant request->send(response); } +// takes a sensorname and optional offset from the Web +void WebDataService::write_sensor(AsyncWebServerRequest * request, JsonVariant & json) { + bool ok = false; + if (json.is()) { + JsonObject sensor = json["sensor"]; + + // if valid add. + uint8_t no = sensor["no"]; + if (no > 0 && no < 100) { + char name[20]; + std::string id = sensor["id"]; + strlcpy(name, id.c_str(), sizeof(name)); + float offset = sensor["offset"]; // this will be a float value. We'll convert it to int and * 10 it + int16_t offset10 = offset * 10; + char idstr[3]; + ok = EMSESP::dallassensor_.update(Helpers::itoa(idstr, no, 10), name, offset10); + } + } + + AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204); + request->send(response); +} + } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebDevicesService.h b/src/web/WebDataService.h similarity index 79% rename from src/web/WebDevicesService.h rename to src/web/WebDataService.h index a5bbe87e7..adb114662 100644 --- a/src/web/WebDevicesService.h +++ b/src/web/WebDataService.h @@ -16,24 +16,25 @@ * along with this program. If not, see . */ -#ifndef WebDevicesService_h -#define WebDevicesService_h +#ifndef WebDataService_h +#define WebDataService_h #include #include #include #include -#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices" +#define EMSESP_DATA_SERVICE_PATH "/rest/data" #define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices" #define DEVICE_DATA_SERVICE_PATH "/rest/deviceData" #define WRITE_VALUE_SERVICE_PATH "/rest/writeValue" +#define WRITE_SENSOR_SERVICE_PATH "/rest/writeSensor" namespace emsesp { -class WebDevicesService { +class WebDataService { public: - WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager); + WebDataService(AsyncWebServer * server, SecurityManager * securityManager); private: // GET @@ -43,8 +44,9 @@ class WebDevicesService { // POST void device_data(AsyncWebServerRequest * request, JsonVariant & json); void write_value(AsyncWebServerRequest * request, JsonVariant & json); + void write_sensor(AsyncWebServerRequest * request, JsonVariant & json); - AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler; + AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler, _writesensor_dataHandler; }; } // namespace emsesp diff --git a/src/web/WebLogService.cpp b/src/web/WebLogService.cpp index 05675c5c4..d489b27e4 100644 --- a/src/web/WebLogService.cpp +++ b/src/web/WebLogService.cpp @@ -23,30 +23,28 @@ using namespace std::placeholders; namespace emsesp { WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager) - : _events(EVENT_SOURCE_LOG_PATH) - , _setLevel(LOG_SETTINGS_PATH, std::bind(&WebLogService::setLevel, this, _1, _2), 256) { // for POSTS + : events_(EVENT_SOURCE_LOG_PATH) + , setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { // for POSTS - _events.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); - server->addHandler(&_events); + events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); + server->addHandler(&events_); server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1)); // for bring back the whole log server->on(FETCH_LOG_PATH, HTTP_GET, std::bind(&WebLogService::fetchLog, this, _1)); // get when page is loaded - server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getLevel, this, _1)); + server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1)); // for setting a level - server->addHandler(&_setLevel); - - // start event source service - start(); + server->addHandler(&setValues_); } void WebLogService::forbidden(AsyncWebServerRequest * request) { request->send(403); } +// start event source service void WebLogService::start() { uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // default is INFO } @@ -79,60 +77,96 @@ void WebLogService::operator<<(std::shared_ptr message) { if (log_messages_.size() >= maximum_log_messages_) { log_messages_.pop_front(); } + log_messages_.emplace_back(log_message_id_++, std::move(message)); + + EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) { + if (!settings.enabled || (time(nullptr) < 1500000000L)) { + time_offset_ = 0; + } else if (!time_offset_) { + time_offset_ = time(nullptr) - uuid::get_uptime_sec(); + } + }); } void WebLogService::loop() { - if (!_events.count() || log_messages_.empty()) { + if (!events_.count() || log_messages_.empty()) { return; } // put a small delay in const uint64_t now = uuid::get_uptime_ms(); - if (now < last_transmit_ || now - last_transmit_ < 100) { + if (now < last_transmit_ || now - last_transmit_ < REFRESH_SYNC) { return; } // see if we've advanced - if (log_messages_.back().id_ > log_message_id_tail_) { - transmit(log_messages_.back()); - log_message_id_tail_ = log_messages_.back().id_; - last_transmit_ = uuid::get_uptime_ms(); + if (log_messages_.back().id_ <= log_message_id_tail_) { + return; } + + // flush + for (auto it = log_messages_.begin(); it != log_messages_.end(); it++) { + if (it->id_ > log_message_id_tail_) { + transmit(*it); + } + } + + log_message_id_tail_ = log_messages_.back().id_; + last_transmit_ = uuid::get_uptime_ms(); +} + +// convert time to real offset +char * WebLogService::messagetime(char * out, const uint64_t t) { + if (!time_offset_) { + strcpy(out, uuid::log::format_timestamp_ms(t, 3).c_str()); + } else { + time_t t1 = time_offset_ + t / 1000ULL; + strftime(out, 25, "%F %T", localtime(&t1)); + snprintf_P(out, 25, "%s.%03d", out, (uint16_t)(t % 1000)); + } + return out; } // send to web eventsource void WebLogService::transmit(const QueuedLogMessage & message) { DynamicJsonDocument jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_SMALL); JsonObject logEvent = jsonDocument.to(); - logEvent["t"] = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3); - logEvent["l"] = message.content_->level; - logEvent["n"] = message.content_->name; - logEvent["m"] = message.content_->text; + char time_string[25]; + + logEvent["t"] = messagetime(time_string, message.content_->uptime_ms); + logEvent["l"] = message.content_->level; + logEvent["i"] = message.id_; + logEvent["n"] = message.content_->name; + logEvent["m"] = message.content_->text; size_t len = measureJson(jsonDocument); char * buffer = new char[len + 1]; if (buffer) { serializeJson(jsonDocument, buffer, len + 1); - _events.send(buffer, "message", millis()); + events_.send(buffer, "message", millis()); } delete[] buffer; } -// send the current log buffer to the API +// send the complete log buffer to the API, filtering on log level void WebLogService::fetchLog(AsyncWebServerRequest * request) { MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN); // 8kb buffer JsonObject root = response->getRoot(); JsonArray log = root.createNestedArray("events"); - for (const auto & msg : log_messages_) { - JsonObject logEvent = log.createNestedObject(); - auto message = std::move(msg); - logEvent["t"] = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3); - logEvent["l"] = message.content_->level; - logEvent["n"] = message.content_->name; - logEvent["m"] = message.content_->text; + for (const auto & message : log_messages_) { + if (message.content_->level <= log_level()) { + JsonObject logEvent = log.createNestedObject(); + char time_string[25]; + + logEvent["t"] = messagetime(time_string, message.content_->uptime_ms); + logEvent["l"] = message.content_->level; + logEvent["i"] = message.id_; + logEvent["n"] = message.content_->name; + logEvent["m"] = message.content_->text; + } } log_message_id_tail_ = log_messages_.back().id_; @@ -141,25 +175,25 @@ void WebLogService::fetchLog(AsyncWebServerRequest * request) { request->send(response); } -// sets the level after a POST -void WebLogService::setLevel(AsyncWebServerRequest * request, JsonVariant & json) { +// sets the values like level after a POST +void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & json) { if (not json.is()) { return; } - auto && body = json.as(); + + auto && body = json.as(); + uuid::log::Level level = body["level"]; log_level(level); - // send the value back - AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); - JsonObject root = response->getRoot(); - root["level"] = log_level(); - response->setLength(); - request->send(response); + uint8_t max_messages = body["max_messages"]; + maximum_log_messages(max_messages); + + request->send(200); // OK } -// return the current log level after a GET -void WebLogService::getLevel(AsyncWebServerRequest * request) { +// return the current value settings after a GET +void WebLogService::getValues(AsyncWebServerRequest * request) { AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); JsonObject root = response->getRoot(); root["level"] = log_level(); diff --git a/src/web/WebLogService.h b/src/web/WebLogService.h index 12bf8afe6..885739e9d 100644 --- a/src/web/WebLogService.h +++ b/src/web/WebLogService.h @@ -34,7 +34,8 @@ namespace emsesp { class WebLogService : public uuid::log::Handler { public: - static constexpr size_t MAX_LOG_MESSAGES = 30; + static constexpr size_t MAX_LOG_MESSAGES = 50; + static constexpr size_t REFRESH_SYNC = 200; WebLogService(AsyncWebServer * server, SecurityManager * securityManager); @@ -48,7 +49,7 @@ class WebLogService : public uuid::log::Handler { virtual void operator<<(std::shared_ptr message); private: - AsyncEventSource _events; + AsyncEventSource events_; class QueuedLogMessage { public: @@ -63,16 +64,19 @@ class WebLogService : public uuid::log::Handler { void forbidden(AsyncWebServerRequest * request); void transmit(const QueuedLogMessage & message); void fetchLog(AsyncWebServerRequest * request); - void getLevel(AsyncWebServerRequest * request); + void getValues(AsyncWebServerRequest * request); - void setLevel(AsyncWebServerRequest * request, JsonVariant & json); - AsyncCallbackJsonWebHandler _setLevel; // for POSTs + char * messagetime(char * out, const uint64_t t); + + void setValues(AsyncWebServerRequest * request, JsonVariant & json); + AsyncCallbackJsonWebHandler setValues_; // for POSTs uint64_t last_transmit_ = 0; // Last transmit time size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output unsigned long log_message_id_ = 0; // The next identifier to use for queued log messages unsigned long log_message_id_tail_ = 0; // last event shown on the screen after fetch std::list log_messages_; // Queued log messages, in the order they were received + time_t time_offset_ = 0; }; } // namespace emsesp diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index c5b9a37c9..f0d8061d9 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -20,7 +20,7 @@ namespace emsesp { -uint8_t WebSettings::flags_; +uint8_t WebSettings::flags_ = 0; using namespace std::placeholders; // for `_1` etc @@ -55,18 +55,34 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) { root["dallas_parasite"] = settings.dallas_parasite; root["led_gpio"] = settings.led_gpio; root["hide_led"] = settings.hide_led; + root["low_clock"] = settings.low_clock; root["notoken_api"] = settings.notoken_api; root["analog_enabled"] = settings.analog_enabled; root["pbutton_gpio"] = settings.pbutton_gpio; root["solar_maxflow"] = settings.solar_maxflow; root["board_profile"] = settings.board_profile; + root["dallas_format"] = settings.dallas_format; + root["bool_format"] = settings.bool_format; + root["enum_format"] = settings.enum_format; + + for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { + char buf[20]; + snprintf_P(buf, sizeof(buf), PSTR("sensor_id%d"), i); + root[buf] = settings.sensor[i].id; + snprintf_P(buf, sizeof(buf), PSTR("sensor_name%d"), i); + root[buf] = settings.sensor[i].name; + snprintf_P(buf, sizeof(buf), PSTR("sensor_offset%d"), i); + root[buf] = settings.sensor[i].offset; + } } // call on initialization and also when settings are updated via web or console StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) { // load default GPIO configuration based on board profile std::vector data; // led, dallas, rx, tx, button - settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE; + + String old_board_profile = settings.board_profile; + settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE; if (!System::load_board_profile(data, settings.board_profile.c_str())) { settings.board_profile = EMSESP_DEFAULT_BOARD_PROFILE; // invalid board configuration, override the default in case it has been misspelled } @@ -77,13 +93,15 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) uint8_t default_tx_gpio = data[3]; uint8_t default_pbutton_gpio = data[4]; - EMSESP::logger().info(F("EMS-ESP version %s"), EMSESP_APP_VERSION); + if (old_board_profile != settings.board_profile) { + EMSESP::logger().info(F("EMS-ESP version %s"), EMSESP_APP_VERSION); - // check to see if we have a settings file, if not it's a fresh install - if (!root.size()) { - EMSESP::logger().info(F("Initializing configuration with board profile %s"), settings.board_profile.c_str()); - } else { - EMSESP::logger().info(F("Using configuration from board profile %s"), settings.board_profile.c_str()); + // check to see if we have a settings file, if not it's a fresh install + if (!root.size()) { + EMSESP::logger().info(F("Initializing configuration with board profile %s"), settings.board_profile.c_str()); + } else { + EMSESP::logger().info(F("Using configuration from board profile %s"), settings.board_profile.c_str()); + } } int prev; @@ -116,19 +134,19 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL; check_flag(prev, settings.syslog_mark_interval, ChangeFlags::SYSLOG); +#ifndef EMSESP_STANDALONE String old_syslog_host = settings.syslog_host; settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST; - if (old_syslog_host.equals(settings.syslog_host.c_str())) { + if (!old_syslog_host.equals(settings.syslog_host)) { add_flags(ChangeFlags::SYSLOG); } +#endif prev = settings.syslog_port; settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT; check_flag(prev, settings.syslog_port, ChangeFlags::SYSLOG); - prev = settings.trace_raw; settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW; - check_flag(prev, settings.trace_raw, ChangeFlags::SYSLOG); EMSESP::trace_raw(settings.trace_raw); // adc @@ -165,14 +183,35 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED; check_flag(prev, settings.hide_led, ChangeFlags::LED); - // these both need reboots to be applied + // these need reboots to be applied settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID; settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT; + settings.low_clock = root["low_clock"] | false; + ; // doesn't need any follow-up actions settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API; settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW; + settings.dallas_format = root["dallas_format"] | EMSESP_DEFAULT_DALLAS_FORMAT; + EMSESP::dallassensor_.dallas_format(settings.dallas_format); + + settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT; + EMSESP::bool_format(settings.bool_format); + + settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT; + EMSESP::enum_format(settings.enum_format); + + for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { + char buf[20]; + snprintf_P(buf, sizeof(buf), PSTR("sensor_id%d"), i); + settings.sensor[i].id = root[buf] | EMSESP_DEFAULT_SENSOR_NAME; + snprintf_P(buf, sizeof(buf), PSTR("sensor_name%d"), i); + settings.sensor[i].name = root[buf] | EMSESP_DEFAULT_SENSOR_NAME; + snprintf_P(buf, sizeof(buf), PSTR("sensor_offset%d"), i); + settings.sensor[i].offset = root[buf] | 0; + } + return StateUpdateResult::CHANGED; } @@ -192,8 +231,7 @@ void WebSettingsService::onUpdate() { } if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) { - EMSESP::system_.syslog_init(true); // reload settings - EMSESP::system_.syslog_start(); // re-start (or stop) + EMSESP::system_.syslog_start(); // re-start (or stop) } if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) { @@ -207,6 +245,8 @@ void WebSettingsService::onUpdate() { if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) { EMSESP::system_.led_init(true); // reload settings } + + WebSettings::reset_flags(); } void WebSettingsService::begin() { @@ -233,6 +273,7 @@ void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVari root["tx_gpio"] = data[3]; root["pbutton_gpio"] = data[4]; } else { + delete response; AsyncWebServerResponse * response = request->beginResponse(200); request->send(response); return; diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index dccad6961..2f8730f3f 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -28,8 +28,13 @@ #define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings" #define EMSESP_BOARD_PROFILE_SERVICE_PATH "/rest/boardProfile" +#define NUM_SENSOR_NAMES 10 + namespace emsesp { +enum { BOOL_FORMAT_ONOFF = 1, BOOL_FORMAT_ONOFF_CAP, BOOL_FORMAT_TRUEFALSE, BOOL_FORMAT_10 }; // matches Web UI settings +enum { ENUM_FORMAT_TEXT = 1, ENUM_FORMAT_NUMBER }; // matches Web UI settings + class WebSettings { public: uint8_t tx_mode; @@ -50,11 +55,21 @@ class WebSettings { bool dallas_parasite; uint8_t led_gpio; bool hide_led; + bool low_clock; bool notoken_api; bool analog_enabled; uint8_t pbutton_gpio; uint8_t solar_maxflow; String board_profile; + uint8_t dallas_format; + uint8_t bool_format; + uint8_t enum_format; + + struct { + String id; + String name; + int16_t offset; + } sensor[NUM_SENSOR_NAMES]; static void read(WebSettings & settings, JsonObject & root); static StateUpdateResult update(JsonObject & root, WebSettings & settings); diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index c7e972582..6d12a0223 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -35,15 +35,20 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { switch (event) { case SYSTEM_EVENT_STA_DISCONNECTED: EMSESP::logger().info(F("WiFi Disconnected. Reason code=%d"), info.disconnected.reason); + EMSESP::system_.network_connected(false); break; case SYSTEM_EVENT_STA_GOT_IP: #ifndef EMSESP_STANDALONE EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname()); #endif - EMSESP::system_.wifi_tweak(); - EMSESP::system_.send_heartbeat(); - EMSESP::system_.syslog_start(); + EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { + if (!networkSettings.enableIPv6) { + EMSESP::system_.network_connected(true); + EMSESP::system_.send_heartbeat(); + EMSESP::system_.syslog_start(); + } + }); break; case SYSTEM_EVENT_ETH_START: @@ -57,8 +62,13 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { #ifndef EMSESP_STANDALONE EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIP().toString().c_str(), ETH.linkSpeed()); #endif - EMSESP::system_.send_heartbeat(); - EMSESP::system_.syslog_start(); + EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { + if (!networkSettings.enableIPv6) { + EMSESP::system_.network_connected(true); + EMSESP::system_.send_heartbeat(); + EMSESP::system_.syslog_start(); + } + }); EMSESP::system_.ethernet_connected(true); } break; @@ -66,13 +76,44 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { case SYSTEM_EVENT_ETH_DISCONNECTED: EMSESP::logger().info(F("Ethernet Disconnected")); EMSESP::system_.ethernet_connected(false); + EMSESP::system_.network_connected(false); break; case SYSTEM_EVENT_ETH_STOP: EMSESP::logger().info(F("Ethernet Stopped")); EMSESP::system_.ethernet_connected(false); + EMSESP::system_.network_connected(false); break; +#ifndef EMSESP_STANDALONE + case SYSTEM_EVENT_STA_CONNECTED: + EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { + if (networkSettings.enableIPv6) { + WiFi.enableIpV6(); + } + }); + break; + + case SYSTEM_EVENT_ETH_CONNECTED: + EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { + if (networkSettings.enableIPv6) { + ETH.enableIpV6(); + } + }); + break; + + case SYSTEM_EVENT_GOT_IP6: + if (EMSESP::system_.ethernet_connected()) { + EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIPv6().toString().c_str(), ETH.linkSpeed()); + } else { + EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIPv6().toString().c_str(), WiFi.getHostname()); + } + EMSESP::system_.network_connected(true); + EMSESP::system_.send_heartbeat(); + EMSESP::system_.syslog_start(); + break; +#endif + default: break; }