mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-08 00:39:50 +03:00
Merge branch 'v2' of https://github.com/proddy/EMS-ESP into v2
This commit is contained in:
67
README.md
67
README.md
@@ -2,7 +2,9 @@
|
||||
|
||||
## **Breaking changes**
|
||||
|
||||
- MQTT base has been removed. The hostname is only used.
|
||||
- MQTT base has been removed. The hostname is only used
|
||||
- refresh command renamed to fetch
|
||||
- have to 'wifi reconnect' after changing wifi in console
|
||||
|
||||
## **New Features in v2**
|
||||
|
||||
@@ -65,7 +67,7 @@ common commands available in all contexts:
|
||||
|
||||
(from the root)
|
||||
set
|
||||
refresh
|
||||
fetch
|
||||
system (enters a context)
|
||||
boiler (enters a context)
|
||||
thermostat (enters a context)
|
||||
@@ -82,11 +84,13 @@ system
|
||||
set
|
||||
show
|
||||
show mqtt
|
||||
show users
|
||||
passwd
|
||||
restart
|
||||
set wifi hostname <name>
|
||||
set wifi password
|
||||
set wifi ssid <name>
|
||||
wifi reconnect
|
||||
|
||||
boiler
|
||||
comfort <hot |eco | intelligent>
|
||||
@@ -96,6 +100,8 @@ boiler
|
||||
wwonetime <on | off>
|
||||
wwtemp <degrees>
|
||||
read <type ID>
|
||||
maxpower <%>
|
||||
minpower <%>
|
||||
|
||||
thermostat
|
||||
set
|
||||
@@ -106,6 +112,61 @@ thermostat
|
||||
```
|
||||
|
||||
----------
|
||||
### **mqtt commands**
|
||||
|
||||
commands can be written as `{"cmd": ,"data": }` or direct.
|
||||
To set thermostat commands depending on heatingcircuits add `"hc": ` or nest the command. Without `"hc":` the first ative heatingcircuit is set.
|
||||
These commands are equivalent:
|
||||
`{"hc":2,"cmd":"daytemp","data":21}` or `{"hc":2,"daytemp":21}`or `{"hc2"{"daytemp":21}}`
|
||||
In direct commands it's possible to combine commands, i.e. `{"hc1":{"daytemp":21,"nighttemp":17},"hc2":{"daytemp":20}}`
|
||||
```
|
||||
*boiler_cmd*
|
||||
comfort <hot, eco, intelligent>
|
||||
flowtemp <degrees>
|
||||
wwtemp <degrees>
|
||||
boilhyston <degrees> (negative value)
|
||||
boilhystoff <degrees> (positive value)
|
||||
burnperiod <minutes>
|
||||
burnminpower <%>
|
||||
burnmaxpower <%>
|
||||
pumpdelay <minutes>
|
||||
|
||||
*thermostat_cmd*
|
||||
--- without hc ---
|
||||
wwmode <off | on | auto>
|
||||
calinttemp <degrees>
|
||||
minexttemp <degrees>
|
||||
building <light | medium | heavy>
|
||||
language <n> (0=de, 1=nl, 2=fr, 3=it) only RC30
|
||||
display <n> (0=int temp, 1= int set, 2=ext. temp, 3=burner, 4=ww, 5=mode, 6=time, 7=date, 8=smoke) only RC30
|
||||
clockoffset <seconds> (only RC30)
|
||||
--- with hc ---
|
||||
mode <auto | night | day | nofrost | heat | eco>
|
||||
temp <degrees>
|
||||
nighttemp <degrees>
|
||||
daytemp <degrees>
|
||||
nofrosttemp <degrees>
|
||||
ecotemp <degrees>
|
||||
heattemp <degrees>
|
||||
summertemp <degrees>
|
||||
designtemp <degrees>
|
||||
offsettemp <degrees>
|
||||
holidaytemp <degrees>
|
||||
remotetemp <degrees>
|
||||
control <0 | 1 | 2>
|
||||
pause <hours>
|
||||
party <hours>
|
||||
holiday <dd.mm.yyyy-dd.mm.yyyy>
|
||||
date <NTP | hh:mm:ss-dd.mm.yyyy-dw-dst>
|
||||
|
||||
*cmd*
|
||||
send <"0B XX XX ..">
|
||||
D0 <0 | 1>
|
||||
D1 <0 | 1>
|
||||
D2 <0 | 1>
|
||||
D3 <0 | 1>
|
||||
|
||||
```
|
||||
|
||||
### **Basic Design Principles**
|
||||
|
||||
@@ -178,7 +239,7 @@ The Web is based off Rick's awesome [esp8266-react](https://github.com/rjwats/es
|
||||
* `MqttStatus.cpp` added root["mqtt_fails"]
|
||||
* `SecuritySettingsService.cpp` added version to the JWT payload
|
||||
* `SecuritySettingsService.h` #include "../../src/version.h"
|
||||
* `WiFiSettingsService.cpp` added WiFi.setOutputPower(20.0f) - removed
|
||||
* `WiFiSettingsService.cpp` added WiFi.setOutputPower(20.0f), moved setHostname
|
||||
* `OTASettingsService.h` added #include "../../src/system.h"
|
||||
* `OTASettingsService.cpp` added call to emsesp::System::upload_status(true)
|
||||
* `features.ini`: -D FT_NTP=0
|
||||
|
||||
@@ -36,12 +36,10 @@ build_flags =
|
||||
-D FACTORY_MQTT_PORT=1883
|
||||
-D FACTORY_MQTT_USERNAME=\"\"
|
||||
-D FACTORY_MQTT_PASSWORD=\"\"
|
||||
; if unspecified the devices hardware ID will be used
|
||||
-D FACTORY_MQTT_CLIENT_ID=\"ems-esp\"
|
||||
-D FACTORY_MQTT_KEEP_ALIVE=60
|
||||
-D FACTORY_MQTT_CLEAN_SESSION=false
|
||||
-D FACTORY_MQTT_MAX_TOPIC_LENGTH=128
|
||||
|
||||
; JWT Secret
|
||||
; if unspecified the devices hardware ID will be used
|
||||
-D FACTORY_JWT_SECRET=\"ems-esp-neo\"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[features]
|
||||
build_flags =
|
||||
-D FT_PROJECT=1
|
||||
-D FT_SECURITY=1
|
||||
-D FT_MQTT=1
|
||||
-D FT_NTP=0
|
||||
-D FT_OTA=1
|
||||
-D FT_UPLOAD_FIRMWARE=1
|
||||
@@ -2,9 +2,9 @@
|
||||
# Remember to also enable CORS in platformio.ini before uploading the code to the device.
|
||||
|
||||
# ESP32 dev
|
||||
#REACT_APP_HTTP_ROOT=http://10.10.10.194
|
||||
#REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.194
|
||||
REACT_APP_HTTP_ROOT=http://10.10.10.194
|
||||
REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.194
|
||||
|
||||
# ESP8266 dev
|
||||
REACT_APP_HTTP_ROOT=http://10.10.10.140
|
||||
REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.140
|
||||
#REACT_APP_HTTP_ROOT=http://10.10.10.140
|
||||
#REACT_APP_WEB_SOCKET_ROOT=ws://10.10.10.140
|
||||
@@ -22,7 +22,7 @@ class EMSESP extends Component<RouteComponentProps> {
|
||||
<MenuAppBar sectionTitle="Dashboard">
|
||||
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
|
||||
<Tab value={`/${PROJECT_PATH}/status`} label="EMS Status" />
|
||||
<Tab value={`/${PROJECT_PATH}/devices`} label="EMS Devices" />
|
||||
<Tab value={`/${PROJECT_PATH}/devices`} label="Devices & Sensors" />
|
||||
<Tab value={`/${PROJECT_PATH}/help`} label="EMS-ESP Help" />
|
||||
</Tabs>
|
||||
<Switch>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ENDPOINT_ROOT } from '../api';
|
||||
import EMSESPDevicesForm from './EMSESPDevicesForm';
|
||||
import { EMSESPDevices } from './EMSESPtypes';
|
||||
|
||||
export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + "emsespDevices";
|
||||
export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + "allDevices";
|
||||
|
||||
type EMSESPDevicesControllerProps = RestControllerProps<EMSESPDevices>;
|
||||
|
||||
@@ -17,7 +17,7 @@ class EMSESPDevicesController extends Component<EMSESPDevicesControllerProps> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SectionContent title="EMS Devices">
|
||||
<SectionContent title="Devices & Sensors">
|
||||
<RestFormLoader
|
||||
{...this.props}
|
||||
render={formProps => <EMSESPDevicesForm {...formProps} />}
|
||||
|
||||
@@ -24,10 +24,11 @@ import {
|
||||
FormButton,
|
||||
} from "../components";
|
||||
|
||||
import { EMSESPDevices, Device } from "./EMSESPtypes";
|
||||
import { EMSESPDevices, EMSESPDeviceData, Device } from "./EMSESPtypes";
|
||||
|
||||
import { ENDPOINT_ROOT } from '../api';
|
||||
export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices";
|
||||
export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + "deviceData";
|
||||
|
||||
const StyledTableCell = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -54,6 +55,7 @@ function compareDevices(a: Device, b: Device) {
|
||||
interface EMSESPDevicesFormState {
|
||||
confirmScanDevices: boolean;
|
||||
processing: boolean;
|
||||
deviceData?: EMSESPDeviceData;
|
||||
}
|
||||
|
||||
type EMSESPDevicesFormProps = RestFormProps<EMSESPDevices> & AuthenticatedContextProps & WithWidthProps;
|
||||
@@ -65,15 +67,26 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
|
||||
processing: false
|
||||
}
|
||||
|
||||
noData = () => {
|
||||
noDevices = () => {
|
||||
return (this.props.data.devices.length === 0);
|
||||
};
|
||||
|
||||
createTableItems() {
|
||||
noSensors = () => {
|
||||
return (this.props.data.sensors.length === 0);
|
||||
};
|
||||
|
||||
noDeviceData = () => {
|
||||
return (this.state.deviceData?.deviceData.length === 0);
|
||||
};
|
||||
|
||||
createDeviceItems() {
|
||||
const { width, data } = this.props;
|
||||
return (
|
||||
<TableContainer>
|
||||
{!this.noData() && (
|
||||
<Typography variant="h6" color="primary" paragraph>
|
||||
Devices:
|
||||
</Typography>
|
||||
{!this.noDevices() && (
|
||||
<Table size="small" padding={isWidthDown('xs', width!) ? "none" : "default"}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
@@ -87,7 +100,9 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.devices.sort(compareDevices).map(device => (
|
||||
<TableRow hover key={device.type}>
|
||||
<TableRow hover key={device.id}
|
||||
onClick={() => this.handleRowClick(device.id)}
|
||||
>
|
||||
<TableCell component="th" scope="row">
|
||||
{device.type}
|
||||
</TableCell>
|
||||
@@ -111,11 +126,54 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
{this.noData() &&
|
||||
{this.noDevices() &&
|
||||
(
|
||||
<Box bgcolor="error.main" color="error.contrastText" p={2} mt={2} mb={2}>
|
||||
<Typography variant="body1">
|
||||
No EMS devices found. Check the connection and for possible Tx errors and try scanning for new devices.
|
||||
No EMS devices found. Check the connections and for possible Tx errors.
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
createSensorItems() {
|
||||
const { data } = this.props;
|
||||
return (
|
||||
<TableContainer>
|
||||
<p></p>
|
||||
<Typography variant="h6" color="primary" paragraph>
|
||||
Sensors:
|
||||
</Typography>
|
||||
{!this.noSensors() && (
|
||||
<Table size="small" padding="default">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<StyledTableCell>ID</StyledTableCell>
|
||||
<StyledTableCell align="left">Temperature</StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.sensors.map(sensorData => (
|
||||
<TableRow key={sensorData.id}>
|
||||
<TableCell component="th" scope="row">
|
||||
{sensorData.id}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
{sensorData.temp}°C
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
{this.noSensors() &&
|
||||
(
|
||||
<Box color="warning.main" p={0} mt={0} mb={0}>
|
||||
<Typography variant="body1">
|
||||
No external temperature sensors detected.
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
@@ -156,26 +214,97 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
|
||||
|
||||
onScanDevicesConfirmed = () => {
|
||||
this.setState({ processing: true });
|
||||
redirectingAuthorizedFetch(SCANDEVICES_ENDPOINT, { method: 'POST' })
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
this.props.enqueueSnackbar("Device scan is starting...", { variant: 'info' });
|
||||
this.setState({ processing: false, confirmScanDevices: false });
|
||||
} else {
|
||||
throw Error("Invalid status code: " + response.status);
|
||||
}
|
||||
})
|
||||
redirectingAuthorizedFetch(SCANDEVICES_ENDPOINT).then(response => {
|
||||
if (response.status === 200) {
|
||||
this.props.enqueueSnackbar("Device scan is starting...", { variant: 'info' });
|
||||
this.setState({ processing: false, confirmScanDevices: false });
|
||||
} else {
|
||||
throw Error("Invalid status code: " + response.status);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.enqueueSnackbar(error.message || "Problem with scan", { variant: 'error' });
|
||||
this.setState({ processing: false, confirmScanDevices: false });
|
||||
});
|
||||
}
|
||||
|
||||
handleRowClick = (id: any) => {
|
||||
this.setState({ deviceData: undefined });
|
||||
redirectingAuthorizedFetch(DEVICE_DATA_ENDPOINT, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id: id }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
// this.setState({ errorMessage: undefined }, this.props.loadData);
|
||||
}
|
||||
throw Error("Unexpected response code: " + response.status);
|
||||
}).then(json => {
|
||||
this.setState({ deviceData: json });
|
||||
}).catch(error => {
|
||||
this.props.enqueueSnackbar(error.message || "Problem getting device data", { variant: 'error' });
|
||||
this.setState({ deviceData: undefined });
|
||||
});
|
||||
}
|
||||
|
||||
renderDeviceData() {
|
||||
const { deviceData } = this.state;
|
||||
const { width } = this.props;
|
||||
|
||||
if (this.noDevices()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deviceData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((deviceData.deviceData || []).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<p></p>
|
||||
<Box bgcolor="info.main" p={2} mt={1} mb={1}>
|
||||
<Typography variant="body1">
|
||||
{deviceData.deviceName}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<TableContainer>
|
||||
<Table size="small" padding={isWidthDown('xs', width!) ? "none" : "default"}>
|
||||
<TableHead>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{deviceData.deviceData.map(deviceData => (
|
||||
<TableRow key={deviceData.name}>
|
||||
<TableCell component="th" scope="row">
|
||||
{deviceData.name}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
{deviceData.value}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<br></br>
|
||||
{this.createTableItems()}
|
||||
{this.createDeviceItems()}
|
||||
{this.renderDeviceData()}
|
||||
{this.createSensorItems()}
|
||||
<br></br>
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} padding={1}>
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface EMSESPStatus {
|
||||
}
|
||||
|
||||
export interface Device {
|
||||
id: number;
|
||||
type: string;
|
||||
brand: string;
|
||||
name: string;
|
||||
@@ -32,7 +33,23 @@ export interface Device {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface EMSESPDevices {
|
||||
devices: Device[];
|
||||
export interface Sensor {
|
||||
id: string;
|
||||
temp: number;
|
||||
}
|
||||
|
||||
export interface EMSESPDevices {
|
||||
devices: Device[];
|
||||
sensors: Sensor[];
|
||||
}
|
||||
|
||||
export interface DeviceData {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface EMSESPDeviceData {
|
||||
deviceName: string;
|
||||
deviceData: DeviceData[];
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ sample code bearing this copyright.
|
||||
#include "OneWire_direct_gpio.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
|
||||
void OneWire::begin(uint8_t pin) {
|
||||
pinMode(pin, INPUT);
|
||||
@@ -155,14 +155,17 @@ void OneWire::begin(uint8_t pin) {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Perform the onewire reset function. We will wait up to 250uS for
|
||||
// the bus to come high, if it doesn't then it is broken or shorted
|
||||
// and we return a 0;
|
||||
//
|
||||
// Returns 1 if a device asserted a presence pulse, 0 otherwise.
|
||||
//
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uint8_t IRAM_ATTR OneWire::reset(void) {
|
||||
#else
|
||||
uint8_t OneWire::reset(void) {
|
||||
#endif
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg;
|
||||
uint8_t r;
|
||||
@@ -196,7 +199,11 @@ uint8_t OneWire::reset(void) {
|
||||
// Write a bit. Port and bit is used to cut lookup time and provide
|
||||
// more certain timing.
|
||||
//
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
void IRAM_ATTR OneWire::write_bit(uint8_t v) {
|
||||
#else
|
||||
void OneWire::write_bit(uint8_t v) {
|
||||
#endif
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg;
|
||||
|
||||
@@ -223,7 +230,11 @@ void OneWire::write_bit(uint8_t v) {
|
||||
// Read a bit. Port and bit is used to cut lookup time and provide
|
||||
// more certain timing.
|
||||
//
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uint8_t IRAM_ATTR OneWire::read_bit(void) {
|
||||
#else
|
||||
uint8_t OneWire::read_bit(void) {
|
||||
#endif
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg;
|
||||
uint8_t r;
|
||||
@@ -474,6 +485,7 @@ bool OneWire::search(uint8_t * newAddr, bool search_mode /* = true */) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
newAddr[i] = ROM_NO[i];
|
||||
}
|
||||
// depower(); // https://github.com/PaulStoffregen/OneWire/pull/80
|
||||
return search_result;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,11 @@ class OneWire {
|
||||
// Perform a 1-Wire reset cycle. Returns 1 if a device responds
|
||||
// with a presence pulse. Returns 0 if there is no device or the
|
||||
// bus is shorted or otherwise held low for more than 250uS
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uint8_t IRAM_ATTR reset(void);
|
||||
#else
|
||||
uint8_t reset(void);
|
||||
#endif
|
||||
|
||||
// Issue a 1-Wire rom select command, you do the reset first.
|
||||
void select(const uint8_t rom[8]);
|
||||
@@ -101,11 +105,18 @@ class OneWire {
|
||||
|
||||
// Write a bit. The bus is always left powered at the end, see
|
||||
// note in write() about that.
|
||||
void write_bit(uint8_t v);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
void IRAM_ATTR write_bit(uint8_t v);
|
||||
#else
|
||||
void write_bit(uint8_t v);
|
||||
#endif
|
||||
|
||||
// Read a bit.
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uint8_t IRAM_ATTR read_bit(void);
|
||||
#else
|
||||
uint8_t read_bit(void);
|
||||
|
||||
#endif
|
||||
// Stop forcing power onto the bus. You only need to do this if
|
||||
// you used the 'power' flag to write() or used a write_bit() call
|
||||
// and aren't about to do another read or write. You would rather
|
||||
|
||||
@@ -7,6 +7,8 @@ SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityMa
|
||||
}
|
||||
|
||||
void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
|
||||
uint8_t free_mem_percent = emsesp::System::free_mem(); // added by proddy
|
||||
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE);
|
||||
JsonObject root = response->getRoot();
|
||||
#ifdef ESP32
|
||||
@@ -39,7 +41,7 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
|
||||
#endif
|
||||
|
||||
root["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); // proddy added
|
||||
root["free_mem"] = emsesp::System::free_mem(); // proddy added
|
||||
root["free_mem"] = free_mem_percent; // proddy added
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
#include <WiFiSettingsService.h>
|
||||
|
||||
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
|
||||
_httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager),
|
||||
_fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE),
|
||||
_lastConnectionAttempt(0) {
|
||||
// We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default.
|
||||
// If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future.
|
||||
if (WiFi.getMode() != WIFI_OFF) {
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
WiFiSettingsService::WiFiSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE)
|
||||
, _lastConnectionAttempt(0) {
|
||||
// We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default.
|
||||
// If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future.
|
||||
if (WiFi.getMode() != WIFI_OFF) {
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
|
||||
// Disable WiFi config persistance and auto reconnect
|
||||
WiFi.persistent(false);
|
||||
WiFi.setAutoReconnect(false);
|
||||
// Disable WiFi config persistance and auto reconnect
|
||||
WiFi.persistent(false);
|
||||
WiFi.setAutoReconnect(false);
|
||||
#ifdef ESP32
|
||||
// Init the wifi driver on ESP32
|
||||
WiFi.mode(WIFI_MODE_MAX);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
WiFi.onEvent(
|
||||
std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
|
||||
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
|
||||
WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2),
|
||||
WiFiEvent_t::SYSTEM_EVENT_STA_STOP);
|
||||
// Init the wifi driver on ESP32
|
||||
WiFi.mode(WIFI_MODE_MAX);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
|
||||
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
|
||||
WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_STOP);
|
||||
#elif defined(ESP8266)
|
||||
|
||||
// proddy added
|
||||
@@ -31,78 +29,81 @@ WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, Securit
|
||||
// high tx power causing weird behavior, slightly lowering from 20.5 to 20.0 may help stability
|
||||
// WiFi.setOutputPower(20.0f); // in dBm
|
||||
|
||||
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(
|
||||
std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
|
||||
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
|
||||
#endif
|
||||
|
||||
addUpdateHandler([&](const String& originId) { reconfigureWiFiConnection(); }, false);
|
||||
addUpdateHandler([&](const String & originId) { reconfigureWiFiConnection(); }, false);
|
||||
}
|
||||
|
||||
void WiFiSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
reconfigureWiFiConnection();
|
||||
_fsPersistence.readFromFS();
|
||||
reconfigureWiFiConnection();
|
||||
}
|
||||
|
||||
void WiFiSettingsService::reconfigureWiFiConnection() {
|
||||
// reset last connection attempt to force loop to reconnect immediately
|
||||
_lastConnectionAttempt = 0;
|
||||
// reset last connection attempt to force loop to reconnect immediately
|
||||
_lastConnectionAttempt = 0;
|
||||
|
||||
// disconnect and de-configure wifi
|
||||
#ifdef ESP32
|
||||
if (WiFi.disconnect(true)) {
|
||||
_stopping = true;
|
||||
}
|
||||
if (WiFi.disconnect(true)) {
|
||||
_stopping = true;
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
WiFi.disconnect(true);
|
||||
WiFi.disconnect(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiFiSettingsService::loop() {
|
||||
unsigned long currentMillis = millis();
|
||||
if (!_lastConnectionAttempt || (unsigned long)(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
|
||||
_lastConnectionAttempt = currentMillis;
|
||||
manageSTA();
|
||||
}
|
||||
unsigned long currentMillis = millis();
|
||||
if (!_lastConnectionAttempt || (unsigned long)(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
|
||||
_lastConnectionAttempt = currentMillis;
|
||||
manageSTA();
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiSettingsService::manageSTA() {
|
||||
// Abort if already connected, or if we have no SSID
|
||||
if (WiFi.isConnected() || _state.ssid.length() == 0) {
|
||||
return;
|
||||
}
|
||||
// Connect or reconnect as required
|
||||
if ((WiFi.getMode() & WIFI_STA) == 0) {
|
||||
// Serial.println(F("Connecting to WiFi."));
|
||||
if (_state.staticIPConfig) {
|
||||
// configure for static IP
|
||||
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
|
||||
} else {
|
||||
// configure for DHCP
|
||||
#ifdef ESP32
|
||||
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
WiFi.setHostname(_state.hostname.c_str());
|
||||
#elif defined(ESP8266)
|
||||
WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY);
|
||||
WiFi.hostname(_state.hostname);
|
||||
#endif
|
||||
// Abort if already connected, or if we have no SSID
|
||||
if (WiFi.isConnected() || _state.ssid.length() == 0) {
|
||||
return;
|
||||
}
|
||||
// Connect or reconnect as required
|
||||
if ((WiFi.getMode() & WIFI_STA) == 0) {
|
||||
// Serial.println(F("Connecting to WiFi."));
|
||||
if (_state.staticIPConfig) {
|
||||
// configure for static IP
|
||||
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
|
||||
} else {
|
||||
// configure for DHCP
|
||||
#ifdef ESP32
|
||||
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
#elif defined(ESP8266)
|
||||
WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY);
|
||||
#endif
|
||||
}
|
||||
// set hostname
|
||||
#ifdef ESP32
|
||||
WiFi.setHostname(_state.hostname.c_str());
|
||||
#elif defined(ESP8266)
|
||||
WiFi.hostname(_state.hostname);
|
||||
#endif
|
||||
// attempt to connect to the network
|
||||
WiFi.begin(_state.ssid.c_str(), _state.password.c_str());
|
||||
}
|
||||
// attempt to connect to the network
|
||||
WiFi.begin(_state.ssid.c_str(), _state.password.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
void WiFiSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.disconnect(true);
|
||||
}
|
||||
void WiFiSettingsService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
if (_stopping) {
|
||||
_lastConnectionAttempt = 0;
|
||||
_stopping = false;
|
||||
}
|
||||
if (_stopping) {
|
||||
_lastConnectionAttempt = 0;
|
||||
_stopping = false;
|
||||
}
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
void WiFiSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
|
||||
WiFi.disconnect(true);
|
||||
void WiFiSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) {
|
||||
WiFi.disconnect(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -111,71 +111,3 @@ int digitalRead(uint8_t pin) {
|
||||
return LOW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Copy string src to buffer dst of size dsize. At most dsize-1
|
||||
* chars will be copied. Always NUL terminates (unless dsize == 0).
|
||||
* Returns strlen(src); if retval >= dsize, truncation occurred.
|
||||
*
|
||||
* https://github.com/freebsd/freebsd/blob/master/sys/libkern/strlcpy.c
|
||||
*/
|
||||
size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize) {
|
||||
const char * osrc = src;
|
||||
size_t nleft = dsize;
|
||||
|
||||
/* Copy as many bytes as will fit. */
|
||||
if (nleft != 0) {
|
||||
while (--nleft != 0) {
|
||||
if ((*dst++ = *src++) == '\0')
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Not enough room in dst, add NUL and traverse rest of src. */
|
||||
if (nleft == 0) {
|
||||
if (dsize != 0)
|
||||
*dst = '\0'; /* NUL-terminate dst */
|
||||
while (*src++)
|
||||
;
|
||||
}
|
||||
|
||||
return (src - osrc - 1); /* count does not include NUL */
|
||||
}
|
||||
|
||||
/*
|
||||
* Appends src to string dst of size siz (unlike strncat, siz is the
|
||||
* full size of dst, not space left). At most siz-1 characters
|
||||
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
|
||||
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
|
||||
* If retval >= siz, truncation occurred.
|
||||
*
|
||||
* https://github.com/freebsd/freebsd/blob/master/sys/libkern/strlcat.c
|
||||
*/
|
||||
size_t strlcat(char * dst, const char * src, size_t siz) {
|
||||
char * d = dst;
|
||||
const char * s = src;
|
||||
size_t n = siz;
|
||||
size_t dlen;
|
||||
|
||||
/* Find the end of dst and adjust bytes left but don't go past end */
|
||||
while (n-- != 0 && *d != '\0')
|
||||
d++;
|
||||
dlen = d - dst;
|
||||
n = siz - dlen;
|
||||
|
||||
if (n == 0)
|
||||
return (dlen + strlen(s));
|
||||
while (*s != '\0') {
|
||||
if (n != 1) {
|
||||
*d++ = *s;
|
||||
n--;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
*d = '\0';
|
||||
|
||||
return (dlen + (s - src)); /* count does not include NUL */
|
||||
}
|
||||
|
||||
@@ -136,34 +136,6 @@ class Stream : public Print {
|
||||
virtual int peek() = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
class String {
|
||||
public:
|
||||
String(const char * data = "")
|
||||
: data_(data) {
|
||||
}
|
||||
|
||||
long toInt() const {
|
||||
return std::stol(data_);
|
||||
}
|
||||
|
||||
const char * c_str() const {
|
||||
return data_.c_str();
|
||||
}
|
||||
|
||||
bool equals(String comp) {
|
||||
return (data_ == comp.c_str());
|
||||
}
|
||||
|
||||
bool isEmpty() {
|
||||
return data_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
};
|
||||
*/
|
||||
|
||||
class NativeConsole : public Stream {
|
||||
public:
|
||||
void begin(unsigned long baud __attribute__((unused))) {
|
||||
@@ -225,10 +197,6 @@ void yield(void);
|
||||
void setup(void);
|
||||
void loop(void);
|
||||
|
||||
|
||||
size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize);
|
||||
size_t strlcat(char * dst, const char * src, size_t siz);
|
||||
|
||||
#include "WString.h"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,16 +3,11 @@
|
||||
#define ASYNC_JSON_H_
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
// #include <Print.h>
|
||||
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
|
||||
constexpr const char * JSON_MIMETYPE = "application/json";
|
||||
|
||||
/*
|
||||
* Json Response
|
||||
* */
|
||||
|
||||
class ChunkPrint : public Print {
|
||||
private:
|
||||
uint8_t * _destination;
|
||||
@@ -45,9 +40,8 @@ class ChunkPrint : public Print {
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncJsonResponse {
|
||||
class AsyncJsonResponse {
|
||||
protected:
|
||||
|
||||
DynamicJsonDocument _jsonBuffer;
|
||||
|
||||
JsonVariant _root;
|
||||
@@ -71,7 +65,7 @@ class AsyncJsonResponse {
|
||||
return _isValid;
|
||||
}
|
||||
size_t setLength() {
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t getSize() {
|
||||
@@ -79,9 +73,6 @@ return 0;
|
||||
}
|
||||
|
||||
size_t _fillBuffer(uint8_t * data, size_t len) {
|
||||
// ChunkPrint dest(data, 0, len);
|
||||
|
||||
// serializeJson(_root, dest);
|
||||
return len;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,139 +5,114 @@
|
||||
#include <functional>
|
||||
|
||||
enum class AsyncMqttClientDisconnectReason : int8_t {
|
||||
TCP_DISCONNECTED = 0,
|
||||
TCP_DISCONNECTED = 0,
|
||||
|
||||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
||||
MQTT_IDENTIFIER_REJECTED = 2,
|
||||
MQTT_SERVER_UNAVAILABLE = 3,
|
||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
||||
MQTT_NOT_AUTHORIZED = 5,
|
||||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
||||
MQTT_IDENTIFIER_REJECTED = 2,
|
||||
MQTT_SERVER_UNAVAILABLE = 3,
|
||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
||||
MQTT_NOT_AUTHORIZED = 5,
|
||||
|
||||
ESP8266_NOT_ENOUGH_SPACE = 6,
|
||||
ESP8266_NOT_ENOUGH_SPACE = 6,
|
||||
|
||||
TLS_BAD_FINGERPRINT = 7
|
||||
TLS_BAD_FINGERPRINT = 7
|
||||
};
|
||||
|
||||
struct AsyncMqttClientMessageProperties {
|
||||
uint8_t qos;
|
||||
bool dup;
|
||||
bool retain;
|
||||
uint8_t qos;
|
||||
bool dup;
|
||||
bool retain;
|
||||
};
|
||||
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
// user callbacks
|
||||
typedef std::function<void(bool sessionPresent)> OnConnectUserCallback;
|
||||
|
||||
typedef std::function<void(bool sessionPresent)> OnConnectUserCallback;
|
||||
typedef std::function<void(AsyncMqttClientDisconnectReason reason)> OnDisconnectUserCallback;
|
||||
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback;
|
||||
typedef std::function<void(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback;
|
||||
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback;
|
||||
typedef std::function<void(char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnPublishUserCallback;
|
||||
};
|
||||
}; // namespace AsyncMqttClientInternals
|
||||
|
||||
class AsyncMqttClient {
|
||||
public:
|
||||
AsyncMqttClient();
|
||||
~AsyncMqttClient();
|
||||
public:
|
||||
AsyncMqttClient();
|
||||
~AsyncMqttClient();
|
||||
|
||||
AsyncMqttClient& setKeepAlive(uint16_t keepAlive);
|
||||
AsyncMqttClient& setClientId(const char* clientId);
|
||||
AsyncMqttClient& setCleanSession(bool cleanSession);
|
||||
AsyncMqttClient& setMaxTopicLength(uint16_t maxTopicLength);
|
||||
AsyncMqttClient& setCredentials(const char* username, const char* password = nullptr);
|
||||
AsyncMqttClient& setWill(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0) { return *this; }
|
||||
AsyncMqttClient& setServer(IPAddress ip, uint16_t port);
|
||||
AsyncMqttClient& setServer(const char* host, uint16_t port);
|
||||
AsyncMqttClient & setKeepAlive(uint16_t keepAlive);
|
||||
AsyncMqttClient & setClientId(const char * clientId);
|
||||
AsyncMqttClient & setCleanSession(bool cleanSession);
|
||||
AsyncMqttClient & setMaxTopicLength(uint16_t maxTopicLength);
|
||||
AsyncMqttClient & setCredentials(const char * username, const char * password = nullptr);
|
||||
AsyncMqttClient & setWill(const char * topic, uint8_t qos, bool retain, const char * payload = nullptr, size_t length = 0) {
|
||||
return *this;
|
||||
}
|
||||
AsyncMqttClient & setServer(IPAddress ip, uint16_t port);
|
||||
AsyncMqttClient & setServer(const char * host, uint16_t port);
|
||||
|
||||
AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) { return *this; }
|
||||
AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) { return *this; }
|
||||
AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) { return *this; }
|
||||
AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) { return *this; }
|
||||
AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) { return *this; }
|
||||
AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) { return *this; }
|
||||
AsyncMqttClient & onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) {
|
||||
return *this;
|
||||
}
|
||||
AsyncMqttClient & onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) {
|
||||
return *this;
|
||||
}
|
||||
AsyncMqttClient & onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) {
|
||||
return *this;
|
||||
}
|
||||
AsyncMqttClient & onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) {
|
||||
return *this;
|
||||
}
|
||||
AsyncMqttClient & onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) {
|
||||
return *this;
|
||||
}
|
||||
AsyncMqttClient & onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool connected() const { return false; }
|
||||
void connect() {}
|
||||
void disconnect(bool force = false) {}
|
||||
uint16_t subscribe(const char* topic, uint8_t qos) {return 0;}
|
||||
uint16_t unsubscribe(const char* topic) {return 0;}
|
||||
uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0) {return 0;}
|
||||
bool connected() const {
|
||||
return false;
|
||||
}
|
||||
void connect() {
|
||||
}
|
||||
void disconnect(bool force = false) {
|
||||
}
|
||||
uint16_t subscribe(const char * topic, uint8_t qos) {
|
||||
return 0;
|
||||
}
|
||||
uint16_t unsubscribe(const char * topic) {
|
||||
return 0;
|
||||
}
|
||||
uint16_t publish(const char * topic, uint8_t qos, bool retain, const char * payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* getClientId() {return "12";}
|
||||
const char * getClientId() {
|
||||
return "12";
|
||||
}
|
||||
|
||||
private:
|
||||
// AsyncClient _client;
|
||||
|
||||
bool _connected;
|
||||
bool _connectPacketNotEnoughSpace;
|
||||
bool _disconnectOnPoll;
|
||||
bool _tlsBadFingerprint;
|
||||
uint32_t _lastClientActivity;
|
||||
uint32_t _lastServerActivity;
|
||||
uint32_t _lastPingRequestTime;
|
||||
char _generatedClientId[18 + 1]; // esp8266-abc123 and esp32-abcdef123456
|
||||
IPAddress _ip;
|
||||
const char* _host;
|
||||
bool _useIp;
|
||||
uint16_t _port;
|
||||
uint16_t _keepAlive;
|
||||
bool _cleanSession;
|
||||
const char* _clientId;
|
||||
const char* _username;
|
||||
const char* _password;
|
||||
const char* _willTopic;
|
||||
const char* _willPayload;
|
||||
uint16_t _willPayloadLength;
|
||||
uint8_t _willQos;
|
||||
bool _willRetain;
|
||||
|
||||
// std::vector<AsyncMqttClientInternals::OnConnectUserCallback> _onConnectUserCallbacks;
|
||||
// std::vector<AsyncMqttClientInternals::OnDisconnectUserCallback> _onDisconnectUserCallbacks;
|
||||
// std::vector<AsyncMqttClientInternals::OnSubscribeUserCallback> _onSubscribeUserCallbacks;
|
||||
// std::vector<AsyncMqttClientInternals::OnUnsubscribeUserCallback> _onUnsubscribeUserCallbacks;
|
||||
// std::vector<AsyncMqttClientInternals::OnMessageUserCallback> _onMessageUserCallbacks;
|
||||
// std::vector<AsyncMqttClientInternals::OnPublishUserCallback> _onPublishUserCallbacks;
|
||||
|
||||
// AsyncMqttClientInternals::ParsingInformation _parsingInformation;
|
||||
// AsyncMqttClientInternals::Packet* _currentParsedPacket;
|
||||
// uint8_t _remainingLengthBufferPosition;
|
||||
// char _remainingLengthBuffer[4];
|
||||
|
||||
// uint16_t _nextPacketId;
|
||||
|
||||
// std::vector<AsyncMqttClientInternals::PendingPubRel> _pendingPubRels;
|
||||
|
||||
// std::vector<AsyncMqttClientInternals::PendingAck> _toSendAcks;
|
||||
|
||||
// void _clear();
|
||||
// void _freeCurrentParsedPacket();
|
||||
|
||||
// TCP
|
||||
// void _onConnect(AsyncClient* client);
|
||||
// void _onDisconnect(AsyncClient* client);
|
||||
// static void _onError(AsyncClient* client, int8_t error);
|
||||
// void _onTimeout(AsyncClient* client, uint32_t time);
|
||||
// static void _onAck(AsyncClient* client, size_t len, uint32_t time);
|
||||
// void _onData(AsyncClient* client, char* data, size_t len);
|
||||
// void _onPoll(AsyncClient* client);
|
||||
|
||||
// // MQTT
|
||||
// void _onPingResp();
|
||||
// void _onConnAck(bool sessionPresent, uint8_t connectReturnCode);
|
||||
// void _onSubAck(uint16_t packetId, char status);
|
||||
// void _onUnsubAck(uint16_t packetId);
|
||||
// void _onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId);
|
||||
// void _onPublish(uint16_t packetId, uint8_t qos);
|
||||
// void _onPubRel(uint16_t packetId);
|
||||
// void _onPubAck(uint16_t packetId);
|
||||
// void _onPubRec(uint16_t packetId);
|
||||
// void _onPubComp(uint16_t packetId);
|
||||
|
||||
// bool _sendPing();
|
||||
// void _sendAcks();
|
||||
// bool _sendDisconnect();
|
||||
|
||||
// uint16_t _getNextPacketId();
|
||||
private:
|
||||
bool _connected;
|
||||
bool _connectPacketNotEnoughSpace;
|
||||
bool _disconnectOnPoll;
|
||||
bool _tlsBadFingerprint;
|
||||
uint32_t _lastClientActivity;
|
||||
uint32_t _lastServerActivity;
|
||||
uint32_t _lastPingRequestTime;
|
||||
char _generatedClientId[18 + 1]; // esp8266-abc123 and esp32-abcdef123456
|
||||
IPAddress _ip;
|
||||
const char * _host;
|
||||
bool _useIp;
|
||||
uint16_t _port;
|
||||
uint16_t _keepAlive;
|
||||
bool _cleanSession;
|
||||
const char * _clientId;
|
||||
const char * _username;
|
||||
const char * _password;
|
||||
const char * _willTopic;
|
||||
const char * _willPayload;
|
||||
uint16_t _willPayloadLength;
|
||||
uint8_t _willQos;
|
||||
bool _willRetain;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,212 +1,29 @@
|
||||
/*
|
||||
Asynchronous TCP library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef ASYNCTCP_H_
|
||||
#define ASYNCTCP_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <functional>
|
||||
|
||||
//If core is not defined, then we are running in Arduino or PIO
|
||||
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
|
||||
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
|
||||
#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event
|
||||
#endif
|
||||
|
||||
class AsyncClient;
|
||||
|
||||
#define ASYNC_MAX_ACK_TIME 5000
|
||||
#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
|
||||
#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
|
||||
|
||||
typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t time)> AcAckHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, void *data, size_t len)> AcDataHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, struct pbuf *pb)> AcPacketHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, uint32_t time)> AcTimeoutHandler;
|
||||
|
||||
struct tcp_pcb;
|
||||
struct ip_addr;
|
||||
|
||||
class AsyncClient {
|
||||
public:
|
||||
AsyncClient(tcp_pcb* pcb = 0);
|
||||
AsyncClient(tcp_pcb * pcb = 0);
|
||||
~AsyncClient();
|
||||
|
||||
AsyncClient & operator=(const AsyncClient &other);
|
||||
AsyncClient & operator+=(const AsyncClient &other);
|
||||
|
||||
bool operator==(const AsyncClient &other);
|
||||
|
||||
bool operator!=(const AsyncClient &other) {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool connect(IPAddress ip, uint16_t port);
|
||||
bool connect(const char* host, uint16_t port);
|
||||
void close(bool now = false);
|
||||
void stop();
|
||||
int8_t abort();
|
||||
bool free();
|
||||
|
||||
bool canSend();//ack is not pending
|
||||
size_t space();//space available in the TCP window
|
||||
size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending
|
||||
bool send();//send all data added with the method above
|
||||
|
||||
//write equals add()+send()
|
||||
size_t write(const char* data);
|
||||
size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true
|
||||
|
||||
uint8_t state();
|
||||
bool connecting();
|
||||
bool connected();
|
||||
bool disconnecting();
|
||||
bool disconnected();
|
||||
bool freeable();//disconnected or disconnecting
|
||||
|
||||
uint16_t getMss();
|
||||
|
||||
uint32_t getRxTimeout();
|
||||
void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds
|
||||
|
||||
uint32_t getAckTimeout();
|
||||
void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds
|
||||
|
||||
void setNoDelay(bool nodelay);
|
||||
bool getNoDelay();
|
||||
|
||||
uint32_t getRemoteAddress();
|
||||
uint16_t getRemotePort();
|
||||
uint32_t getLocalAddress();
|
||||
uint16_t getLocalPort();
|
||||
|
||||
//compatibility
|
||||
IPAddress remoteIP();
|
||||
uint16_t remotePort();
|
||||
IPAddress localIP();
|
||||
uint16_t localPort();
|
||||
|
||||
void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect
|
||||
void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected
|
||||
void onAck(AcAckHandler cb, void* arg = 0); //ack received
|
||||
void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error
|
||||
void onData(AcDataHandler cb, void* arg = 0); //data received (called if onPacket is not used)
|
||||
void onPacket(AcPacketHandler cb, void* arg = 0); //data received
|
||||
void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout
|
||||
void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected
|
||||
|
||||
void ackPacket(struct pbuf * pb);//ack pbuf from onPacket
|
||||
size_t ack(size_t len); //ack data that you have not acked using the method below
|
||||
void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData
|
||||
|
||||
const char * errorToString(int8_t error);
|
||||
const char * stateToString();
|
||||
|
||||
//Do not use any of the functions below!
|
||||
static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb);
|
||||
static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err);
|
||||
static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err);
|
||||
static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err);
|
||||
static void _s_error(void *arg, int8_t err);
|
||||
static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len);
|
||||
static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
|
||||
static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg);
|
||||
|
||||
int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err);
|
||||
tcp_pcb * pcb(){ return _pcb; }
|
||||
|
||||
protected:
|
||||
tcp_pcb* _pcb;
|
||||
int8_t _closed_slot;
|
||||
|
||||
AcConnectHandler _connect_cb;
|
||||
void* _connect_cb_arg;
|
||||
AcConnectHandler _discard_cb;
|
||||
void* _discard_cb_arg;
|
||||
AcAckHandler _sent_cb;
|
||||
void* _sent_cb_arg;
|
||||
AcErrorHandler _error_cb;
|
||||
void* _error_cb_arg;
|
||||
AcDataHandler _recv_cb;
|
||||
void* _recv_cb_arg;
|
||||
AcPacketHandler _pb_cb;
|
||||
void* _pb_cb_arg;
|
||||
AcTimeoutHandler _timeout_cb;
|
||||
void* _timeout_cb_arg;
|
||||
AcConnectHandler _poll_cb;
|
||||
void* _poll_cb_arg;
|
||||
|
||||
bool _pcb_busy;
|
||||
uint32_t _pcb_sent_at;
|
||||
bool _ack_pcb;
|
||||
uint32_t _rx_ack_len;
|
||||
uint32_t _rx_last_packet;
|
||||
uint32_t _rx_since_timeout;
|
||||
uint32_t _ack_timeout;
|
||||
uint16_t _connect_port;
|
||||
|
||||
int8_t _close();
|
||||
int8_t _connected(void* pcb, int8_t err);
|
||||
void _error(int8_t err);
|
||||
int8_t _poll(tcp_pcb* pcb);
|
||||
int8_t _sent(tcp_pcb* pcb, uint16_t len);
|
||||
int8_t _fin(tcp_pcb* pcb, int8_t err);
|
||||
int8_t _lwip_fin(tcp_pcb* pcb, int8_t err);
|
||||
void _dns_found(struct ip_addr *ipaddr);
|
||||
|
||||
public:
|
||||
AsyncClient* prev;
|
||||
AsyncClient* next;
|
||||
};
|
||||
|
||||
class AsyncServer {
|
||||
public:
|
||||
AsyncServer(IPAddress addr, uint16_t port) : _port(port), _addr("poep") {};
|
||||
|
||||
AsyncServer(uint16_t port) : _port(port) {};
|
||||
|
||||
~AsyncServer() {};
|
||||
void onClient(AcConnectHandler cb, void* arg);
|
||||
void begin();
|
||||
void end();
|
||||
void setNoDelay(bool nodelay);
|
||||
bool getNoDelay();
|
||||
uint8_t status();
|
||||
|
||||
//Do not use any of the functions below!
|
||||
static int8_t _s_accept(void *arg, tcp_pcb* newpcb, int8_t err);
|
||||
static int8_t _s_accepted(void *arg, AsyncClient* client);
|
||||
AsyncServer(uint16_t port)
|
||||
: _port(port){};
|
||||
~AsyncServer(){};
|
||||
|
||||
protected:
|
||||
uint16_t _port;
|
||||
IPAddress _addr;
|
||||
bool _noDelay;
|
||||
tcp_pcb* _pcb;
|
||||
AcConnectHandler _connect_cb;
|
||||
void* _connect_cb_arg;
|
||||
|
||||
int8_t _accept(tcp_pcb* newpcb, int8_t err);
|
||||
int8_t _accepted(AsyncClient* client);
|
||||
};
|
||||
|
||||
|
||||
#endif /* ASYNCTCP_H_ */
|
||||
#endif
|
||||
|
||||
@@ -4,32 +4,31 @@
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <FS.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <SecuritySettingsService.h>
|
||||
#include <StatefulService.h>
|
||||
|
||||
class DummySettings {
|
||||
public:
|
||||
uint8_t tx_mode;
|
||||
uint8_t ems_bus_id;
|
||||
bool system_heartbeat;
|
||||
int8_t syslog_level; // uuid::log::Level
|
||||
uint32_t syslog_mark_interval;
|
||||
String syslog_host;
|
||||
uint8_t master_thermostat;
|
||||
bool shower_timer;
|
||||
bool shower_alert;
|
||||
|
||||
uint16_t publish_time; // seconds
|
||||
uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom
|
||||
uint8_t mqtt_qos;
|
||||
|
||||
String hostname;
|
||||
String jwtSecret;
|
||||
String ssid;
|
||||
String password;
|
||||
uint8_t tx_mode = 1;
|
||||
uint8_t ems_bus_id = 0x0B;
|
||||
bool system_heartbeat = false;
|
||||
int8_t syslog_level = 1; // uuid::log::Level
|
||||
uint32_t syslog_mark_interval = 0;
|
||||
String syslog_host = "192.168.1.4";
|
||||
uint8_t master_thermostat = 0;
|
||||
bool shower_timer = false;
|
||||
bool shower_alert = false;
|
||||
uint16_t publish_time = 10; // seconds
|
||||
uint8_t mqtt_format = 1; // 1=single, 2=nested, 3=ha, 4=custom
|
||||
uint8_t mqtt_qos = 0;
|
||||
String hostname = "ems-esp";
|
||||
String jwtSecret = "ems-esp";
|
||||
String ssid = "ems-esp";
|
||||
String password = "ems-esp";
|
||||
|
||||
static void read(DummySettings & settings, JsonObject & root){};
|
||||
static void read(DummySettings & settings){};
|
||||
@@ -56,13 +55,14 @@ class DummySettingsService : public StatefulService<DummySettings> {
|
||||
class ESP8266React {
|
||||
public:
|
||||
ESP8266React(AsyncWebServer * server, FS * fs)
|
||||
: _settings(server, fs, nullptr){};
|
||||
: _settings(server, fs, nullptr)
|
||||
, _securitySettingsService(server, fs){};
|
||||
|
||||
void begin(){};
|
||||
void loop(){};
|
||||
|
||||
SecurityManager * getSecurityManager() {
|
||||
return nullptr;
|
||||
return &_securitySettingsService;
|
||||
}
|
||||
|
||||
AsyncMqttClient * getMqttClient() {
|
||||
@@ -82,8 +82,10 @@ class ESP8266React {
|
||||
}
|
||||
|
||||
private:
|
||||
DummySettingsService _settings;
|
||||
AsyncMqttClient * _mqttClient;
|
||||
DummySettingsService _settings;
|
||||
SecuritySettingsService _securitySettingsService;
|
||||
|
||||
AsyncMqttClient * _mqttClient;
|
||||
};
|
||||
|
||||
class EMSESPSettingsService {
|
||||
@@ -93,8 +95,6 @@ class EMSESPSettingsService {
|
||||
void begin();
|
||||
|
||||
private:
|
||||
// HttpEndpoint<EMSESPSettings> _httpEndpoint;
|
||||
// FSPersistence<EMSESPSettings> _fsPersistence;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,22 +7,11 @@
|
||||
#include <AsyncTCP.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#define DEBUGF(...) //Serial.printf(__VA_ARGS__)
|
||||
|
||||
class AsyncWebServer;
|
||||
class AsyncWebServerRequest;
|
||||
class AsyncWebServerResponse;
|
||||
class AsyncWebHeader;
|
||||
class AsyncWebParameter;
|
||||
class AsyncWebRewrite;
|
||||
class AsyncWebHandler;
|
||||
class AsyncStaticWebHandler;
|
||||
class AsyncCallbackWebHandler;
|
||||
class AsyncResponseStream;
|
||||
class AsyncJsonResponse;
|
||||
|
||||
|
||||
|
||||
typedef enum {
|
||||
HTTP_GET = 0b00000001,
|
||||
HTTP_POST = 0b00000010,
|
||||
@@ -34,151 +23,17 @@ typedef enum {
|
||||
HTTP_ANY = 0b01111111,
|
||||
} WebRequestMethod;
|
||||
|
||||
//if this value is returned when asked for data, packet will not be sent and you will be asked for data again
|
||||
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
|
||||
|
||||
typedef uint8_t WebRequestMethodComposite;
|
||||
typedef std::function<void(void)> ArDisconnectHandler;
|
||||
|
||||
/*
|
||||
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters
|
||||
* */
|
||||
|
||||
class AsyncWebParameter {
|
||||
private:
|
||||
String _name;
|
||||
String _value;
|
||||
size_t _size;
|
||||
bool _isForm;
|
||||
bool _isFile;
|
||||
|
||||
public:
|
||||
AsyncWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0)
|
||||
: _name(name)
|
||||
, _value(value)
|
||||
, _size(size)
|
||||
, _isForm(form)
|
||||
, _isFile(file) {
|
||||
}
|
||||
const String & name() const {
|
||||
return _name;
|
||||
}
|
||||
const String & value() const {
|
||||
return _value;
|
||||
}
|
||||
size_t size() const {
|
||||
return _size;
|
||||
}
|
||||
bool isPost() const {
|
||||
return _isForm;
|
||||
}
|
||||
bool isFile() const {
|
||||
return _isFile;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* HEADER :: Chainable object to hold the headers
|
||||
* */
|
||||
|
||||
class AsyncWebHeader {
|
||||
private:
|
||||
String _name;
|
||||
String _value;
|
||||
|
||||
public:
|
||||
AsyncWebHeader(const String & name, const String & value)
|
||||
: _name(name)
|
||||
, _value(value) {
|
||||
}
|
||||
AsyncWebHeader(const String & data)
|
||||
: _name()
|
||||
, _value() {
|
||||
}
|
||||
~AsyncWebHeader() {
|
||||
}
|
||||
const String & name() const {
|
||||
return _name;
|
||||
}
|
||||
const String & value() const {
|
||||
return _value;
|
||||
}
|
||||
String toString() const {
|
||||
return _value;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
|
||||
* */
|
||||
|
||||
typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType;
|
||||
|
||||
typedef std::function<size_t(uint8_t *, size_t, size_t)> AwsResponseFiller;
|
||||
typedef std::function<String(const String &)> AwsTemplateProcessor;
|
||||
|
||||
class AsyncWebServerRequest {
|
||||
friend class AsyncWebServer;
|
||||
friend class AsyncCallbackWebHandler;
|
||||
|
||||
private:
|
||||
AsyncClient * _client;
|
||||
AsyncWebServer * _server;
|
||||
AsyncWebHandler * _handler;
|
||||
AsyncWebServerResponse * _response;
|
||||
ArDisconnectHandler _onDisconnectfn;
|
||||
|
||||
String _temp;
|
||||
uint8_t _parseState;
|
||||
|
||||
uint8_t _version;
|
||||
AsyncClient * _client;
|
||||
AsyncWebServer * _server;
|
||||
WebRequestMethodComposite _method;
|
||||
String _url;
|
||||
String _host;
|
||||
String _contentType;
|
||||
String _boundary;
|
||||
String _authorization;
|
||||
RequestedConnectionType _reqconntype;
|
||||
void _removeNotInterestingHeaders();
|
||||
bool _isDigest;
|
||||
bool _isMultipart;
|
||||
bool _isPlainPost;
|
||||
bool _expectingContinue;
|
||||
size_t _contentLength;
|
||||
size_t _parsedLength;
|
||||
|
||||
uint8_t _multiParseState;
|
||||
uint8_t _boundaryPosition;
|
||||
size_t _itemStartIndex;
|
||||
size_t _itemSize;
|
||||
String _itemName;
|
||||
String _itemFilename;
|
||||
String _itemType;
|
||||
String _itemValue;
|
||||
uint8_t * _itemBuffer;
|
||||
size_t _itemBufferIndex;
|
||||
bool _itemIsFile;
|
||||
|
||||
void _onPoll();
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onError(int8_t error);
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect(){};
|
||||
void _onData(void * buf, size_t len);
|
||||
|
||||
void _addParam(AsyncWebParameter *);
|
||||
void _addPathParam(const char * param);
|
||||
|
||||
bool _parseReqHead();
|
||||
bool _parseReqHeader();
|
||||
void _parseLine();
|
||||
void _parsePlainPostChar(uint8_t data);
|
||||
void _parseMultipartPostByte(uint8_t data, bool last);
|
||||
void _addGetParams(const String & params);
|
||||
|
||||
void _handleUploadStart();
|
||||
void _handleUploadByte(uint8_t data, bool last);
|
||||
void _handleUploadEnd();
|
||||
|
||||
public:
|
||||
void * _tempObject;
|
||||
@@ -189,177 +44,39 @@ class AsyncWebServerRequest {
|
||||
AsyncClient * client() {
|
||||
return _client;
|
||||
}
|
||||
uint8_t version() const {
|
||||
return _version;
|
||||
}
|
||||
|
||||
WebRequestMethodComposite method() const {
|
||||
return _method;
|
||||
}
|
||||
const String & url() const {
|
||||
return _url;
|
||||
}
|
||||
const String & host() const {
|
||||
return _host;
|
||||
}
|
||||
const String & contentType() const {
|
||||
return _contentType;
|
||||
}
|
||||
size_t contentLength() const {
|
||||
return _contentLength;
|
||||
}
|
||||
bool multipart() const {
|
||||
return _isMultipart;
|
||||
}
|
||||
const char * methodToString() const;
|
||||
const char * requestedConnTypeToString() const;
|
||||
RequestedConnectionType requestedConnType() const {
|
||||
return _reqconntype;
|
||||
}
|
||||
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
|
||||
void onDisconnect(ArDisconnectHandler fn){};
|
||||
|
||||
//hash is the string representation of:
|
||||
// base64(user:pass) for basic or
|
||||
// user:realm:md5(user:realm:pass) for digest
|
||||
bool authenticate(const char * hash);
|
||||
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false);
|
||||
void requestAuthentication(const char * realm = NULL, bool isDigest = true);
|
||||
|
||||
void setHandler(AsyncWebHandler * handler) {
|
||||
_handler = handler;
|
||||
}
|
||||
void addInterestingHeader(const String & name);
|
||||
|
||||
void redirect(const String & url);
|
||||
void addInterestingHeader(const String & name){};
|
||||
|
||||
void send(AsyncWebServerResponse * response){};
|
||||
void send(AsyncJsonResponse * response){};
|
||||
void send(int code, const String & contentType = String(), const String & content = String()){};
|
||||
void send(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
void send(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
void sendChunked(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
void send_P(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
void send_P(int code, const String & contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
|
||||
|
||||
AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()) {
|
||||
// AsyncWebServerResponse *a = new AsyncWebServerResponse()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String());
|
||||
AsyncWebServerResponse * beginResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncWebServerResponse * beginResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncWebServerResponse * beginChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncResponseStream * beginResponseStream(const String & contentType, size_t bufferSize = 1460);
|
||||
AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
|
||||
|
||||
size_t headers() const; // get header count
|
||||
bool hasHeader(const String & name) const; // check if header exists
|
||||
bool hasHeader(const __FlashStringHelper * data) const; // check if header exists
|
||||
|
||||
AsyncWebHeader * getHeader(const String & name) const;
|
||||
AsyncWebHeader * getHeader(const __FlashStringHelper * data) const;
|
||||
AsyncWebHeader * getHeader(size_t num) const;
|
||||
|
||||
size_t params() const; // get arguments count
|
||||
bool hasParam(const String & name, bool post = false, bool file = false) const;
|
||||
bool hasParam(const __FlashStringHelper * data, bool post = false, bool file = false) const;
|
||||
|
||||
AsyncWebParameter * getParam(const String & name, bool post = false, bool file = false) const;
|
||||
AsyncWebParameter * getParam(const __FlashStringHelper * data, bool post, bool file) const;
|
||||
AsyncWebParameter * getParam(size_t num) const;
|
||||
|
||||
size_t args() const {
|
||||
return params();
|
||||
} // get arguments count
|
||||
const String & arg(const String & name) const; // get request argument value by name
|
||||
const String & arg(const __FlashStringHelper * data) const; // get request argument value by F(name)
|
||||
const String & arg(size_t i) const; // get request argument value by number
|
||||
const String & argName(size_t i) const; // get request argument name by number
|
||||
bool hasArg(const char * name) const; // check if argument exists
|
||||
bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists
|
||||
|
||||
const String & pathArg(size_t i) const;
|
||||
|
||||
const String & header(const char * name) const; // get request header value by name
|
||||
const String & header(const __FlashStringHelper * data) const; // get request header value by F(name)
|
||||
const String & header(size_t i) const; // get request header value by number
|
||||
const String & headerName(size_t i) const; // get request header name by number
|
||||
String urlDecode(const String & text) const;
|
||||
size_t headers() const; // get header count
|
||||
size_t params() const; // get arguments count
|
||||
};
|
||||
|
||||
/*
|
||||
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
|
||||
* */
|
||||
|
||||
typedef std::function<bool(AsyncWebServerRequest * request)> ArRequestFilterFunction;
|
||||
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest * request);
|
||||
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest * request);
|
||||
|
||||
/*
|
||||
* REWRITE :: One instance can be handle any Request (done by the Server)
|
||||
* */
|
||||
|
||||
class AsyncWebRewrite {
|
||||
protected:
|
||||
String _from;
|
||||
String _toUrl;
|
||||
String _params;
|
||||
ArRequestFilterFunction _filter;
|
||||
|
||||
public:
|
||||
AsyncWebRewrite(const char * from, const char * to)
|
||||
: _from(from)
|
||||
, _toUrl(to)
|
||||
, _params(String())
|
||||
, _filter(NULL) {
|
||||
}
|
||||
virtual ~AsyncWebRewrite() {
|
||||
}
|
||||
AsyncWebRewrite & setFilter(ArRequestFilterFunction fn) {
|
||||
_filter = fn;
|
||||
return *this;
|
||||
}
|
||||
bool filter(AsyncWebServerRequest * request) const {
|
||||
return _filter == NULL || _filter(request);
|
||||
}
|
||||
const String & from(void) const {
|
||||
return _from;
|
||||
}
|
||||
const String & toUrl(void) const {
|
||||
return _toUrl;
|
||||
}
|
||||
const String & params(void) const {
|
||||
return _params;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* HANDLER :: One instance can be attached to any Request (done by the Server)
|
||||
* */
|
||||
|
||||
class AsyncWebHandler {
|
||||
protected:
|
||||
ArRequestFilterFunction _filter;
|
||||
String _username;
|
||||
String _password;
|
||||
String _username;
|
||||
String _password;
|
||||
|
||||
public:
|
||||
AsyncWebHandler()
|
||||
: _username("")
|
||||
, _password("") {
|
||||
}
|
||||
AsyncWebHandler & setFilter(ArRequestFilterFunction fn) {
|
||||
_filter = fn;
|
||||
return *this;
|
||||
}
|
||||
AsyncWebHandler & setAuthentication(const char * username, const char * password) {
|
||||
_username = String(username);
|
||||
_password = String(password);
|
||||
return *this;
|
||||
};
|
||||
bool filter(AsyncWebServerRequest * request) {
|
||||
return _filter == NULL || _filter(request);
|
||||
}
|
||||
|
||||
virtual ~AsyncWebHandler() {
|
||||
}
|
||||
virtual bool canHandle(AsyncWebServerRequest * request __attribute__((unused))) {
|
||||
@@ -380,142 +97,41 @@ class AsyncWebHandler {
|
||||
size_t index __attribute__((unused)),
|
||||
size_t total __attribute__((unused))) {
|
||||
}
|
||||
|
||||
virtual bool isRequestHandlerTrivial() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* RESPONSE :: One instance is created for each Request (attached by the Handler)
|
||||
* */
|
||||
|
||||
typedef enum { RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED } WebResponseState;
|
||||
|
||||
class AsyncWebServerResponse {
|
||||
protected:
|
||||
int _code;
|
||||
String _contentType;
|
||||
size_t _contentLength;
|
||||
bool _sendContentLength;
|
||||
bool _chunked;
|
||||
size_t _headLength;
|
||||
size_t _sentLength;
|
||||
size_t _ackedLength;
|
||||
size_t _writtenLength;
|
||||
WebResponseState _state;
|
||||
const char * _responseCodeToString(int code);
|
||||
|
||||
public:
|
||||
AsyncWebServerResponse();
|
||||
virtual ~AsyncWebServerResponse();
|
||||
virtual void setCode(int code);
|
||||
virtual void setContentLength(size_t len);
|
||||
virtual void setContentType(const String & type);
|
||||
virtual void addHeader(const String & name, const String & value);
|
||||
virtual String _assembleHead(uint8_t version);
|
||||
virtual bool _started() const;
|
||||
virtual bool _finished() const;
|
||||
virtual bool _failed() const;
|
||||
virtual bool _sourceValid() const;
|
||||
virtual void _respond(AsyncWebServerRequest * request);
|
||||
virtual size_t _ack(AsyncWebServerRequest * request, size_t len, uint32_t time);
|
||||
};
|
||||
|
||||
/*
|
||||
* SERVER :: One instance
|
||||
* */
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest * request)> ArRequestHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final)> ArUploadHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
|
||||
|
||||
class AsyncWebServer {
|
||||
protected:
|
||||
AsyncServer _server;
|
||||
AsyncCallbackWebHandler * _catchAllHandler;
|
||||
AsyncServer _server;
|
||||
|
||||
public:
|
||||
// proddy
|
||||
AsyncWebServer(uint16_t port)
|
||||
: _server(port){};
|
||||
// , _rewrites(LinkedList<AsyncWebRewrite*>([](AsyncWebRewrite* r){ delete r; }))
|
||||
// , _handlers(LinkedList<AsyncWebHandler*>([](AsyncWebHandler* h){ delete h; }))
|
||||
|
||||
|
||||
~AsyncWebServer(){};
|
||||
|
||||
void begin(){};
|
||||
void end();
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void onSslFileRequest(AcSSlFileHandler cb, void * arg);
|
||||
void beginSecure(const char * cert, const char * private_key_file, const char * password);
|
||||
#endif
|
||||
|
||||
AsyncWebRewrite & addRewrite(AsyncWebRewrite * rewrite);
|
||||
bool removeRewrite(AsyncWebRewrite * rewrite);
|
||||
AsyncWebRewrite & rewrite(const char * from, const char * to);
|
||||
|
||||
AsyncWebHandler & addHandler(AsyncWebHandler * handler);
|
||||
bool removeHandler(AsyncWebHandler * handler);
|
||||
|
||||
/*
|
||||
|
||||
AsyncCallbackWebHandler & on(const char * uri, ArRequestHandlerFunction onRequest) {
|
||||
AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler();
|
||||
AsyncWebHandler & addHandler(AsyncWebHandler * handler) {
|
||||
return *handler;
|
||||
};
|
||||
AsyncCallbackWebHandler & on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) {
|
||||
AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler();
|
||||
return handler;
|
||||
};
|
||||
AsyncCallbackWebHandler & on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) {
|
||||
AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler();
|
||||
return *handler;
|
||||
};
|
||||
AsyncCallbackWebHandler &
|
||||
on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) {
|
||||
AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler();
|
||||
return *handler;
|
||||
};
|
||||
*/
|
||||
}
|
||||
|
||||
void on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){};
|
||||
|
||||
void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned
|
||||
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads
|
||||
void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request)
|
||||
|
||||
void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
|
||||
|
||||
void _handleDisconnect(AsyncWebServerRequest * request);
|
||||
void _attachHandler(AsyncWebServerRequest * request);
|
||||
void _rewriteRequest(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
// class DefaultHeaders {
|
||||
// headers_t _headers;
|
||||
|
||||
// DefaultHeaders()
|
||||
// public:
|
||||
|
||||
// void addHeader(const String& name, const String& value){
|
||||
// _headers.add(new AsyncWebHeader(name, value));
|
||||
// }
|
||||
|
||||
// DefaultHeaders(DefaultHeaders const &) = delete;
|
||||
// DefaultHeaders &operator=(DefaultHeaders const &) = delete;
|
||||
// static DefaultHeaders &Instance() {
|
||||
// static DefaultHeaders instance;
|
||||
// return instance;
|
||||
// }
|
||||
// };
|
||||
|
||||
// #include "WebResponseImpl.h"
|
||||
// #include "WebHandlerImpl.h"
|
||||
// #include "AsyncWebSocket.h"
|
||||
// #include "AsyncEventSource.h"
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant & json)> ArJsonRequestHandlerFunction;
|
||||
|
||||
#endif /* _AsyncWebServer_H_ */
|
||||
#endif
|
||||
|
||||
@@ -24,46 +24,13 @@ class FSPersistence {
|
||||
}
|
||||
|
||||
void readFromFS() {
|
||||
/*
|
||||
File settingsFile = _fs->open(_filePath, "r");
|
||||
|
||||
if (settingsFile) {
|
||||
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
|
||||
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
|
||||
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
|
||||
JsonObject jsonObject = jsonDocument.as<JsonObject>();
|
||||
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
||||
settingsFile.close();
|
||||
return;
|
||||
}
|
||||
settingsFile.close();
|
||||
}
|
||||
*/
|
||||
// If we reach here we have not been successful in loading the config,
|
||||
// hard-coded emergency defaults are now applied.
|
||||
|
||||
applyDefaults();
|
||||
}
|
||||
|
||||
bool writeToFS() {
|
||||
// create and populate a new json object
|
||||
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
|
||||
JsonObject jsonObject = jsonDocument.to<JsonObject>();
|
||||
_statefulService->read(jsonObject, _stateReader);
|
||||
|
||||
// serialize it to filesystem
|
||||
/*
|
||||
File settingsFile = _fs->open(_filePath, "w");
|
||||
|
||||
// failed to open file, return false
|
||||
if (!settingsFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// serialize the data to the file
|
||||
serializeJson(jsonDocument, settingsFile);
|
||||
settingsFile.close();
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -90,8 +57,6 @@ class FSPersistence {
|
||||
update_handler_id_t _updateHandlerId;
|
||||
|
||||
protected:
|
||||
// We assume the updater supplies sensible defaults if an empty object
|
||||
// is supplied, this virtual function allows that to be changed.
|
||||
virtual void applyDefaults() {
|
||||
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
|
||||
JsonObject jsonObject = jsonDocument.as<JsonObject>();
|
||||
|
||||
@@ -22,12 +22,6 @@ class HttpGetEndpoint {
|
||||
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
|
||||
_stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) {
|
||||
/*
|
||||
server->on(servicePath.c_str(),
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1),
|
||||
authenticationPredicate));
|
||||
*/
|
||||
}
|
||||
|
||||
HttpGetEndpoint(JsonStateReader<T> stateReader,
|
||||
@@ -36,9 +30,6 @@ class HttpGetEndpoint {
|
||||
const String& servicePath,
|
||||
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
|
||||
_stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) {
|
||||
/*
|
||||
server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1));
|
||||
*/
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -47,12 +38,6 @@ class HttpGetEndpoint {
|
||||
size_t _bufferSize;
|
||||
|
||||
void fetchSettings(AsyncWebServerRequest* request) {
|
||||
// AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
|
||||
// JsonObject jsonObject = response->getRoot().to<JsonObject>();
|
||||
// _statefulService->read(jsonObject, _stateReader);
|
||||
|
||||
// response->setLength();
|
||||
// request->send(response);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -70,17 +55,7 @@ class HttpPostEndpoint {
|
||||
_stateReader(stateReader),
|
||||
_stateUpdater(stateUpdater),
|
||||
_statefulService(statefulService),
|
||||
/*
|
||||
_updateHandler(
|
||||
servicePath,
|
||||
securityManager->wrapCallback(
|
||||
std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
|
||||
authenticationPredicate),
|
||||
bufferSize),
|
||||
*/
|
||||
_bufferSize(bufferSize) {
|
||||
//_updateHandler.setMethod(HTTP_POST);
|
||||
// server->addHandler(&_updateHandler);
|
||||
}
|
||||
|
||||
HttpPostEndpoint(JsonStateReader<T> stateReader,
|
||||
@@ -92,42 +67,26 @@ class HttpPostEndpoint {
|
||||
_stateReader(stateReader),
|
||||
_stateUpdater(stateUpdater),
|
||||
_statefulService(statefulService),
|
||||
/*
|
||||
_updateHandler(servicePath,
|
||||
std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
|
||||
bufferSize),
|
||||
*/
|
||||
_bufferSize(bufferSize) {
|
||||
// _updateHandler.setMethod(HTTP_POST);
|
||||
// server->addHandler(&_updateHandler);
|
||||
}
|
||||
|
||||
protected:
|
||||
JsonStateReader<T> _stateReader;
|
||||
JsonStateUpdater<T> _stateUpdater;
|
||||
StatefulService<T>* _statefulService;
|
||||
//AsyncCallbackJsonWebHandler _updateHandler;
|
||||
size_t _bufferSize;
|
||||
|
||||
void updateSettings(AsyncWebServerRequest* request, JsonVariant& json) {
|
||||
if (!json.is<JsonObject>()) {
|
||||
// request->send(400);
|
||||
return;
|
||||
}
|
||||
JsonObject jsonObject = json.as<JsonObject>();
|
||||
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
||||
if (outcome == StateUpdateResult::ERROR) {
|
||||
// request->send(400);
|
||||
return;
|
||||
}
|
||||
if (outcome == StateUpdateResult::CHANGED) {
|
||||
// request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
|
||||
}
|
||||
// AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
|
||||
// jsonObject = response->getRoot().to<JsonObject>();
|
||||
// _statefulService->read(jsonObject, _stateReader);
|
||||
// response->setLength();
|
||||
// request->send(response);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <Features.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
// #include <ESPUtils.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <list>
|
||||
|
||||
@@ -21,82 +20,64 @@
|
||||
#define MAX_JWT_SIZE 128
|
||||
|
||||
class User {
|
||||
public:
|
||||
String username;
|
||||
String password;
|
||||
bool admin;
|
||||
public:
|
||||
String username;
|
||||
String password;
|
||||
bool admin;
|
||||
|
||||
public:
|
||||
User(String username, String password, bool admin) : username(username), password(password), admin(admin) {
|
||||
}
|
||||
public:
|
||||
User(String username, String password, bool admin)
|
||||
: username(username)
|
||||
, password(password)
|
||||
, admin(admin) {
|
||||
}
|
||||
};
|
||||
|
||||
class Authentication {
|
||||
public:
|
||||
User* user;
|
||||
boolean authenticated;
|
||||
public:
|
||||
User * user;
|
||||
boolean authenticated;
|
||||
|
||||
public:
|
||||
Authentication(User& user) : user(new User(user)), authenticated(true) {
|
||||
}
|
||||
Authentication() : user(nullptr), authenticated(false) {
|
||||
}
|
||||
~Authentication() {
|
||||
delete (user);
|
||||
}
|
||||
public:
|
||||
Authentication(User & user)
|
||||
: user(new User(user))
|
||||
, authenticated(true) {
|
||||
}
|
||||
Authentication()
|
||||
: user(nullptr)
|
||||
, authenticated(false) {
|
||||
}
|
||||
~Authentication() {
|
||||
delete (user);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::function<boolean(Authentication& authentication)> AuthenticationPredicate;
|
||||
typedef std::function<boolean(Authentication & authentication)> AuthenticationPredicate;
|
||||
|
||||
class AuthenticationPredicates {
|
||||
public:
|
||||
static bool NONE_REQUIRED(Authentication& authentication) {
|
||||
return true;
|
||||
};
|
||||
static bool IS_AUTHENTICATED(Authentication& authentication) {
|
||||
return authentication.authenticated;
|
||||
};
|
||||
static bool IS_ADMIN(Authentication& authentication) {
|
||||
return authentication.authenticated && authentication.user->admin;
|
||||
};
|
||||
public:
|
||||
static bool NONE_REQUIRED(Authentication & authentication) {
|
||||
return true;
|
||||
};
|
||||
static bool IS_AUTHENTICATED(Authentication & authentication) {
|
||||
return authentication.authenticated;
|
||||
};
|
||||
static bool IS_ADMIN(Authentication & authentication) {
|
||||
return authentication.authenticated && authentication.user->admin;
|
||||
};
|
||||
};
|
||||
|
||||
class SecurityManager {
|
||||
public:
|
||||
public:
|
||||
#if FT_ENABLED(FT_SECURITY)
|
||||
/*
|
||||
* Authenticate, returning the user if found
|
||||
*/
|
||||
virtual Authentication authenticate(const String& username, const String& password) = 0;
|
||||
|
||||
/*
|
||||
* Generate a JWT for the user provided
|
||||
*/
|
||||
virtual String generateJWT(User* user) = 0;
|
||||
|
||||
virtual Authentication authenticate(const String & username, const String & password) = 0;
|
||||
virtual String generateJWT(User * user) = 0;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Check the request header for the Authorization token
|
||||
*/
|
||||
virtual Authentication authenticateRequest(AsyncWebServerRequest* request) = 0;
|
||||
|
||||
/**
|
||||
* Filter a request with the provided predicate, only returning true if the predicate matches.
|
||||
*/
|
||||
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
|
||||
|
||||
/**
|
||||
* Wrap the provided request to provide validation against an AuthenticationPredicate.
|
||||
*/
|
||||
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest,
|
||||
AuthenticationPredicate predicate) = 0;
|
||||
|
||||
/**
|
||||
* Wrap the provided json request callback to provide validation against an AuthenticationPredicate.
|
||||
*/
|
||||
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest,
|
||||
AuthenticationPredicate predicate) = 0;
|
||||
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
|
||||
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
|
||||
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
|
||||
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
|
||||
};
|
||||
|
||||
#endif // end SecurityManager_h
|
||||
#endif // end SecurityManager_h
|
||||
|
||||
141
lib_standalone/SecuritySettingsService.cpp
Normal file
141
lib_standalone/SecuritySettingsService.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <SecuritySettingsService.h>
|
||||
|
||||
#if FT_ENABLED(FT_SECURITY)
|
||||
|
||||
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) :
|
||||
_httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this),
|
||||
_fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE),
|
||||
_jwtHandler(FACTORY_JWT_SECRET) {
|
||||
addUpdateHandler([&](const String& originId) { configureJWTHandler(); }, false);
|
||||
}
|
||||
|
||||
void SecuritySettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
configureJWTHandler();
|
||||
}
|
||||
|
||||
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) {
|
||||
AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
|
||||
if (authorizationHeader) {
|
||||
String value = authorizationHeader->value();
|
||||
if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
|
||||
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
|
||||
return authenticateJWT(value);
|
||||
}
|
||||
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
|
||||
AsyncWebParameter* tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
|
||||
String value = tokenParamater->value();
|
||||
return authenticateJWT(value);
|
||||
}
|
||||
return Authentication();
|
||||
}
|
||||
|
||||
void SecuritySettingsService::configureJWTHandler() {
|
||||
_jwtHandler.setSecret(_state.jwtSecret);
|
||||
}
|
||||
|
||||
Authentication SecuritySettingsService::authenticateJWT(String& jwt) {
|
||||
DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
|
||||
_jwtHandler.parseJWT(jwt, payloadDocument);
|
||||
if (payloadDocument.is<JsonObject>()) {
|
||||
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
|
||||
String username = parsedPayload["username"];
|
||||
for (User _user : _state.users) {
|
||||
if (_user.username == username && validatePayload(parsedPayload, &_user)) {
|
||||
return Authentication(_user);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Authentication();
|
||||
}
|
||||
|
||||
Authentication SecuritySettingsService::authenticate(const String& username, const String& password) {
|
||||
for (User _user : _state.users) {
|
||||
if (_user.username == username && _user.password == password) {
|
||||
return Authentication(_user);
|
||||
}
|
||||
}
|
||||
return Authentication();
|
||||
}
|
||||
|
||||
inline void populateJWTPayload(JsonObject& payload, User* user) {
|
||||
payload["username"] = user->username;
|
||||
payload["admin"] = user->admin;
|
||||
payload["version"] = EMSESP_APP_VERSION; // proddy added
|
||||
}
|
||||
|
||||
boolean SecuritySettingsService::validatePayload(JsonObject& parsedPayload, User* user) {
|
||||
DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
|
||||
JsonObject payload = jsonDocument.to<JsonObject>();
|
||||
populateJWTPayload(payload, user);
|
||||
return payload == parsedPayload;
|
||||
}
|
||||
|
||||
String SecuritySettingsService::generateJWT(User* user) {
|
||||
DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
|
||||
JsonObject payload = jsonDocument.to<JsonObject>();
|
||||
populateJWTPayload(payload, user);
|
||||
return _jwtHandler.buildJWT(payload);
|
||||
}
|
||||
|
||||
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
|
||||
return [this, predicate](AsyncWebServerRequest* request) {
|
||||
Authentication authentication = authenticateRequest(request);
|
||||
return predicate(authentication);
|
||||
};
|
||||
}
|
||||
|
||||
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
|
||||
AuthenticationPredicate predicate) {
|
||||
return [this, onRequest, predicate](AsyncWebServerRequest* request) {
|
||||
Authentication authentication = authenticateRequest(request);
|
||||
if (!predicate(authentication)) {
|
||||
request->send(401);
|
||||
return;
|
||||
}
|
||||
onRequest(request);
|
||||
};
|
||||
}
|
||||
|
||||
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest,
|
||||
AuthenticationPredicate predicate) {
|
||||
return [this, onRequest, predicate](AsyncWebServerRequest* request, JsonVariant& json) {
|
||||
Authentication authentication = authenticateRequest(request);
|
||||
if (!predicate(authentication)) {
|
||||
request->send(401);
|
||||
return;
|
||||
}
|
||||
onRequest(request, json);
|
||||
};
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
|
||||
|
||||
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : SecurityManager() {
|
||||
}
|
||||
SecuritySettingsService::~SecuritySettingsService() {
|
||||
}
|
||||
|
||||
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
|
||||
return [this, predicate](AsyncWebServerRequest* request) { return true; };
|
||||
}
|
||||
|
||||
// Return the admin user on all request - disabling security features
|
||||
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) {
|
||||
return Authentication(ADMIN_USER);
|
||||
}
|
||||
|
||||
// Return the function unwrapped
|
||||
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
|
||||
AuthenticationPredicate predicate) {
|
||||
return onRequest;
|
||||
}
|
||||
|
||||
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest,
|
||||
AuthenticationPredicate predicate) {
|
||||
return onRequest;
|
||||
}
|
||||
|
||||
#endif
|
||||
108
lib_standalone/SecuritySettingsService.h
Normal file
108
lib_standalone/SecuritySettingsService.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#ifndef SecuritySettingsService_h
|
||||
#define SecuritySettingsService_h
|
||||
|
||||
#include <Features.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
|
||||
#include "../../src/version.h" // added by proddy
|
||||
|
||||
#ifndef FACTORY_ADMIN_USERNAME
|
||||
#define FACTORY_ADMIN_USERNAME "admin"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_ADMIN_PASSWORD
|
||||
#define FACTORY_ADMIN_PASSWORD "admin"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_GUEST_USERNAME
|
||||
#define FACTORY_GUEST_USERNAME "guest"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_GUEST_PASSWORD
|
||||
#define FACTORY_GUEST_PASSWORD "guest"
|
||||
#endif
|
||||
|
||||
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
||||
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
|
||||
|
||||
#if FT_ENABLED(FT_SECURITY)
|
||||
|
||||
class SecuritySettings {
|
||||
public:
|
||||
String jwtSecret;
|
||||
std::list<User> users;
|
||||
|
||||
static void read(SecuritySettings & settings, JsonObject & root) {
|
||||
// secret
|
||||
root["jwt_secret"] = settings.jwtSecret;
|
||||
|
||||
// users
|
||||
JsonArray users = root.createNestedArray("users");
|
||||
for (User user : settings.users) {
|
||||
JsonObject userRoot = users.createNestedObject();
|
||||
userRoot["username"] = user.username;
|
||||
userRoot["password"] = user.password;
|
||||
userRoot["admin"] = user.admin;
|
||||
}
|
||||
}
|
||||
|
||||
static StateUpdateResult update(JsonObject & root, SecuritySettings & settings) {
|
||||
// secret
|
||||
settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET;
|
||||
|
||||
// users
|
||||
settings.users.clear();
|
||||
if (root["users"].is<JsonArray>()) {
|
||||
for (JsonVariant user : root["users"].as<JsonArray>()) {
|
||||
settings.users.push_back(User(user["username"], user["password"], user["admin"]));
|
||||
}
|
||||
} else {
|
||||
settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true));
|
||||
settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false));
|
||||
}
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
};
|
||||
|
||||
class SecuritySettingsService : public StatefulService<SecuritySettings>, public SecurityManager {
|
||||
public:
|
||||
SecuritySettingsService(AsyncWebServer * server, FS * fs);
|
||||
|
||||
void begin();
|
||||
|
||||
// Functions to implement SecurityManager
|
||||
Authentication authenticate(const String & username, const String & password);
|
||||
Authentication authenticateRequest(AsyncWebServerRequest * request);
|
||||
String generateJWT(User * user);
|
||||
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
|
||||
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
|
||||
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate);
|
||||
|
||||
private:
|
||||
HttpEndpoint<SecuritySettings> _httpEndpoint;
|
||||
FSPersistence<SecuritySettings> _fsPersistence;
|
||||
ArduinoJsonJWT _jwtHandler;
|
||||
|
||||
void configureJWTHandler();
|
||||
Authentication authenticateJWT(String & jwt);
|
||||
boolean validatePayload(JsonObject & parsedPayload, User * user);
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class SecuritySettingsService : public SecurityManager {
|
||||
public:
|
||||
SecuritySettingsService(AsyncWebServer * server, FS * fs);
|
||||
~SecuritySettingsService();
|
||||
|
||||
// minimal set of functions to support framework with security settings disabled
|
||||
Authentication authenticateRequest(AsyncWebServerRequest * request);
|
||||
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
|
||||
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
|
||||
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
|
||||
};
|
||||
|
||||
#endif // end FT_ENABLED(FT_SECURITY)
|
||||
#endif // end SecuritySettingsService_h
|
||||
@@ -12,133 +12,137 @@
|
||||
#endif
|
||||
|
||||
enum class StateUpdateResult {
|
||||
CHANGED = 0, // The update changed the state and propagation should take place if required
|
||||
UNCHANGED, // The state was unchanged, propagation should not take place
|
||||
ERROR // There was a problem updating the state, propagation should not take place
|
||||
CHANGED = 0, // The update changed the state and propagation should take place if required
|
||||
UNCHANGED, // The state was unchanged, propagation should not take place
|
||||
ERROR // There was a problem updating the state, propagation should not take place
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject& root, T& settings)>;
|
||||
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject & root, T & settings)>;
|
||||
|
||||
template <typename T>
|
||||
using JsonStateReader = std::function<void(T& settings, JsonObject& root)>;
|
||||
using JsonStateReader = std::function<void(T & settings, JsonObject & root)>;
|
||||
|
||||
typedef size_t update_handler_id_t;
|
||||
typedef std::function<void(const String& originId)> StateUpdateCallback;
|
||||
typedef size_t update_handler_id_t;
|
||||
typedef std::function<void(const String & originId)> StateUpdateCallback;
|
||||
|
||||
typedef struct StateUpdateHandlerInfo {
|
||||
static update_handler_id_t currentUpdatedHandlerId;
|
||||
update_handler_id_t _id;
|
||||
StateUpdateCallback _cb;
|
||||
bool _allowRemove;
|
||||
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove) :
|
||||
_id(++currentUpdatedHandlerId), _cb(cb), _allowRemove(allowRemove){};
|
||||
static update_handler_id_t currentUpdatedHandlerId;
|
||||
update_handler_id_t _id;
|
||||
StateUpdateCallback _cb;
|
||||
bool _allowRemove;
|
||||
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove)
|
||||
: _id(++currentUpdatedHandlerId)
|
||||
, _cb(cb)
|
||||
, _allowRemove(allowRemove){};
|
||||
} StateUpdateHandlerInfo_t;
|
||||
|
||||
template <class T>
|
||||
class StatefulService {
|
||||
public:
|
||||
template <typename... Args>
|
||||
public:
|
||||
template <typename... Args>
|
||||
#ifdef ESP32
|
||||
StatefulService(Args&&... args) :
|
||||
_state(std::forward<Args>(args)...), _accessMutex(xSemaphoreCreateRecursiveMutex()) {
|
||||
}
|
||||
StatefulService(Args &&... args)
|
||||
: _state(std::forward<Args>(args)...)
|
||||
, _accessMutex(xSemaphoreCreateRecursiveMutex()) {
|
||||
}
|
||||
#else
|
||||
StatefulService(Args&&... args) : _state(std::forward<Args>(args)...) {
|
||||
}
|
||||
StatefulService(Args &&... args)
|
||||
: _state(std::forward<Args>(args)...) {
|
||||
}
|
||||
#endif
|
||||
|
||||
update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
|
||||
if (!cb) {
|
||||
return 0;
|
||||
update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
|
||||
if (!cb) {
|
||||
return 0;
|
||||
}
|
||||
StateUpdateHandlerInfo_t updateHandler(cb, allowRemove);
|
||||
_updateHandlers.push_back(updateHandler);
|
||||
return updateHandler._id;
|
||||
}
|
||||
StateUpdateHandlerInfo_t updateHandler(cb, allowRemove);
|
||||
_updateHandlers.push_back(updateHandler);
|
||||
return updateHandler._id;
|
||||
}
|
||||
|
||||
void removeUpdateHandler(update_handler_id_t id) {
|
||||
for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) {
|
||||
if ((*i)._allowRemove && (*i)._id == id) {
|
||||
i = _updateHandlers.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
void removeUpdateHandler(update_handler_id_t id) {
|
||||
for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) {
|
||||
if ((*i)._allowRemove && (*i)._id == id) {
|
||||
i = _updateHandlers.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateUpdateResult update(std::function<StateUpdateResult(T&)> stateUpdater, const String& originId) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(_state);
|
||||
endTransaction();
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers(originId);
|
||||
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const String & originId) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(_state);
|
||||
endTransaction();
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers(originId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T&)> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(_state);
|
||||
endTransaction();
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult update(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater, const String& originId) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||
endTransaction();
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers(originId);
|
||||
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T &)> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(_state);
|
||||
endTransaction();
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult updateWithoutPropagation(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||
endTransaction();
|
||||
return result;
|
||||
}
|
||||
|
||||
void read(std::function<void(T&)> stateReader) {
|
||||
beginTransaction();
|
||||
stateReader(_state);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void read(JsonObject& jsonObject, JsonStateReader<T> stateReader) {
|
||||
beginTransaction();
|
||||
stateReader(_state, jsonObject);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void callUpdateHandlers(const String& originId) {
|
||||
for (const StateUpdateHandlerInfo_t& updateHandler : _updateHandlers) {
|
||||
updateHandler._cb(originId);
|
||||
StateUpdateResult update(JsonObject & jsonObject, JsonStateUpdater<T> stateUpdater, const String & originId) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||
endTransaction();
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers(originId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
T _state;
|
||||
StateUpdateResult updateWithoutPropagation(JsonObject & jsonObject, JsonStateUpdater<T> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||
endTransaction();
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void beginTransaction() {
|
||||
void read(std::function<void(T &)> stateReader) {
|
||||
beginTransaction();
|
||||
stateReader(_state);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void read(JsonObject & jsonObject, JsonStateReader<T> stateReader) {
|
||||
beginTransaction();
|
||||
stateReader(_state, jsonObject);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void callUpdateHandlers(const String & originId) {
|
||||
for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) {
|
||||
updateHandler._cb(originId);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
T _state;
|
||||
|
||||
inline void beginTransaction() {
|
||||
#ifdef ESP32
|
||||
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
|
||||
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
inline void endTransaction() {
|
||||
inline void endTransaction() {
|
||||
#ifdef ESP32
|
||||
xSemaphoreGiveRecursive(_accessMutex);
|
||||
xSemaphoreGiveRecursive(_accessMutex);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
#ifdef ESP32
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
#endif
|
||||
std::list<StateUpdateHandlerInfo_t> _updateHandlers;
|
||||
std::list<StateUpdateHandlerInfo_t> _updateHandlers;
|
||||
};
|
||||
|
||||
#endif // end StatefulService_h
|
||||
#endif // end StatefulService_h
|
||||
|
||||
@@ -1,863 +1,68 @@
|
||||
/*
|
||||
WString.cpp - String library for Wiring & Arduino
|
||||
...mostly rewritten by Paul Stoffregen...
|
||||
Copyright (c) 2009-10 Hernando Barragan. All rights reserved.
|
||||
Copyright 2011, Paul Stoffregen, paul@pjrc.com
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "WString.h"
|
||||
|
||||
/*********************************************/
|
||||
/* Constructors */
|
||||
/*********************************************/
|
||||
/*
|
||||
* Copy string src to buffer dst of size dsize. At most dsize-1
|
||||
* chars will be copied. Always NUL terminates (unless dsize == 0).
|
||||
* Returns strlen(src); if retval >= dsize, truncation occurred.
|
||||
*
|
||||
* https://github.com/freebsd/freebsd/blob/master/sys/libkern/strlcpy.c
|
||||
*/
|
||||
size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize) {
|
||||
const char * osrc = src;
|
||||
size_t nleft = dsize;
|
||||
|
||||
char * itoa(signed short value, char * result, const unsigned int base) {
|
||||
// check that the base if valid
|
||||
if (base < 2 || base > 36) {
|
||||
*result = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
char * ptr = result, *ptr1 = result, tmp_char;
|
||||
signed short tmp_value;
|
||||
|
||||
do {
|
||||
tmp_value = value;
|
||||
value /= base;
|
||||
*ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)];
|
||||
} while (value);
|
||||
|
||||
// Apply negative sign
|
||||
if (tmp_value < 0) {
|
||||
*ptr++ = '-';
|
||||
}
|
||||
|
||||
*ptr-- = '\0';
|
||||
while (ptr1 < ptr) {
|
||||
tmp_char = *ptr;
|
||||
*ptr-- = *ptr1;
|
||||
*ptr1++ = tmp_char;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char* ltoa(long value, char* result, int base) {
|
||||
return itoa((int)value, result, base);
|
||||
}
|
||||
|
||||
char* ultoa(unsigned long value, char* result, int base) {
|
||||
return itoa((unsigned int)value, result, base);
|
||||
}
|
||||
|
||||
char * dtostrf(double number, signed char width, unsigned char prec, char *s) {
|
||||
bool negative = false;
|
||||
|
||||
char* out = s;
|
||||
|
||||
int fillme = width; // how many cells to fill for the integer part
|
||||
if (prec > 0) {
|
||||
fillme -= (prec+1);
|
||||
}
|
||||
|
||||
// Handle negative numbers
|
||||
if (number < 0.0) {
|
||||
negative = true;
|
||||
fillme--;
|
||||
number = -number;
|
||||
}
|
||||
|
||||
// Round correctly so that print(1.999, 2) prints as "2.00"
|
||||
// I optimized out most of the divisions
|
||||
double rounding = 2.0;
|
||||
for (unsigned int i = 0; i < prec; ++i)
|
||||
rounding *= 10.0;
|
||||
rounding = 1.0 / rounding;
|
||||
|
||||
number += rounding;
|
||||
|
||||
// Figure out how big our number really is
|
||||
double tenpow = 1.0;
|
||||
int digitcount = 1;
|
||||
double nextpow;
|
||||
while (number >= (nextpow = (10.0 * tenpow))) {
|
||||
tenpow = nextpow;
|
||||
digitcount++;
|
||||
}
|
||||
|
||||
// minimal compensation for possible lack of precision (#7087 addition)
|
||||
// number *= 1 + std::numeric_limits<decltype(number)>::epsilon();
|
||||
|
||||
number /= tenpow;
|
||||
fillme -= digitcount;
|
||||
|
||||
// Pad unused cells with spaces
|
||||
while (fillme-- > 0) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
|
||||
// Handle negative sign
|
||||
if (negative) *out++ = '-';
|
||||
|
||||
// Print the digits, and if necessary, the decimal point
|
||||
digitcount += prec;
|
||||
int8_t digit = 0;
|
||||
while (digitcount-- > 0) {
|
||||
digit = (int8_t)number;
|
||||
if (digit > 9) digit = 9; // insurance
|
||||
*out++ = (char)('0' | digit);
|
||||
if ((digitcount == prec) && (prec > 0)) {
|
||||
*out++ = '.';
|
||||
/* Copy as many bytes as will fit. */
|
||||
if (nleft != 0) {
|
||||
while (--nleft != 0) {
|
||||
if ((*dst++ = *src++) == '\0')
|
||||
break;
|
||||
}
|
||||
number -= digit;
|
||||
number *= 10.0;
|
||||
}
|
||||
|
||||
// make sure the string is terminated
|
||||
*out = 0;
|
||||
return s;
|
||||
}
|
||||
/* Not enough room in dst, add NUL and traverse rest of src. */
|
||||
if (nleft == 0) {
|
||||
if (dsize != 0)
|
||||
*dst = '\0'; /* NUL-terminate dst */
|
||||
while (*src++)
|
||||
;
|
||||
}
|
||||
|
||||
String::String(const char *cstr)
|
||||
{
|
||||
init();
|
||||
if (cstr) copy(cstr, strlen(cstr));
|
||||
}
|
||||
|
||||
String::String(const String &value)
|
||||
{
|
||||
init();
|
||||
*this = value;
|
||||
}
|
||||
|
||||
String::String(const __FlashStringHelper *pstr)
|
||||
{
|
||||
init();
|
||||
*this = pstr;
|
||||
}
|
||||
|
||||
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
|
||||
String::String(String &&rval)
|
||||
{
|
||||
init();
|
||||
move(rval);
|
||||
}
|
||||
String::String(StringSumHelper &&rval)
|
||||
{
|
||||
init();
|
||||
move(rval);
|
||||
}
|
||||
#endif
|
||||
|
||||
String::String(char c)
|
||||
{
|
||||
init();
|
||||
char buf[2];
|
||||
buf[0] = c;
|
||||
buf[1] = 0;
|
||||
*this = buf;
|
||||
}
|
||||
|
||||
String::String(unsigned char value, unsigned char base)
|
||||
{
|
||||
init();
|
||||
char buf[1 + 8 * sizeof(unsigned char)];
|
||||
itoa(value, buf, base);
|
||||
*this = buf;
|
||||
}
|
||||
|
||||
String::String(int value, unsigned char base)
|
||||
{
|
||||
init();
|
||||
char buf[2 + 8 * sizeof(int)];
|
||||
itoa(value, buf, base);
|
||||
*this = buf;
|
||||
}
|
||||
|
||||
String::String(unsigned int value, unsigned char base)
|
||||
{
|
||||
init();
|
||||
char buf[1 + 8 * sizeof(unsigned int)];
|
||||
itoa(value, buf, base);
|
||||
*this = buf;
|
||||
}
|
||||
|
||||
String::String(long value, unsigned char base)
|
||||
{
|
||||
init();
|
||||
char buf[2 + 8 * sizeof(long)];
|
||||
ltoa(value, buf, base);
|
||||
*this = buf;
|
||||
}
|
||||
|
||||
String::String(unsigned long value, unsigned char base)
|
||||
{
|
||||
init();
|
||||
char buf[1 + 8 * sizeof(unsigned long)];
|
||||
ultoa(value, buf, base);
|
||||
*this = buf;
|
||||
}
|
||||
|
||||
String::String(float value, unsigned char decimalPlaces)
|
||||
{
|
||||
init();
|
||||
char buf[33];
|
||||
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
||||
}
|
||||
|
||||
String::String(double value, unsigned char decimalPlaces)
|
||||
{
|
||||
init();
|
||||
char buf[33];
|
||||
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
||||
}
|
||||
|
||||
String::~String()
|
||||
{
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Memory Management */
|
||||
/*********************************************/
|
||||
|
||||
inline void String::init(void)
|
||||
{
|
||||
buffer = NULL;
|
||||
capacity = 0;
|
||||
len = 0;
|
||||
}
|
||||
|
||||
void String::invalidate(void)
|
||||
{
|
||||
if (buffer) free(buffer);
|
||||
buffer = NULL;
|
||||
capacity = len = 0;
|
||||
}
|
||||
|
||||
unsigned char String::reserve(unsigned int size)
|
||||
{
|
||||
if (buffer && capacity >= size) return 1;
|
||||
if (changeBuffer(size)) {
|
||||
if (len == 0) buffer[0] = 0;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned char String::changeBuffer(unsigned int maxStrLen)
|
||||
{
|
||||
char *newbuffer = (char *)realloc(buffer, maxStrLen + 1);
|
||||
if (newbuffer) {
|
||||
buffer = newbuffer;
|
||||
capacity = maxStrLen;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Copy and Move */
|
||||
/*********************************************/
|
||||
|
||||
String & String::copy(const char *cstr, unsigned int length)
|
||||
{
|
||||
if (!reserve(length)) {
|
||||
invalidate();
|
||||
return *this;
|
||||
}
|
||||
len = length;
|
||||
strcpy(buffer, cstr);
|
||||
return *this;
|
||||
return (src - osrc - 1); /* count does not include NUL */
|
||||
}
|
||||
|
||||
/*
|
||||
String & String::copy(const char *pstr, unsigned int length)
|
||||
{
|
||||
if (!reserve(length)) {
|
||||
invalidate();
|
||||
return *this;
|
||||
}
|
||||
len = length;
|
||||
strcpy_P(buffer, pstr);
|
||||
return *this;
|
||||
}
|
||||
*/
|
||||
|
||||
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
|
||||
void String::move(String &rhs)
|
||||
{
|
||||
if (buffer) {
|
||||
if (rhs && capacity >= rhs.len) {
|
||||
strcpy(buffer, rhs.buffer);
|
||||
len = rhs.len;
|
||||
rhs.len = 0;
|
||||
return;
|
||||
} else {
|
||||
free(buffer);
|
||||
}
|
||||
}
|
||||
buffer = rhs.buffer;
|
||||
capacity = rhs.capacity;
|
||||
len = rhs.len;
|
||||
rhs.buffer = NULL;
|
||||
rhs.capacity = 0;
|
||||
rhs.len = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
String & String::operator = (const String &rhs)
|
||||
{
|
||||
if (this == &rhs) return *this;
|
||||
|
||||
if (rhs.buffer) copy(rhs.buffer, rhs.len);
|
||||
else invalidate();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
|
||||
String & String::operator = (String &&rval)
|
||||
{
|
||||
if (this != &rval) move(rval);
|
||||
return *this;
|
||||
}
|
||||
|
||||
String & String::operator = (StringSumHelper &&rval)
|
||||
{
|
||||
if (this != &rval) move(rval);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
String & String::operator = (const char *cstr)
|
||||
{
|
||||
if (cstr) copy(cstr, strlen(cstr));
|
||||
else invalidate();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
String & String::operator = (const __FlashStringHelper *pstr)
|
||||
{
|
||||
// if (pstr) copy(pstr, strlen_P((PGM_P)pstr));
|
||||
// else invalidate();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* concat */
|
||||
/*********************************************/
|
||||
|
||||
unsigned char String::concat(const String &s)
|
||||
{
|
||||
return concat(s.buffer, s.len);
|
||||
}
|
||||
|
||||
unsigned char String::concat(const char *cstr, unsigned int length)
|
||||
{
|
||||
unsigned int newlen = len + length;
|
||||
if (!cstr) return 0;
|
||||
if (length == 0) return 1;
|
||||
if (!reserve(newlen)) return 0;
|
||||
strcpy(buffer + len, cstr);
|
||||
len = newlen;
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned char String::concat(const char *cstr)
|
||||
{
|
||||
if (!cstr) return 0;
|
||||
return concat(cstr, strlen(cstr));
|
||||
}
|
||||
|
||||
unsigned char String::concat(char c)
|
||||
{
|
||||
char buf[2];
|
||||
buf[0] = c;
|
||||
buf[1] = 0;
|
||||
return concat(buf, 1);
|
||||
}
|
||||
|
||||
unsigned char String::concat(unsigned char num)
|
||||
{
|
||||
char buf[1 + 3 * sizeof(unsigned char)];
|
||||
itoa(num, buf, 10);
|
||||
return concat(buf, strlen(buf));
|
||||
}
|
||||
|
||||
unsigned char String::concat(int num)
|
||||
{
|
||||
char buf[2 + 3 * sizeof(int)];
|
||||
itoa(num, buf, 10);
|
||||
return concat(buf, strlen(buf));
|
||||
}
|
||||
|
||||
unsigned char String::concat(unsigned int num)
|
||||
{
|
||||
char buf[1 + 3 * sizeof(unsigned int)];
|
||||
itoa(num, buf, 10);
|
||||
return concat(buf, strlen(buf));
|
||||
}
|
||||
|
||||
unsigned char String::concat(long num)
|
||||
{
|
||||
char buf[2 + 3 * sizeof(long)];
|
||||
ltoa(num, buf, 10);
|
||||
return concat(buf, strlen(buf));
|
||||
}
|
||||
|
||||
unsigned char String::concat(unsigned long num)
|
||||
{
|
||||
char buf[1 + 3 * sizeof(unsigned long)];
|
||||
ultoa(num, buf, 10);
|
||||
return concat(buf, strlen(buf));
|
||||
}
|
||||
|
||||
unsigned char String::concat(float num)
|
||||
{
|
||||
char buf[20];
|
||||
char* string = dtostrf(num, 4, 2, buf);
|
||||
return concat(string, strlen(string));
|
||||
}
|
||||
|
||||
unsigned char String::concat(double num)
|
||||
{
|
||||
char buf[20];
|
||||
char* string = dtostrf(num, 4, 2, buf);
|
||||
return concat(string, strlen(string));
|
||||
}
|
||||
|
||||
unsigned char String::concat(const __FlashStringHelper * str)
|
||||
{
|
||||
if (!str) return 0;
|
||||
int length = strlen_P((const char *) str);
|
||||
if (length == 0) return 1;
|
||||
unsigned int newlen = len + length;
|
||||
if (!reserve(newlen)) return 0;
|
||||
strcpy_P(buffer + len, (const char *) str);
|
||||
len = newlen;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Concatenate */
|
||||
/*********************************************/
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(rhs.buffer, rhs.len)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!cstr || !a.concat(cstr, strlen(cstr))) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, char c)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(c)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(num)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, int num)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(num)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(num)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, long num)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(num)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(num)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, float num)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(num)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, double num)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(num)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
if (!a.concat(rhs)) a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Comparison */
|
||||
/*********************************************/
|
||||
|
||||
int String::compareTo(const String &s) const
|
||||
{
|
||||
if (!buffer || !s.buffer) {
|
||||
if (s.buffer && s.len > 0) return 0 - *(unsigned char *)s.buffer;
|
||||
if (buffer && len > 0) return *(unsigned char *)buffer;
|
||||
return 0;
|
||||
}
|
||||
return strcmp(buffer, s.buffer);
|
||||
}
|
||||
|
||||
unsigned char String::isEmpty() const {
|
||||
return (len == 0);
|
||||
}
|
||||
|
||||
unsigned char String::equals(const String &s2) const
|
||||
{
|
||||
return (len == s2.len && compareTo(s2) == 0);
|
||||
}
|
||||
|
||||
unsigned char String::equals(const char *cstr) const
|
||||
{
|
||||
if (len == 0) return (cstr == NULL || *cstr == 0);
|
||||
if (cstr == NULL) return buffer[0] == 0;
|
||||
return strcmp(buffer, cstr) == 0;
|
||||
}
|
||||
|
||||
unsigned char String::operator<(const String &rhs) const
|
||||
{
|
||||
return compareTo(rhs) < 0;
|
||||
}
|
||||
|
||||
unsigned char String::operator>(const String &rhs) const
|
||||
{
|
||||
return compareTo(rhs) > 0;
|
||||
}
|
||||
|
||||
unsigned char String::operator<=(const String &rhs) const
|
||||
{
|
||||
return compareTo(rhs) <= 0;
|
||||
}
|
||||
|
||||
unsigned char String::operator>=(const String &rhs) const
|
||||
{
|
||||
return compareTo(rhs) >= 0;
|
||||
}
|
||||
|
||||
unsigned char String::equalsIgnoreCase( const String &s2 ) const
|
||||
{
|
||||
if (this == &s2) return 1;
|
||||
if (len != s2.len) return 0;
|
||||
if (len == 0) return 1;
|
||||
const char *p1 = buffer;
|
||||
const char *p2 = s2.buffer;
|
||||
while (*p1) {
|
||||
if (tolower(*p1++) != tolower(*p2++)) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned char String::startsWith( const String &s2 ) const
|
||||
{
|
||||
if (len < s2.len) return 0;
|
||||
return startsWith(s2, 0);
|
||||
}
|
||||
|
||||
unsigned char String::startsWith( const String &s2, unsigned int offset ) const
|
||||
{
|
||||
if (offset > len - s2.len || !buffer || !s2.buffer) return 0;
|
||||
return strncmp( &buffer[offset], s2.buffer, s2.len ) == 0;
|
||||
}
|
||||
|
||||
unsigned char String::endsWith( const String &s2 ) const
|
||||
{
|
||||
if ( len < s2.len || !buffer || !s2.buffer) return 0;
|
||||
return strcmp(&buffer[len - s2.len], s2.buffer) == 0;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Character Access */
|
||||
/*********************************************/
|
||||
|
||||
char String::charAt(unsigned int loc) const
|
||||
{
|
||||
return operator[](loc);
|
||||
}
|
||||
|
||||
void String::setCharAt(unsigned int loc, char c)
|
||||
{
|
||||
if (loc < len) buffer[loc] = c;
|
||||
}
|
||||
|
||||
char & String::operator[](unsigned int index)
|
||||
{
|
||||
static char dummy_writable_char;
|
||||
if (index >= len || !buffer) {
|
||||
dummy_writable_char = 0;
|
||||
return dummy_writable_char;
|
||||
}
|
||||
return buffer[index];
|
||||
}
|
||||
|
||||
char String::operator[]( unsigned int index ) const
|
||||
{
|
||||
if (index >= len || !buffer) return 0;
|
||||
return buffer[index];
|
||||
}
|
||||
|
||||
void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const
|
||||
{
|
||||
if (!bufsize || !buf) return;
|
||||
if (index >= len) {
|
||||
buf[0] = 0;
|
||||
return;
|
||||
}
|
||||
unsigned int n = bufsize - 1;
|
||||
if (n > len - index) n = len - index;
|
||||
strncpy((char *)buf, buffer + index, n);
|
||||
buf[n] = 0;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Search */
|
||||
/*********************************************/
|
||||
|
||||
int String::indexOf(char c) const
|
||||
{
|
||||
return indexOf(c, 0);
|
||||
}
|
||||
|
||||
int String::indexOf( char ch, unsigned int fromIndex ) const
|
||||
{
|
||||
if (fromIndex >= len) return -1;
|
||||
const char* temp = strchr(buffer + fromIndex, ch);
|
||||
if (temp == NULL) return -1;
|
||||
return temp - buffer;
|
||||
}
|
||||
|
||||
int String::indexOf(const String &s2) const
|
||||
{
|
||||
return indexOf(s2, 0);
|
||||
}
|
||||
|
||||
int String::indexOf(const String &s2, unsigned int fromIndex) const
|
||||
{
|
||||
if (fromIndex >= len) return -1;
|
||||
const char *found = strstr(buffer + fromIndex, s2.buffer);
|
||||
if (found == NULL) return -1;
|
||||
return found - buffer;
|
||||
}
|
||||
|
||||
int String::lastIndexOf( char theChar ) const
|
||||
{
|
||||
return lastIndexOf(theChar, len - 1);
|
||||
}
|
||||
|
||||
int String::lastIndexOf(char ch, unsigned int fromIndex) const
|
||||
{
|
||||
if (fromIndex >= len) return -1;
|
||||
char tempchar = buffer[fromIndex + 1];
|
||||
buffer[fromIndex + 1] = '\0';
|
||||
char* temp = strrchr( buffer, ch );
|
||||
buffer[fromIndex + 1] = tempchar;
|
||||
if (temp == NULL) return -1;
|
||||
return temp - buffer;
|
||||
}
|
||||
|
||||
int String::lastIndexOf(const String &s2) const
|
||||
{
|
||||
return lastIndexOf(s2, len - s2.len);
|
||||
}
|
||||
|
||||
int String::lastIndexOf(const String &s2, unsigned int fromIndex) const
|
||||
{
|
||||
if (s2.len == 0 || len == 0 || s2.len > len) return -1;
|
||||
if (fromIndex >= len) fromIndex = len - 1;
|
||||
int found = -1;
|
||||
for (char *p = buffer; p <= buffer + fromIndex; p++) {
|
||||
p = strstr(p, s2.buffer);
|
||||
if (!p) break;
|
||||
if ((unsigned int)(p - buffer) <= fromIndex) found = p - buffer;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
String String::substring(unsigned int left, unsigned int right) const
|
||||
{
|
||||
if (left > right) {
|
||||
unsigned int temp = right;
|
||||
right = left;
|
||||
left = temp;
|
||||
}
|
||||
String out;
|
||||
if (left >= len) return out;
|
||||
if (right > len) right = len;
|
||||
char temp = buffer[right]; // save the replaced character
|
||||
buffer[right] = '\0';
|
||||
out = buffer + left; // pointer arithmetic
|
||||
buffer[right] = temp; //restore character
|
||||
return out;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Modification */
|
||||
/*********************************************/
|
||||
|
||||
void String::replace(char find, char replace)
|
||||
{
|
||||
if (!buffer) return;
|
||||
for (char *p = buffer; *p; p++) {
|
||||
if (*p == find) *p = replace;
|
||||
}
|
||||
}
|
||||
|
||||
void String::replace(const String& find, const String& replace)
|
||||
{
|
||||
if (len == 0 || find.len == 0) return;
|
||||
int diff = replace.len - find.len;
|
||||
char *readFrom = buffer;
|
||||
char *foundAt;
|
||||
if (diff == 0) {
|
||||
while ((foundAt = strstr(readFrom, find.buffer)) != NULL) {
|
||||
memcpy(foundAt, replace.buffer, replace.len);
|
||||
readFrom = foundAt + replace.len;
|
||||
}
|
||||
} else if (diff < 0) {
|
||||
char *writeTo = buffer;
|
||||
while ((foundAt = strstr(readFrom, find.buffer)) != NULL) {
|
||||
unsigned int n = foundAt - readFrom;
|
||||
memcpy(writeTo, readFrom, n);
|
||||
writeTo += n;
|
||||
memcpy(writeTo, replace.buffer, replace.len);
|
||||
writeTo += replace.len;
|
||||
readFrom = foundAt + find.len;
|
||||
len += diff;
|
||||
}
|
||||
strcpy(writeTo, readFrom);
|
||||
} else {
|
||||
unsigned int size = len; // compute size needed for result
|
||||
while ((foundAt = strstr(readFrom, find.buffer)) != NULL) {
|
||||
readFrom = foundAt + find.len;
|
||||
size += diff;
|
||||
}
|
||||
if (size == len) return;
|
||||
if (size > capacity && !changeBuffer(size)) return;
|
||||
int index = len - 1;
|
||||
while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) {
|
||||
readFrom = buffer + index + find.len;
|
||||
memmove(readFrom + diff, readFrom, len - (readFrom - buffer));
|
||||
len += diff;
|
||||
buffer[len] = 0;
|
||||
memcpy(buffer + index, replace.buffer, replace.len);
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void String::remove(unsigned int index){
|
||||
// Pass the biggest integer as the count. The remove method
|
||||
// below will take care of truncating it at the end of the
|
||||
// string.
|
||||
remove(index, (unsigned int)-1);
|
||||
}
|
||||
|
||||
void String::remove(unsigned int index, unsigned int count){
|
||||
if (index >= len) { return; }
|
||||
if (count <= 0) { return; }
|
||||
if (count > len - index) { count = len - index; }
|
||||
char *writeTo = buffer + index;
|
||||
len = len - count;
|
||||
strncpy(writeTo, buffer + index + count,len - index);
|
||||
buffer[len] = 0;
|
||||
}
|
||||
|
||||
void String::toLowerCase(void)
|
||||
{
|
||||
if (!buffer) return;
|
||||
for (char *p = buffer; *p; p++) {
|
||||
*p = tolower(*p);
|
||||
}
|
||||
}
|
||||
|
||||
void String::toUpperCase(void)
|
||||
{
|
||||
if (!buffer) return;
|
||||
for (char *p = buffer; *p; p++) {
|
||||
*p = toupper(*p);
|
||||
}
|
||||
}
|
||||
|
||||
void String::trim(void)
|
||||
{
|
||||
if (!buffer || len == 0) return;
|
||||
char *begin = buffer;
|
||||
while (isspace(*begin)) begin++;
|
||||
char *end = buffer + len - 1;
|
||||
while (isspace(*end) && end >= begin) end--;
|
||||
len = end + 1 - begin;
|
||||
if (begin > buffer) memcpy(buffer, begin, len);
|
||||
buffer[len] = 0;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Parsing / Conversion */
|
||||
/*********************************************/
|
||||
|
||||
long String::toInt(void) const
|
||||
{
|
||||
if (buffer) return atol(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
float String::toFloat(void) const
|
||||
{
|
||||
return float(toDouble());
|
||||
}
|
||||
|
||||
double String::toDouble(void) const
|
||||
{
|
||||
if (buffer) return atof(buffer);
|
||||
return 0;
|
||||
* Appends src to string dst of size siz (unlike strncat, siz is the
|
||||
* full size of dst, not space left). At most siz-1 characters
|
||||
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
|
||||
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
|
||||
* If retval >= siz, truncation occurred.
|
||||
*
|
||||
* https://github.com/freebsd/freebsd/blob/master/sys/libkern/strlcat.c
|
||||
*/
|
||||
size_t strlcat(char * dst, const char * src, size_t siz) {
|
||||
char * d = dst;
|
||||
const char * s = src;
|
||||
size_t n = siz;
|
||||
size_t dlen;
|
||||
|
||||
/* Find the end of dst and adjust bytes left but don't go past end */
|
||||
while (n-- != 0 && *d != '\0')
|
||||
d++;
|
||||
dlen = d - dst;
|
||||
n = siz - dlen;
|
||||
|
||||
if (n == 0)
|
||||
return (dlen + strlen(s));
|
||||
while (*s != '\0') {
|
||||
if (n != 1) {
|
||||
*d++ = *s;
|
||||
n--;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
*d = '\0';
|
||||
|
||||
return (dlen + (s - src)); /* count does not include NUL */
|
||||
}
|
||||
@@ -1,236 +1,69 @@
|
||||
/*
|
||||
WString.h - String library for Wiring & Arduino
|
||||
...mostly rewritten by Paul Stoffregen...
|
||||
Copyright (c) 2009-10 Hernando Barragan. All right reserved.
|
||||
Copyright 2011, Paul Stoffregen, paul@pjrc.com
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
#ifndef WSTRING_H
|
||||
#define WSTRING_H
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
#include <string>
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
// Reproduces Arduino's String class
|
||||
class String {
|
||||
public:
|
||||
String & operator+=(const char * rhs) {
|
||||
_str += rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifndef String_class_h
|
||||
#define String_class_h
|
||||
size_t length() const {
|
||||
return _str.size();
|
||||
}
|
||||
|
||||
String(const char * str = "")
|
||||
: _str(str) {
|
||||
}
|
||||
|
||||
const char * c_str() const {
|
||||
return _str.c_str();
|
||||
}
|
||||
|
||||
bool operator==(const char * s) const {
|
||||
return _str == s;
|
||||
}
|
||||
|
||||
friend std::ostream & operator<<(std::ostream & lhs, const ::String & rhs) {
|
||||
lhs << rhs._str;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
///
|
||||
bool isEmpty() {
|
||||
return _str.empty();
|
||||
}
|
||||
|
||||
// long toInt() const {
|
||||
// return std::stol(_str);
|
||||
// }
|
||||
|
||||
bool equals(const char * s) {
|
||||
return _str == s;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
std::string _str;
|
||||
};
|
||||
|
||||
class StringSumHelper;
|
||||
|
||||
inline bool operator==(const std::string & lhs, const ::String & rhs) {
|
||||
return lhs == rhs.c_str();
|
||||
}
|
||||
|
||||
|
||||
size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize);
|
||||
size_t strlcat(char * dst, const char * src, size_t siz);
|
||||
|
||||
#define strlen_P strlen
|
||||
#define strncpy_P strncpy
|
||||
#define strcmp_P strcmp
|
||||
#define strcpy_P strcpy
|
||||
|
||||
// #include "pgmspace.h"
|
||||
// #include "noniso.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
// When compiling programs with this class, the following gcc parameters
|
||||
// dramatically increase performance and memory (RAM) efficiency, typically
|
||||
// with little or no increase in code size.
|
||||
// -felide-constructors
|
||||
// -std=c++0x
|
||||
|
||||
class __FlashStringHelper;
|
||||
// #define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
|
||||
|
||||
// An inherited class for holding the result of a concatenation. These
|
||||
// result objects are assumed to be writable by subsequent concatenations.
|
||||
class StringSumHelper;
|
||||
|
||||
// The string class
|
||||
class String
|
||||
{
|
||||
// use a function pointer to allow for "if (s)" without the
|
||||
// complications of an operator bool(). for more information, see:
|
||||
// http://www.artima.com/cppsource/safebool.html
|
||||
typedef void (String::*StringIfHelperType)() const;
|
||||
void StringIfHelper() const {}
|
||||
|
||||
public:
|
||||
// constructors
|
||||
// creates a copy of the initial value.
|
||||
// if the initial value is null or invalid, or if memory allocation
|
||||
// fails, the string will be marked as invalid (i.e. "if (s)" will
|
||||
// be false).
|
||||
String(const char *cstr = "");
|
||||
String(const String &str);
|
||||
String(const __FlashStringHelper *str);
|
||||
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
|
||||
String(String &&rval);
|
||||
String(StringSumHelper &&rval);
|
||||
#endif
|
||||
explicit String(char c);
|
||||
explicit String(unsigned char, unsigned char base=10);
|
||||
explicit String(int, unsigned char base=10);
|
||||
explicit String(unsigned int, unsigned char base=10);
|
||||
explicit String(long, unsigned char base=10);
|
||||
explicit String(unsigned long, unsigned char base=10);
|
||||
explicit String(float, unsigned char decimalPlaces=2);
|
||||
explicit String(double, unsigned char decimalPlaces=2);
|
||||
~String(void);
|
||||
|
||||
// memory management
|
||||
// return true on success, false on failure (in which case, the string
|
||||
// is left unchanged). reserve(0), if successful, will validate an
|
||||
// invalid string (i.e., "if (s)" will be true afterwards)
|
||||
unsigned char reserve(unsigned int size);
|
||||
inline unsigned int length(void) const {return len;}
|
||||
|
||||
// creates a copy of the assigned value. if the value is null or
|
||||
// invalid, or if the memory allocation fails, the string will be
|
||||
// marked as invalid ("if (s)" will be false).
|
||||
String & operator = (const String &rhs);
|
||||
String & operator = (const char *cstr);
|
||||
String & operator = (const __FlashStringHelper *str);
|
||||
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
|
||||
String & operator = (String &&rval);
|
||||
String & operator = (StringSumHelper &&rval);
|
||||
#endif
|
||||
|
||||
// concatenate (works w/ built-in types)
|
||||
|
||||
// returns true on success, false on failure (in which case, the string
|
||||
// is left unchanged). if the argument is null or invalid, the
|
||||
// concatenation is considered unsucessful.
|
||||
unsigned char concat(const String &str);
|
||||
unsigned char concat(const char *cstr);
|
||||
unsigned char concat(char c);
|
||||
unsigned char concat(unsigned char c);
|
||||
unsigned char concat(int num);
|
||||
unsigned char concat(unsigned int num);
|
||||
unsigned char concat(long num);
|
||||
unsigned char concat(unsigned long num);
|
||||
unsigned char concat(float num);
|
||||
unsigned char concat(double num);
|
||||
unsigned char concat(const __FlashStringHelper * str);
|
||||
|
||||
// if there's not enough memory for the concatenated value, the string
|
||||
// will be left unchanged (but this isn't signalled in any way)
|
||||
String & operator += (const String &rhs) {concat(rhs); return (*this);}
|
||||
String & operator += (const char *cstr) {concat(cstr); return (*this);}
|
||||
String & operator += (char c) {concat(c); return (*this);}
|
||||
String & operator += (unsigned char num) {concat(num); return (*this);}
|
||||
String & operator += (int num) {concat(num); return (*this);}
|
||||
String & operator += (unsigned int num) {concat(num); return (*this);}
|
||||
String & operator += (long num) {concat(num); return (*this);}
|
||||
String & operator += (unsigned long num) {concat(num); return (*this);}
|
||||
String & operator += (float num) {concat(num); return (*this);}
|
||||
String & operator += (double num) {concat(num); return (*this);}
|
||||
String & operator += (const __FlashStringHelper *str){concat(str); return (*this);}
|
||||
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, char c);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, int num);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, long num);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, float num);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, double num);
|
||||
friend StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs);
|
||||
|
||||
// comparison (only works w/ Strings and "strings")
|
||||
operator StringIfHelperType() const { return buffer ? &String::StringIfHelper : 0; }
|
||||
int compareTo(const String &s) const;
|
||||
unsigned char equals(const String &s) const;
|
||||
unsigned char equals(const char *cstr) const;
|
||||
unsigned char operator == (const String &rhs) const {return equals(rhs);}
|
||||
unsigned char operator == (const char *cstr) const {return equals(cstr);}
|
||||
unsigned char operator != (const String &rhs) const {return !equals(rhs);}
|
||||
unsigned char operator != (const char *cstr) const {return !equals(cstr);}
|
||||
unsigned char operator < (const String &rhs) const;
|
||||
unsigned char operator > (const String &rhs) const;
|
||||
unsigned char operator <= (const String &rhs) const;
|
||||
unsigned char operator >= (const String &rhs) const;
|
||||
unsigned char equalsIgnoreCase(const String &s) const;
|
||||
unsigned char startsWith( const String &prefix) const;
|
||||
unsigned char startsWith(const String &prefix, unsigned int offset) const;
|
||||
unsigned char endsWith(const String &suffix) const;
|
||||
|
||||
// character acccess
|
||||
char charAt(unsigned int index) const;
|
||||
void setCharAt(unsigned int index, char c);
|
||||
char operator [] (unsigned int index) const;
|
||||
char& operator [] (unsigned int index);
|
||||
void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index=0) const;
|
||||
void toCharArray(char *buf, unsigned int bufsize, unsigned int index=0) const
|
||||
{ getBytes((unsigned char *)buf, bufsize, index); }
|
||||
const char* c_str() const { return buffer; }
|
||||
char* begin() { return buffer; }
|
||||
char* end() { return buffer + length(); }
|
||||
const char* begin() const { return c_str(); }
|
||||
const char* end() const { return c_str() + length(); }
|
||||
|
||||
// search
|
||||
int indexOf( char ch ) const;
|
||||
int indexOf( char ch, unsigned int fromIndex ) const;
|
||||
int indexOf( const String &str ) const;
|
||||
int indexOf( const String &str, unsigned int fromIndex ) const;
|
||||
int lastIndexOf( char ch ) const;
|
||||
int lastIndexOf( char ch, unsigned int fromIndex ) const;
|
||||
int lastIndexOf( const String &str ) const;
|
||||
int lastIndexOf( const String &str, unsigned int fromIndex ) const;
|
||||
String substring( unsigned int beginIndex ) const { return substring(beginIndex, len); };
|
||||
String substring( unsigned int beginIndex, unsigned int endIndex ) const;
|
||||
|
||||
// modification
|
||||
void replace(char find, char replace);
|
||||
void replace(const String& find, const String& replace);
|
||||
void remove(unsigned int index);
|
||||
void remove(unsigned int index, unsigned int count);
|
||||
void toLowerCase(void);
|
||||
void toUpperCase(void);
|
||||
void trim(void);
|
||||
|
||||
// parsing/conversion
|
||||
long toInt(void) const;
|
||||
float toFloat(void) const;
|
||||
double toDouble(void) const;
|
||||
|
||||
unsigned char isEmpty() const;
|
||||
|
||||
protected:
|
||||
char *buffer; // the actual char array
|
||||
unsigned int capacity; // the array length minus one (for the '\0')
|
||||
unsigned int len; // the String length (not counting the '\0')
|
||||
protected:
|
||||
void init(void);
|
||||
void invalidate(void);
|
||||
unsigned char changeBuffer(unsigned int maxStrLen);
|
||||
unsigned char concat(const char *cstr, unsigned int length);
|
||||
|
||||
// copy and move
|
||||
String & copy(const char *cstr, unsigned int length);
|
||||
String & copy(const __FlashStringHelper *pstr, unsigned int length);
|
||||
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
|
||||
void move(String &rhs);
|
||||
#endif
|
||||
};
|
||||
|
||||
class StringSumHelper : public String
|
||||
{
|
||||
public:
|
||||
StringSumHelper(const String &s) : String(s) {}
|
||||
StringSumHelper(const char *p) : String(p) {}
|
||||
StringSumHelper(char c) : String(c) {}
|
||||
StringSumHelper(unsigned char num) : String(num) {}
|
||||
StringSumHelper(int num) : String(num) {}
|
||||
StringSumHelper(unsigned int num) : String(num) {}
|
||||
StringSumHelper(long num) : String(num) {}
|
||||
StringSumHelper(unsigned long num) : String(num) {}
|
||||
StringSumHelper(float num) : String(num) {}
|
||||
StringSumHelper(double num) : String(num) {}
|
||||
};
|
||||
|
||||
#endif // String_class_h
|
||||
#endif
|
||||
|
||||
6
makefile
6
makefile
@@ -65,9 +65,9 @@ CPPFLAGS += -g3
|
||||
CPPFLAGS += -Os
|
||||
|
||||
CFLAGS += $(CPPFLAGS)
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += -Wno-unused -Wno-restrict
|
||||
CFLAGS += -Wextra
|
||||
# CFLAGS += -Wall
|
||||
# CFLAGS += -Wno-unused -Wno-restrict
|
||||
# CFLAGS += -Wextra
|
||||
|
||||
CXXFLAGS += $(CFLAGS) -MMD
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[ZoneTransfer]
|
||||
LastWriterPackageFamilyName=Microsoft.MSPaint_8wekyb3d8bbwe
|
||||
ZoneId=3
|
||||
@@ -1,3 +0,0 @@
|
||||
[ZoneTransfer]
|
||||
LastWriterPackageFamilyName=Microsoft.MSPaint_8wekyb3d8bbwe
|
||||
ZoneId=3
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 58 KiB |
@@ -1,13 +1,12 @@
|
||||
; PlatformIO Project Configuration File for EMS-ESP
|
||||
|
||||
[platformio]
|
||||
default_envs = esp8266
|
||||
; default_envs = esp32
|
||||
; default_envs = esp8266
|
||||
default_envs = esp32
|
||||
|
||||
# override any settings with your own local ones in pio_local.ini
|
||||
extra_configs =
|
||||
factory_settings.ini
|
||||
features.ini
|
||||
pio_local.ini
|
||||
|
||||
[common]
|
||||
@@ -20,7 +19,11 @@ debug_flags =
|
||||
; default platformio compile flags are: -fno-rtti -std=c++11 -Os -mlongcalls -mtext-section-literals -falign-functions=4 -ffunction-sections -fdata-sections -fno-exceptions -Wall
|
||||
build_flags =
|
||||
${factory_settings.build_flags}
|
||||
${features.build_flags}
|
||||
-D FT_PROJECT=1
|
||||
-D FT_SECURITY=1
|
||||
-D FT_MQTT=1
|
||||
-D FT_OTA=1
|
||||
-D FT_UPLOAD_FIRMWARE=1
|
||||
-D ONEWIRE_CRC16=0
|
||||
-D NO_GLOBAL_ARDUINOOTA
|
||||
-D ARDUINOJSON_ENABLE_STD_STRING=1
|
||||
@@ -53,8 +56,8 @@ check_flags =
|
||||
|
||||
; USB upload
|
||||
; upload_protocol = esptool
|
||||
; upload_port = COM6
|
||||
; upload_port = /dev/cu.wchusbserial1420
|
||||
; upload_port = COM4
|
||||
; upload_port = /dev/cu.wchusbserial1410
|
||||
|
||||
; OTA upload
|
||||
upload_protocol = espota
|
||||
@@ -73,14 +76,15 @@ board_build.f_cpu = 160000000L ; 160MHz
|
||||
; eagle.flash.4m1m.ld = 1019 KB sketch, 1000 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 2052 KB OTA & buffer
|
||||
; eagle.flash.4m2m.ld = 1019 KB sketch, 2024 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 1028 KB OTA & buffer
|
||||
; board_build.ldscript = eagle.flash.4m2m.ld
|
||||
build_flags = ${common.build_flags} ${common.debug_flags}
|
||||
build_flags = ${common.build_flags} ${common.debug_flags} -DFT_NTP=0
|
||||
lib_ignore =
|
||||
AsyncTCP
|
||||
|
||||
[env:esp32]
|
||||
board = esp32dev
|
||||
build_type = release
|
||||
platform = espressif32
|
||||
; platform = espressif32
|
||||
platform = https://github.com/platformio/platform-espressif32.git
|
||||
board_build.partitions = min_spiffs.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/
|
||||
lib_deps = ${common.libs_core}
|
||||
build_flags = ${common.build_flags} ${common.debug_flags}
|
||||
build_flags = ${common.build_flags} ${common.debug_flags} -DFT_NTP=1
|
||||
|
||||
@@ -1,35 +1,93 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "EMSESPDevicesService.h"
|
||||
#include "emsesp.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH,
|
||||
securityManager->wrapCallback(std::bind(&EMSESPDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) {
|
||||
server->on(EMSESP_DEVICES_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&EMSESPDevicesService::emsespDevicesService, this, std::placeholders::_1),
|
||||
AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
securityManager->wrapRequest(std::bind(&EMSESPDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
server->on(SCAN_DEVICES_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&EMSESPDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
_device_dataHandler.setMethod(HTTP_POST);
|
||||
_device_dataHandler.setMaxContentLength(256);
|
||||
server->addHandler(&_device_dataHandler);
|
||||
}
|
||||
|
||||
void EMSESPDevicesService::emsespDevicesService(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_STATUS_SIZE);
|
||||
void EMSESPDevicesService::scan_devices(AsyncWebServerRequest * request) {
|
||||
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
void EMSESPDevicesService::all_devices(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
JsonArray devices = root.createNestedArray("devices");
|
||||
|
||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||
if (emsdevice) {
|
||||
JsonObject deviceRoot = devices.createNestedObject();
|
||||
deviceRoot["type"] = emsdevice->device_type_name();
|
||||
deviceRoot["brand"] = emsdevice->brand_to_string();
|
||||
deviceRoot["name"] = emsdevice->name();
|
||||
deviceRoot["deviceid"] = emsdevice->device_id();
|
||||
deviceRoot["productid"] = emsdevice->product_id();
|
||||
deviceRoot["version"] = emsdevice->version();
|
||||
JsonObject obj = devices.createNestedObject();
|
||||
obj["id"] = emsdevice->unique_id();
|
||||
obj["type"] = emsdevice->device_type_name();
|
||||
obj["brand"] = emsdevice->brand_to_string();
|
||||
obj["name"] = emsdevice->name();
|
||||
obj["deviceid"] = emsdevice->device_id();
|
||||
obj["productid"] = emsdevice->product_id();
|
||||
obj["version"] = emsdevice->version();
|
||||
}
|
||||
}
|
||||
|
||||
JsonArray sensors = root.createNestedArray("sensors");
|
||||
if (!EMSESP::sensor_devices().empty()) {
|
||||
for (const auto & sensor : EMSESP::sensor_devices()) {
|
||||
JsonObject obj = sensors.createNestedObject();
|
||||
obj["id"] = sensor.to_string();
|
||||
obj["temp"] = sensor.temperature_c;
|
||||
}
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void EMSESPDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
uint8_t id = json["id"]; // get id from selected table row
|
||||
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
EMSESP::device_info(id, (JsonObject &)response->getRoot());
|
||||
#endif
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
} else {
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESPDevicesService_h
|
||||
#define EMSESPDevicesService_h
|
||||
|
||||
@@ -6,23 +24,27 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
// #include <HttpEndpoint.h>
|
||||
// #include <MqttPubSub.h>
|
||||
// #include <WebSocketTxRx.h>
|
||||
// #define MAX_EMSESP_STATUS_SIZE 1024
|
||||
#define MAX_EMSESP_DEVICE_SIZE 1280
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#define MAX_EMSESP_STATUS_SIZE 1024
|
||||
#define EMSESP_DEVICES_SERVICE_PATH "/rest/emsespDevices"
|
||||
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
|
||||
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
|
||||
#define DEVICE_DATA_SERVICE_PATH "/rest/deviceData"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using namespace std::placeholders; // for `_1`
|
||||
|
||||
class EMSESPDevicesService {
|
||||
public:
|
||||
EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
void emsespDevicesService(AsyncWebServerRequest * request);
|
||||
void all_devices(AsyncWebServerRequest * request);
|
||||
void scan_devices(AsyncWebServerRequest * request);
|
||||
void device_data(AsyncWebServerRequest * request, JsonVariant & json);
|
||||
|
||||
AsyncCallbackJsonWebHandler _device_dataHandler;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#include <EMSESPScanDevicesService.h>
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
EMSESPScanDevicesService::EMSESPScanDevicesService(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(SCAN_DEVICES_SERVICE_PATH,
|
||||
HTTP_POST,
|
||||
securityManager->wrapRequest(std::bind(&EMSESPScanDevicesService::scan_devices, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
|
||||
}
|
||||
|
||||
void EMSESPScanDevicesService::scan_devices(AsyncWebServerRequest * request) {
|
||||
request->onDisconnect([]() {
|
||||
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
});
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef EMSESPScanDevicesService_h
|
||||
#define EMSESPScanDevicesService_h
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class EMSESPScanDevicesService {
|
||||
public:
|
||||
EMSESPScanDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
void scan_devices(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "EMSESPSettingsService.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
@@ -6,62 +24,46 @@ namespace emsesp {
|
||||
EMSESPSettingsService::EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(EMSESPSettings::read, EMSESPSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(EMSESPSettings::read, EMSESPSettings::update, this, fs, EMSESP_SETTINGS_FILE) {
|
||||
addUpdateHandler([&](const String & originId) { onUpdate(); }, false);
|
||||
}
|
||||
|
||||
void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
|
||||
root["tx_mode"] = settings.tx_mode;
|
||||
root["ems_bus_id"] = settings.ems_bus_id;
|
||||
|
||||
root["tx_mode"] = settings.tx_mode;
|
||||
root["ems_bus_id"] = settings.ems_bus_id;
|
||||
root["syslog_level"] = settings.syslog_level;
|
||||
root["syslog_mark_interval"] = settings.syslog_mark_interval;
|
||||
root["syslog_host"] = settings.syslog_host;
|
||||
|
||||
root["master_thermostat"] = settings.master_thermostat;
|
||||
root["shower_timer"] = settings.shower_timer;
|
||||
root["shower_alert"] = settings.shower_alert;
|
||||
root["master_thermostat"] = settings.master_thermostat;
|
||||
root["shower_timer"] = settings.shower_timer;
|
||||
root["shower_alert"] = settings.shower_alert;
|
||||
}
|
||||
|
||||
StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) {
|
||||
EMSESPSettings newSettings = {};
|
||||
newSettings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
|
||||
newSettings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
|
||||
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
|
||||
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
|
||||
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
|
||||
settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
|
||||
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
|
||||
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
|
||||
|
||||
newSettings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
|
||||
newSettings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
|
||||
newSettings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
newSettings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
|
||||
newSettings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
|
||||
newSettings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
|
||||
|
||||
bool changed = false;
|
||||
|
||||
if (newSettings.tx_mode != settings.tx_mode) {
|
||||
EMSESP::reset_tx(newSettings.tx_mode); // reset counters
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ((newSettings.shower_timer != settings.shower_timer) || (newSettings.shower_alert != settings.shower_alert)) {
|
||||
EMSESP::shower_.start();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ((newSettings.syslog_level != settings.syslog_level) || (newSettings.syslog_mark_interval != settings.syslog_mark_interval)
|
||||
|| !newSettings.syslog_host.equals(settings.syslog_host)) {
|
||||
EMSESP::system_.syslog_init();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
settings = newSettings;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
return StateUpdateResult::UNCHANGED;
|
||||
// this is called after the settings have been persisted to the filesystem
|
||||
void EMSESPSettingsService::onUpdate() {
|
||||
EMSESP::shower_.start();
|
||||
EMSESP::system_.syslog_init();
|
||||
EMSESP::reset_tx();
|
||||
}
|
||||
|
||||
void EMSESPSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
}
|
||||
|
||||
void EMSESPSettingsService::save() {
|
||||
_fsPersistence.writeToFS();
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESPSettingsConfig_h
|
||||
#define EMSESPSettingsConfig_h
|
||||
|
||||
@@ -7,7 +25,7 @@
|
||||
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
|
||||
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
|
||||
|
||||
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
|
||||
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
|
||||
#define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key
|
||||
|
||||
#define EMSESP_DEFAULT_SYSLOG_LEVEL -1 // OFF
|
||||
@@ -44,10 +62,13 @@ class EMSESPSettingsService : public StatefulService<EMSESPSettings> {
|
||||
EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
|
||||
void begin();
|
||||
void save();
|
||||
|
||||
private:
|
||||
HttpEndpoint<EMSESPSettings> _httpEndpoint;
|
||||
FSPersistence<EMSESPSettings> _fsPersistence;
|
||||
|
||||
void onUpdate();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "EMSESPStatusService.h"
|
||||
#include "emsesp.h"
|
||||
#include "mqtt.h"
|
||||
@@ -12,38 +30,30 @@ EMSESPStatusService::EMSESPStatusService(AsyncWebServer * server, SecurityManage
|
||||
securityManager->wrapRequest(std::bind(&EMSESPStatusService::emsespStatusService, this, std::placeholders::_1),
|
||||
AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
// trigger on wifi connects
|
||||
// trigger on wifi connects/disconnects
|
||||
#ifdef ESP32
|
||||
WiFi.onEvent(onStationModeConnected, WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED);
|
||||
WiFi.onEvent(onStationModeDisconnected, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
|
||||
WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
|
||||
#elif defined(ESP8266)
|
||||
_onStationModeConnectedHandler = WiFi.onStationModeConnected(onStationModeConnected);
|
||||
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(onStationModeDisconnected);
|
||||
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(onStationModeGotIP);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
void EMSESPStatusService::onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
EMSESP::logger().debug(F("Wifi Connected"));
|
||||
}
|
||||
void EMSESPStatusService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
EMSESP::logger().debug(F("WiFi Disconnected. Reason code=%d"), info.disconnected.reason);
|
||||
}
|
||||
void EMSESPStatusService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
EMSESP::logger().debug(F("WiFi connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
|
||||
EMSESP::logger().debug(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
|
||||
EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
void EMSESPStatusService::onStationModeConnected(const WiFiEventStationModeConnected & event) {
|
||||
EMSESP::logger().debug(F("Wifi connected with SSID %s"), event.ssid.c_str());
|
||||
}
|
||||
void EMSESPStatusService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) {
|
||||
EMSESP::logger().debug(F("WiFi Disconnected. Reason code=%d"), event.reason);
|
||||
}
|
||||
void EMSESPStatusService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) {
|
||||
EMSESP::logger().debug(F("WiFi connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str());
|
||||
EMSESP::logger().debug(F("WiFi Connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str());
|
||||
EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESPStatusService_h
|
||||
#define EMSESPStatusService_h
|
||||
|
||||
@@ -20,14 +38,11 @@ class EMSESPStatusService {
|
||||
void emsespStatusService(AsyncWebServerRequest * request);
|
||||
|
||||
#ifdef ESP32
|
||||
static void onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
static void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
#elif defined(ESP8266)
|
||||
WiFiEventHandler _onStationModeConnectedHandler;
|
||||
WiFiEventHandler _onStationModeDisconnectedHandler;
|
||||
WiFiEventHandler _onStationModeGotIPHandler;
|
||||
static void onStationModeConnected(const WiFiEventStationModeConnected & event);
|
||||
static void onStationModeDisconnected(const WiFiEventStationModeDisconnected & event);
|
||||
static void onStationModeGotIP(const WiFiEventStationModeGotIP & event);
|
||||
#endif
|
||||
|
||||
@@ -90,6 +90,7 @@ void EMSESPShell::display_banner() {
|
||||
|
||||
// turn off watch
|
||||
emsesp::EMSESP::watch_id(WATCH_ID_NONE);
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
|
||||
}
|
||||
|
||||
// pre-loads all the console commands into the MAIN context
|
||||
@@ -107,11 +108,9 @@ void EMSESPShell::add_console_commands() {
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(refresh)},
|
||||
flash_string_vector{F_(fetch)},
|
||||
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
shell.printfln(F("Requesting data from EMS devices"));
|
||||
console_commands_loaded_ = false;
|
||||
add_console_commands();
|
||||
EMSESP::fetch_device_values();
|
||||
});
|
||||
|
||||
@@ -177,15 +176,14 @@ void EMSESPShell::add_console_commands() {
|
||||
flash_string_vector{F_(n_mandatory)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10);
|
||||
|
||||
// save the tx_mode
|
||||
EMSESP::emsespSettingsService.update(
|
||||
[&](EMSESPSettings & settings) {
|
||||
settings.tx_mode = tx_mode;
|
||||
shell.printfln(F_(tx_mode_fmt), tx_mode);
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
EMSESP::reset_tx(tx_mode); // reset counters and set tx_mode
|
||||
EMSESP::reset_tx(); // reset counters and set tx_mode
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
@@ -516,6 +514,9 @@ void Console::start() {
|
||||
shell = std::make_shared<EMSESPStreamConsole>(serial_console_, true);
|
||||
shell->maximum_log_messages(100); // default is 50
|
||||
shell->start();
|
||||
#endif
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all
|
||||
#endif
|
||||
|
||||
@@ -524,8 +525,6 @@ void Console::start() {
|
||||
shell->add_flags(CommandFlags::ADMIN);
|
||||
#endif
|
||||
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // turn watch off in case it was still set in the last session
|
||||
|
||||
// start the telnet service
|
||||
// default idle is 10 minutes, default write timeout is 0 (automatic)
|
||||
// note, this must be started after the network/wifi for ESP32 otherwise it'll crash
|
||||
@@ -533,6 +532,9 @@ void Console::start() {
|
||||
telnet_.start();
|
||||
telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second
|
||||
#endif
|
||||
|
||||
// turn watch off in case it was still set in the last session
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
|
||||
}
|
||||
|
||||
// handles telnet sync and logging to console
|
||||
|
||||
@@ -73,7 +73,7 @@ MAKE_PSTR_WORD(read)
|
||||
MAKE_PSTR_WORD(version)
|
||||
MAKE_PSTR_WORD(values)
|
||||
MAKE_PSTR_WORD(system)
|
||||
MAKE_PSTR_WORD(refresh)
|
||||
MAKE_PSTR_WORD(fetch)
|
||||
MAKE_PSTR_WORD(restart)
|
||||
MAKE_PSTR_WORD(format)
|
||||
MAKE_PSTR_WORD(raw)
|
||||
@@ -191,8 +191,8 @@ class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPSh
|
||||
|
||||
class Console {
|
||||
public:
|
||||
static void loop();
|
||||
void start();
|
||||
void loop();
|
||||
void start();
|
||||
|
||||
uuid::log::Level log_level();
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ MAKE_PSTR_WORD(comfort)
|
||||
MAKE_PSTR_WORD(eco)
|
||||
MAKE_PSTR_WORD(intelligent)
|
||||
MAKE_PSTR_WORD(hot)
|
||||
MAKE_PSTR_WORD(maxpower)
|
||||
MAKE_PSTR_WORD(minpower)
|
||||
|
||||
MAKE_PSTR(comfort_mandatory, "<hot | eco | intelligent>")
|
||||
|
||||
@@ -59,7 +61,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_telegram_type(0x33, F("UBAParameterWW"), true, std::bind(&Boiler::process_UBAParameterWW, this, _1));
|
||||
register_telegram_type(0x14, F("UBATotalUptime"), false, std::bind(&Boiler::process_UBATotalUptime, this, _1));
|
||||
register_telegram_type(0x35, F("UBAFlags"), false, std::bind(&Boiler::process_UBAFlags, this, _1));
|
||||
register_telegram_type(0x15, F("UBAMaintenanceSettings"), false, std::bind(&Boiler::process_UBAMaintenanceSettings, this, _1));
|
||||
register_telegram_type(0x15, F("UBAMaintenanceData"), false, std::bind(&Boiler::process_UBAMaintenanceData, this, _1));
|
||||
register_telegram_type(0x16, F("UBAParameters"), true, std::bind(&Boiler::process_UBAParameters, this, _1));
|
||||
register_telegram_type(0x1A, F("UBASetPoints"), false, std::bind(&Boiler::process_UBASetPoints, this, _1));
|
||||
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, std::bind(&Boiler::process_UBAOutdoorTemp, this, _1));
|
||||
@@ -103,18 +105,50 @@ void Boiler::boiler_cmd(const char * message) {
|
||||
uint8_t t = doc["wwtemp"];
|
||||
set_warmwater_temp(t);
|
||||
}
|
||||
if (nullptr != doc["boilhyston"]) {
|
||||
int8_t t = doc["boilhyston"];
|
||||
set_hyst_on(t);
|
||||
}
|
||||
if (nullptr != doc["boilhystoff"]) {
|
||||
uint8_t t = doc["boilhystoff"];
|
||||
set_hyst_off(t);
|
||||
}
|
||||
if (nullptr != doc["burnperiod"]) {
|
||||
uint8_t t = doc["burnperiod"];
|
||||
set_burn_period(t);
|
||||
}
|
||||
if (nullptr != doc["burnminpower"]) {
|
||||
uint8_t p = doc["burnminpower"];
|
||||
set_min_power(p);
|
||||
}
|
||||
if (nullptr != doc["burnmaxpower"]) {
|
||||
uint8_t p = doc["burnmaxpower"];
|
||||
set_max_power(p);
|
||||
}
|
||||
if (nullptr != doc["pumpdelay"]) {
|
||||
uint8_t t = doc["pumpdelay"];
|
||||
set_pump_delay(t);
|
||||
}
|
||||
|
||||
if (nullptr != doc["comfort"]) {
|
||||
const char * data = doc["comfort"];
|
||||
if (strcmp((char *)data, "hot") == 0) {
|
||||
set_warmwater_mode(1);
|
||||
} else if (strcmp((char *)data, "eco") == 0) {
|
||||
set_warmwater_mode(2);
|
||||
} else if (strcmp((char *)data, "intelligent") == 0) {
|
||||
set_warmwater_mode(3);
|
||||
}
|
||||
}
|
||||
|
||||
const char * command = doc["cmd"];
|
||||
if (command == nullptr) {
|
||||
if (command == nullptr || doc["data"] == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// boiler ww comfort setting
|
||||
if (strcmp(command, "comfort") == 0) {
|
||||
const char * data = doc["data"];
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (strcmp((char *)data, "hot") == 0) {
|
||||
set_warmwater_mode(1);
|
||||
} else if (strcmp((char *)data, "eco") == 0) {
|
||||
@@ -128,9 +162,45 @@ void Boiler::boiler_cmd(const char * message) {
|
||||
// boiler flowtemp setting
|
||||
if (strcmp(command, "flowtemp") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
if (t) {
|
||||
set_flow_temp(t);
|
||||
}
|
||||
set_flow_temp(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "wwtemp") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_warmwater_temp(t);
|
||||
return;
|
||||
}
|
||||
// boiler max power setting
|
||||
if (strcmp(command, "burnmaxpower") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_max_power(p);
|
||||
return;
|
||||
}
|
||||
|
||||
// boiler min power setting
|
||||
if (strcmp(command, "burnminpower") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_min_power(p);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "boilhyston") == 0) {
|
||||
int8_t t = doc["data"];
|
||||
set_hyst_on(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "boilhystoff") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_hyst_off(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "burnperiod") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_burn_period(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "pumpdelay") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_pump_delay(t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -166,9 +236,27 @@ void Boiler::boiler_cmd_wwtemp(const char * message) {
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::device_info(JsonArray & root) {
|
||||
JsonObject dataElement;
|
||||
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Hot tap water");
|
||||
dataElement["value"] = tap_water_active_ ? F("running") : F("off");
|
||||
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Central heating");
|
||||
dataElement["value"] = heating_active_ ? F("active") : F("off");
|
||||
|
||||
render_value_json(root, "", F("Selected flow temperature"), selFlowTemp_, F_(degrees));
|
||||
render_value_json(root, "", F("Current flow temperature"), curFlowTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Warm Water selected temperature"), wWSelTemp_, F_(degrees));
|
||||
render_value_json(root, "", F("Warm Water set temperature"), wWSetTmp_, F_(degrees));
|
||||
render_value_json(root, "", F("Warm Water current temperature (intern)"), wWCurTmp_, F_(degrees), 10);
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
void Boiler::publish_values() {
|
||||
const size_t capacity = JSON_OBJECT_SIZE(47); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
||||
const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
||||
DynamicJsonDocument doc(capacity);
|
||||
|
||||
char s[10]; // for formatting strings
|
||||
@@ -280,7 +368,7 @@ void Boiler::publish_values() {
|
||||
doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(heatPmp_, VALUE_BOOL)) {
|
||||
doc["heatPmp"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
|
||||
doc["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(fanWork_, VALUE_BOOL)) {
|
||||
doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL);
|
||||
@@ -292,13 +380,37 @@ void Boiler::publish_values() {
|
||||
doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(heating_temp_)) {
|
||||
doc["heating_temp"] = heating_temp_;
|
||||
doc["heatingTemp"] = heating_temp_;
|
||||
}
|
||||
if (Helpers::hasValue(pump_mod_max_)) {
|
||||
doc["pump_mod_max"] = pump_mod_max_;
|
||||
doc["pumpModMax"] = pump_mod_max_;
|
||||
}
|
||||
if (Helpers::hasValue(pump_mod_min_)) {
|
||||
doc["pump_mod_min"] = pump_mod_min_;
|
||||
doc["pumpModMin"] = pump_mod_min_;
|
||||
}
|
||||
if (Helpers::hasValue(pumpDelay_)) {
|
||||
doc["pumpDelay"] = pumpDelay_;
|
||||
}
|
||||
if (Helpers::hasValue(burnPeriod_)) {
|
||||
doc["burnMinPeriod"] = burnPeriod_;
|
||||
}
|
||||
if (Helpers::hasValue(burnPowermin_)) {
|
||||
doc["burnMinPower"] = burnPowermin_;
|
||||
}
|
||||
if (Helpers::hasValue(burnPowermax_)) {
|
||||
doc["burnMaxPower"] = burnPowermax_;
|
||||
}
|
||||
if (Helpers::hasValue(boilTemp_on_)) {
|
||||
doc["boilHystOn"] = boilTemp_on_;
|
||||
}
|
||||
if (Helpers::hasValue(boilTemp_off_)) {
|
||||
doc["boilHystOff"] = boilTemp_off_;
|
||||
}
|
||||
if (Helpers::hasValue(setFlowTemp_)) {
|
||||
doc["setFlowTemp"] = setFlowTemp_;
|
||||
}
|
||||
if (Helpers::hasValue(setWWPumpPow_)) {
|
||||
doc["wWSetPumpPower"] = setWWPumpPow_;
|
||||
}
|
||||
if (Helpers::hasValue(wWStarts_)) {
|
||||
doc["wWStarts"] = wWStarts_;
|
||||
@@ -318,7 +430,6 @@ void Boiler::publish_values() {
|
||||
if (Helpers::hasValue(heatWorkMin_)) {
|
||||
doc["heatWorkMin"] = heatWorkMin_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(serviceCode_)) {
|
||||
doc["serviceCode"] = serviceCodeChar_;
|
||||
doc["serviceCodeNumber"] = serviceCode_;
|
||||
@@ -416,6 +527,17 @@ void Boiler::show_values(uuid::console::Shell & shell) {
|
||||
print_value(shell, 2, F("Heating temperature setting on the boiler"), heating_temp_, F_(degrees));
|
||||
print_value(shell, 2, F("Boiler circuit pump modulation max power"), pump_mod_max_, F_(percent));
|
||||
print_value(shell, 2, F("Boiler circuit pump modulation min power"), pump_mod_min_, F_(percent));
|
||||
print_value(shell, 2, F("Boiler circuit pump delay time"), pumpDelay_, F("min"));
|
||||
print_value(shell, 2, F("Boiler temp hysteresis on"), boilTemp_on_, F_(degrees));
|
||||
print_value(shell, 2, F("Boiler temp hysteresis off"), boilTemp_off_, F_(degrees));
|
||||
print_value(shell, 2, F("Boiler burner min period"), burnPeriod_, F("min"));
|
||||
print_value(shell, 2, F("Boiler burner min power"), burnPowermin_, F_(percent));
|
||||
print_value(shell, 2, F("Boiler burner max power"), burnPowermax_, F_(percent));
|
||||
|
||||
// UBASetPoint - these may differ from the above
|
||||
print_value(shell, 2, F("Set Flow temperature"), setFlowTemp_, F_(degrees));
|
||||
print_value(shell, 2, F("Boiler burner set power"), setBurnPow_, F_(percent));
|
||||
print_value(shell, 2, F("Warm Water pump set power"), setWWPumpPow_, F_(percent));
|
||||
|
||||
// UBAMonitorSlow
|
||||
if (Helpers::hasValue(extTemp_)) {
|
||||
@@ -528,6 +650,12 @@ void Boiler::process_UBATotalUptime(std::shared_ptr<const Telegram> telegram) {
|
||||
*/
|
||||
void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(heating_temp_, 1);
|
||||
telegram->read_value(burnPowermax_,2);
|
||||
telegram->read_value(burnPowermin_,3);
|
||||
telegram->read_value(boilTemp_off_,4);
|
||||
telegram->read_value(boilTemp_on_,5);
|
||||
telegram->read_value(burnPeriod_,6);
|
||||
telegram->read_value(pumpDelay_,8);
|
||||
telegram->read_value(pump_mod_max_, 9);
|
||||
telegram->read_value(pump_mod_min_, 10);
|
||||
}
|
||||
@@ -652,16 +780,16 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(extTemp_, 0);
|
||||
}
|
||||
|
||||
// UBASetPoint 0x1A
|
||||
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat
|
||||
telegram->read_value(setBurnPow_, 1); // max output power in %
|
||||
telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power?
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
// UBASetPoint 0x1A
|
||||
// not yet implemented
|
||||
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
|
||||
// uint8_t setpoint = telegram->message_data[0]; // boiler flow temp
|
||||
// uint8_t ww_power = telegram->message_data[2]; // power in %
|
||||
}
|
||||
|
||||
// 0x35
|
||||
// not yet implemented
|
||||
void Boiler::process_UBAFlags(std::shared_ptr<const Telegram> telegram) {
|
||||
@@ -670,11 +798,7 @@ void Boiler::process_UBAFlags(std::shared_ptr<const Telegram> telegram) {
|
||||
// 0x1C
|
||||
// not yet implemented
|
||||
void Boiler::process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram) {
|
||||
}
|
||||
|
||||
// 0x15
|
||||
// not yet implemented
|
||||
void Boiler::process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> telegram) {
|
||||
// first byte: Maintenance due (0 = no, 3 = yes, due to operating hours, 8 = yes, due to date)
|
||||
}
|
||||
|
||||
// 0x10, 0x11, 0x12
|
||||
@@ -685,12 +809,20 @@ void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// 0x15
|
||||
void Boiler::process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram) {
|
||||
// first byte: Maintenance messages (0 = none, 1 = by operating hours, 2 = by date)
|
||||
// I see a value of 3 in the 1st byte when the boiler is booted, so probably a flag
|
||||
if (telegram->message_data[0] == 3) {
|
||||
LOG_WARNING(F("Boiler has booted."));
|
||||
}
|
||||
}
|
||||
|
||||
// Set the warm water temperature 0x33
|
||||
void Boiler::set_warmwater_temp(const uint8_t temperature) {
|
||||
LOG_INFO(F("Setting boiler warm water temperature to %d C"), temperature);
|
||||
write_command(EMS_TYPE_UBAParameterWW, 2, temperature);
|
||||
// for i9000, see #397
|
||||
write_command(EMS_TYPE_UBAFlags, 3, temperature);
|
||||
write_command(EMS_TYPE_UBAFlags, 3, temperature); // for i9000, see #397
|
||||
}
|
||||
|
||||
// flow temp
|
||||
@@ -699,6 +831,42 @@ void Boiler::set_flow_temp(const uint8_t temperature) {
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, temperature);
|
||||
}
|
||||
|
||||
// set min boiler output
|
||||
void Boiler::set_min_power(const uint8_t power) {
|
||||
LOG_INFO(F("Setting boiler min power to "), power);
|
||||
write_command(EMS_TYPE_UBAParameters, 3, power);
|
||||
}
|
||||
|
||||
// set max temp
|
||||
void Boiler::set_max_power(const uint8_t power) {
|
||||
LOG_INFO(F("Setting boiler max power to %d C"), power);
|
||||
write_command(EMS_TYPE_UBAParameters, 2, power);
|
||||
}
|
||||
|
||||
// set oiler on hysteresis
|
||||
void Boiler::set_hyst_on(const uint8_t temp) {
|
||||
LOG_INFO(F("Setting boiler hysteresis on to %d C"), temp);
|
||||
write_command(EMS_TYPE_UBAParameters, 5, temp);
|
||||
}
|
||||
|
||||
// set boiler off hysteresis
|
||||
void Boiler::set_hyst_off(const uint8_t temp) {
|
||||
LOG_INFO(F("Setting boiler hysteresis off to %d C"), temp);
|
||||
write_command(EMS_TYPE_UBAParameters, 4, temp);
|
||||
}
|
||||
|
||||
// set min burner period
|
||||
void Boiler::set_burn_period(const uint8_t t) {
|
||||
LOG_INFO(F("Setting burner min. period to %d min"), t);
|
||||
write_command(EMS_TYPE_UBAParameters, 6, t);
|
||||
}
|
||||
|
||||
// set pump delay
|
||||
void Boiler::set_pump_delay(const uint8_t t) {
|
||||
LOG_INFO(F("Setting boiler pump delay to %d min"), t);
|
||||
write_command(EMS_TYPE_UBAParameters, 8, t);
|
||||
}
|
||||
|
||||
// 1=hot, 2=eco, 3=intelligent
|
||||
// note some boilers do not have this setting, than it's done by thermostat
|
||||
// on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3)
|
||||
@@ -803,6 +971,22 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
|
||||
set_flow_temp(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(maxpower)},
|
||||
flash_string_vector{F_(n_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_max_power(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(minpower)},
|
||||
flash_string_vector{F_(n_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_min_power(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
|
||||
@@ -40,6 +40,7 @@ class Boiler : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
@@ -54,6 +55,7 @@ class Boiler : public EMSdevice {
|
||||
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;
|
||||
static constexpr uint8_t EMS_TYPE_UBAFlags = 0x35;
|
||||
static constexpr uint8_t EMS_TYPE_UBASetPoints = 0x1A;
|
||||
static constexpr uint8_t EMS_TYPE_UBAParameters = 0x16;
|
||||
|
||||
static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344
|
||||
|
||||
@@ -119,11 +121,22 @@ class Boiler : public EMSdevice {
|
||||
uint8_t heating_temp_ = EMS_VALUE_UINT_NOTSET; // Heating temperature setting on the boiler
|
||||
uint8_t pump_mod_max_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation max. power %
|
||||
uint8_t pump_mod_min_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation min. power
|
||||
uint8_t burnPowermin_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t burnPowermax_ = EMS_VALUE_UINT_NOTSET;
|
||||
int8_t boilTemp_off_ = EMS_VALUE_INT_NOTSET;
|
||||
int8_t boilTemp_on_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t burnPeriod_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET;
|
||||
|
||||
// UBASetPoint
|
||||
uint8_t setFlowTemp_ = EMS_VALUE_UINT_NOTSET; // boiler setpoint temp
|
||||
uint8_t setBurnPow_ = EMS_VALUE_UINT_NOTSET; // max output power in %
|
||||
uint8_t setWWPumpPow_ = EMS_VALUE_UINT_NOTSET; // ww pump speed/power?
|
||||
|
||||
// other internal calculated params
|
||||
uint8_t tap_water_active_ = EMS_VALUE_BOOL_NOTSET; // Hot tap water is on/off
|
||||
uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off
|
||||
|
||||
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
|
||||
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
|
||||
|
||||
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -140,7 +153,7 @@ class Boiler : public EMSdevice {
|
||||
void process_UBAFlags(std::shared_ptr<const Telegram> telegram);
|
||||
void process_MC10Status(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -154,6 +167,13 @@ class Boiler : public EMSdevice {
|
||||
void set_tapwarmwater_activated(const bool activated);
|
||||
void set_warmwater_onetime(const bool activated);
|
||||
void set_warmwater_circulation(const bool activated);
|
||||
void set_min_power(const uint8_t power);
|
||||
void set_max_power(const uint8_t power);
|
||||
void set_hyst_on(const uint8_t temp);
|
||||
void set_hyst_off(const uint8_t temp);
|
||||
void set_burn_period(const uint8_t t);
|
||||
void set_pump_delay(const uint8_t t);
|
||||
|
||||
|
||||
// mqtt callbacks
|
||||
void boiler_cmd(const char * message);
|
||||
|
||||
@@ -34,6 +34,9 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
||||
// register_mqtt_topic("topic", std::bind(&Connect::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Connect::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
void Connect::add_context_menu() {
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class Connect : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
void Controller::add_context_menu() {
|
||||
}
|
||||
|
||||
void Controller::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Controller::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
@@ -37,6 +37,7 @@ class Controller : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
||||
void Gateway::add_context_menu() {
|
||||
}
|
||||
|
||||
void Gateway::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Gateway::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
@@ -37,6 +37,7 @@ class Gateway : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
|
||||
void Heatpump::add_context_menu() {
|
||||
}
|
||||
|
||||
void Heatpump::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Heatpump::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
@@ -37,6 +37,7 @@ class Heatpump : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -57,6 +57,24 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
|
||||
void Mixing::add_context_menu() {
|
||||
}
|
||||
|
||||
// output json to web UI
|
||||
void Mixing::device_info(JsonArray & root) {
|
||||
if (type_ == Type::NONE) {
|
||||
return; // don't have any values yet
|
||||
}
|
||||
|
||||
if (type_ == Type::WWC) {
|
||||
render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr);
|
||||
} else {
|
||||
render_value_json(root, "", F("Heating Circuit"), hc_, nullptr);
|
||||
}
|
||||
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
|
||||
render_value_json(root, "", F("Current pump modulation"), pumpMod_, F_(percent));
|
||||
render_value_json(root, "", F("Current valve status"), status_, nullptr);
|
||||
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Mixing::updated_values() {
|
||||
return false;
|
||||
|
||||
@@ -37,6 +37,7 @@ class Mixing : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -58,6 +58,32 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
|
||||
void Solar::add_context_menu() {
|
||||
}
|
||||
|
||||
// print to web
|
||||
void Solar::device_info(JsonArray & root) {
|
||||
render_value_json(root, "", F("Collector temperature (TS1)"), collectorTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Bottom temperature (TS2)"), bottomTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Bottom temperature (TS5)"), bottomTemp2_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Pump modulation"), pumpModulation_, F_(percent));
|
||||
render_value_json(root, "", F("Valve (VS2) status"), valveStatus_, nullptr, EMS_VALUE_BOOL);
|
||||
render_value_json(root, "", F("Pump (PS1) active"), pump_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
if (Helpers::hasValue(pumpWorkMin_)) {
|
||||
JsonObject dataElement;
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Pump working time");
|
||||
std::string time_str(60, '\0');
|
||||
snprintf_P(&time_str[0], time_str.capacity() + 1, PSTR("%d days %d hours %d minutes"), pumpWorkMin_ / 1440, (pumpWorkMin_ % 1440) / 60, pumpWorkMin_ % 60);
|
||||
dataElement["value"] = time_str;
|
||||
}
|
||||
|
||||
render_value_json(root, "", F("Tank Heated"), tankHeated_, nullptr, EMS_VALUE_BOOL);
|
||||
render_value_json(root, "", F("Collector shutdown"), collectorOnOff_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
render_value_json(root, "", F("Energy last hour"), energyLastHour_, F_(wh), 10);
|
||||
render_value_json(root, "", F("Energy today"), energyToday_, F_(wh));
|
||||
render_value_json(root, "", F("Energy total"), energyTotal_, F_(kwh), 10);
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Solar::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
@@ -74,7 +100,7 @@ void Solar::show_values(uuid::console::Shell & shell) {
|
||||
}
|
||||
|
||||
print_value(shell, 2, F("Tank Heated"), tankHeated_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 2, F("Collector"), collectorOnOff_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 2, F("Collector shutdown"), collectorOnOff_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
print_value(shell, 2, F("Energy last hour"), energyLastHour_, F_(wh), 10);
|
||||
print_value(shell, 2, F("Energy today"), energyToday_, F_(wh));
|
||||
@@ -203,8 +229,8 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
|
||||
pumpModulation_ = 15; // set to minimum
|
||||
}
|
||||
|
||||
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
|
||||
telegram->read_bitvalue(collectorOnOff_, 3, 0);
|
||||
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
|
||||
telegram->read_bitvalue(collectorOnOff_, 3, 0); // collector shutdown
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -240,11 +266,14 @@ void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram)
|
||||
if (Wh != 0xFFFF) {
|
||||
energyLastHour_ = Wh * 10; // set to *10
|
||||
}
|
||||
telegram->read_bitvalue(collectorOnOff_, 9, 0); // collector on/off
|
||||
telegram->read_bitvalue(pump_, 8, 0); // Solar pump on (1) or off (0)
|
||||
telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
|
||||
telegram->read_bitvalue(collectorOnOff_, 9, 0); // collector shutdown on/off
|
||||
telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
|
||||
}
|
||||
|
||||
/*
|
||||
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
|
||||
* e.g. 90 30 FF 06 00 01 50
|
||||
*/
|
||||
void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
@@ -37,6 +37,7 @@ class Solar : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
@@ -57,7 +58,7 @@ class Solar : public EMSdevice {
|
||||
uint32_t energyTotal_ = EMS_VALUE_ULONG_NOTSET;
|
||||
uint32_t pumpWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total solar pump operating time
|
||||
uint8_t tankHeated_ = EMS_VALUE_BOOL_NOTSET;
|
||||
uint8_t collectorOnOff_ = EMS_VALUE_BOOL_NOTSET;
|
||||
uint8_t collectorOnOff_ = EMS_VALUE_BOOL_NOTSET; // Collector shutdown on/off
|
||||
|
||||
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
|
||||
@@ -39,6 +39,9 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
|
||||
void Switch::add_context_menu() {
|
||||
}
|
||||
|
||||
void Switch::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Switch::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
@@ -37,6 +37,7 @@ class Switch : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -141,8 +141,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
// if we're on auto mode, register this thermostat if it has a device id of 0x10 or 0x17
|
||||
// or if its the master thermostat we defined
|
||||
// see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161
|
||||
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && ((device_id == 0x10) || (device_id == 0x17)))
|
||||
|| (master_thermostat == device_id)) {
|
||||
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) {
|
||||
EMSESP::actual_master_thermostat(device_id);
|
||||
LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X (as master)"), device_id);
|
||||
init_mqtt();
|
||||
@@ -169,6 +168,59 @@ void Thermostat::init_mqtt() {
|
||||
register_mqtt_topic("thermostat_cmd_mode", std::bind(&Thermostat::thermostat_cmd_mode, this, _1));
|
||||
}
|
||||
|
||||
// prepare data for Web UI
|
||||
void Thermostat::device_info(JsonArray & root) {
|
||||
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits
|
||||
|
||||
for (const auto & hc : heating_circuits_) {
|
||||
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
|
||||
break; // skip this HC
|
||||
}
|
||||
|
||||
// different thermostat types store their temperature values differently
|
||||
uint8_t format_setpoint, format_curr;
|
||||
switch (flags) {
|
||||
case EMS_DEVICE_FLAG_EASY:
|
||||
format_setpoint = 100; // *100
|
||||
format_curr = 100; // *100
|
||||
break;
|
||||
case EMS_DEVICE_FLAG_JUNKERS:
|
||||
format_setpoint = 10; // *10
|
||||
format_curr = 10; // *10
|
||||
break;
|
||||
default: // RC30, RC35 etc...
|
||||
format_setpoint = 2; // *2
|
||||
format_curr = 10; // *10
|
||||
break;
|
||||
}
|
||||
|
||||
// create prefix with heating circuit number
|
||||
std::string hc_str(5, '\0');
|
||||
snprintf_P(&hc_str[0], hc_str.capacity() + 1, PSTR("hc%d: "), hc->hc_num());
|
||||
|
||||
render_value_json(root, hc_str, F("Current room temperature"), hc->curr_roomTemp, F_(degrees), format_curr);
|
||||
render_value_json(root, hc_str, F("Setpoint room temperature"), hc->setpoint_roomTemp, F_(degrees), format_setpoint);
|
||||
if (Helpers::hasValue(hc->mode)) {
|
||||
JsonObject dataElement;
|
||||
dataElement = root.createNestedObject();
|
||||
std::string mode_str(15, '\0');
|
||||
snprintf_P(&mode_str[0], mode_str.capacity() + 1, PSTR("%sMode"), hc_str.c_str());
|
||||
dataElement["name"] = mode_str;
|
||||
std::string modetype_str(20, '\0');
|
||||
if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) {
|
||||
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - summer"), mode_tostring(hc->get_mode(flags)).c_str());
|
||||
} else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) {
|
||||
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - holiday"), mode_tostring(hc->get_mode(flags)).c_str());
|
||||
} else if (Helpers::hasValue(hc->mode_type)) {
|
||||
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - %s"), mode_tostring(hc->get_mode(flags)).c_str(), mode_tostring(hc->get_mode_type(flags)).c_str());
|
||||
} else {
|
||||
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, mode_tostring(hc->get_mode(flags)).c_str());
|
||||
}
|
||||
dataElement["value"] = modetype_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only add the menu for the master thermostat
|
||||
void Thermostat::add_context_menu() {
|
||||
if (device_id() != EMSESP::actual_master_thermostat()) {
|
||||
@@ -258,7 +310,7 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
set_holiday(holiday.c_str(), hc_num);
|
||||
}
|
||||
}
|
||||
|
||||
// commands without heatingcircuit
|
||||
if (nullptr != doc["wwmode"]) {
|
||||
std::string mode = doc["wwmode"];
|
||||
set_ww_mode(mode);
|
||||
@@ -363,10 +415,10 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
|
||||
// check for commands like {"hc":2,"cmd":"temp","data":21}
|
||||
const char * command = doc["cmd"];
|
||||
if (command == nullptr) {
|
||||
if (command == nullptr || doc["data"] == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ok, we have command and data
|
||||
if (strcmp(command, "temp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -374,8 +426,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// thermostat mode changes
|
||||
if (strcmp(command, "mode") == 0) {
|
||||
std::string mode = doc["data"];
|
||||
if (mode.empty()) {
|
||||
@@ -384,7 +434,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
set_mode(mode, hc_num);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "nighttemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -392,7 +441,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "daytemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -400,7 +448,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "holidaytemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -408,7 +455,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "ecotemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -416,7 +462,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "heattemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -424,7 +469,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "nofrosttemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -432,7 +476,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "summertemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -440,7 +483,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "designtemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -448,7 +490,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "offsettemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -456,6 +497,40 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "remotetemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f > 100 || f < 0) {
|
||||
Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET);
|
||||
} else {
|
||||
Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "control") == 0) {
|
||||
uint8_t ctrl = doc["data"];
|
||||
set_control(ctrl, hc_num);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "pause") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_pause(p, hc_num);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "party") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_party(p, hc_num);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "holiday") == 0) {
|
||||
std::string holiday = doc["data"];
|
||||
set_holiday(holiday.c_str(), hc_num);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "date") == 0) {
|
||||
std::string date = doc["data"];
|
||||
set_datetime(date.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Thermostat::thermostat_cmd_temp(const char * message) {
|
||||
@@ -552,7 +627,8 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
// send this specific data using the thermostat_data topic
|
||||
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
// if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
if (mqtt_format_ != MQTT_format::NESTED) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
rootThermostat = doc.to<JsonObject>(); // clear object
|
||||
}
|
||||
@@ -566,7 +642,8 @@ void Thermostat::publish_values() {
|
||||
|
||||
has_data = true;
|
||||
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
|
||||
if (mqtt_format_ != MQTT_format::SINGLE) {
|
||||
// if (mqtt_format_ != MQTT_format::SINGLE) {
|
||||
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
char s[3];
|
||||
@@ -655,7 +732,8 @@ void Thermostat::publish_values() {
|
||||
|
||||
// if format is single, send immediately and clear object for next hc
|
||||
// the topic will have the hc number appended
|
||||
if (mqtt_format_ == MQTT_format::SINGLE) {
|
||||
// if (mqtt_format_ == MQTT_format::SINGLE) {
|
||||
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) {
|
||||
char topic[30];
|
||||
char s[3];
|
||||
strlcpy(topic, "thermostat_data", 30);
|
||||
@@ -674,10 +752,9 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
// if we're using nested json, send all in one go under one topic called thermostat_data
|
||||
// if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
|
||||
if (mqtt_format_ == MQTT_format::NESTED) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
} else if (mqtt_format_ == MQTT_format::CUSTOM) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -987,7 +1064,7 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
|
||||
shell.printfln(F(" Display: time"));
|
||||
} else if (ibaMainDisplay_ == 7) {
|
||||
shell.printfln(F(" Display: date"));
|
||||
} else if (ibaMainDisplay_ == 9) {
|
||||
} else if (ibaMainDisplay_ == 8) {
|
||||
shell.printfln(F(" Display: smoke temperature"));
|
||||
}
|
||||
}
|
||||
@@ -1024,8 +1101,6 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
|
||||
}
|
||||
}
|
||||
|
||||
// std::sort(heating_circuits_.begin(), heating_circuits_.end()); // sort based on hc number. This has moved to the heating_circuit() function
|
||||
|
||||
for (const auto & hc : heating_circuits_) {
|
||||
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
|
||||
break; // skip this HC
|
||||
@@ -1168,7 +1243,7 @@ void Thermostat::process_EasyMonitor(std::shared_ptr<const Telegram> telegram) {
|
||||
void Thermostat::process_IBASettings(std::shared_ptr<const Telegram> telegram) {
|
||||
// 22 - display line on RC35
|
||||
telegram->read_value(ibaMainDisplay_,
|
||||
0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
|
||||
0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp
|
||||
telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
|
||||
telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin
|
||||
telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy
|
||||
@@ -1310,11 +1385,16 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
||||
if (flags() == EMS_DEVICE_FLAG_EASY) {
|
||||
return; // not supported
|
||||
}
|
||||
|
||||
if (telegram->message_length < 7) {
|
||||
return;
|
||||
}
|
||||
if (telegram->message_data[7] & 0x0C) { // date and time not valid
|
||||
set_datetime("NTP"); // set from NTP
|
||||
return;
|
||||
}
|
||||
if (datetime_.empty()) {
|
||||
datetime_.resize(25, '\0');
|
||||
}
|
||||
|
||||
// render time to HH:MM:SS DD/MM/YYYY
|
||||
// had to create separate buffers because of how printf works
|
||||
char buf1[6];
|
||||
@@ -1469,18 +1549,38 @@ void Thermostat::set_party(const uint8_t hrs, const uint8_t hc_num) {
|
||||
}
|
||||
}
|
||||
|
||||
// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst
|
||||
// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst or "NTP" for setting to internet-time
|
||||
// dw - day of week (0..6), dst- summertime (0/1)
|
||||
void Thermostat::set_datetime(const char * dt) {
|
||||
uint8_t data[9];
|
||||
data[0] = (dt[16] - '0') * 100 + (dt[17] - '0') * 10 + (dt[18] - '0'); // year
|
||||
data[1] = (dt[12] - '0') * 10 + (dt[13] - '0'); // month
|
||||
data[2] = (dt[0] - '0') * 10 + (dt[1] - '0'); // hour
|
||||
data[3] = (dt[9] - '0') * 10 + (dt[10] - '0'); // day
|
||||
data[4] = (dt[3] - '0') * 10 + (dt[4] - '0'); // min
|
||||
data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec
|
||||
data[6] = (dt[20] - '0'); // day of week
|
||||
data[7] = (dt[22] - '0'); // summerime
|
||||
if (strcmp(dt,"NTP") == 0) {
|
||||
time_t now = time(nullptr);
|
||||
tm * tm_ = localtime(&now);
|
||||
if (tm_->tm_year < 110) { // no NTP time
|
||||
LOG_WARNING(F("No NTP time. Cannot set RCtime"));
|
||||
return;
|
||||
}
|
||||
data[0] = tm_->tm_year - 100; // Bosch counts from 2000
|
||||
data[1] = tm_->tm_mon;
|
||||
data[2] = tm_->tm_hour;
|
||||
data[3] = tm_->tm_mday;
|
||||
data[4] = tm_->tm_min;
|
||||
data[5] = tm_->tm_sec;
|
||||
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
|
||||
data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock
|
||||
char time_string[25];
|
||||
strftime(time_string, 25, "%FT%T%z", tm_);
|
||||
LOG_INFO(F("Date and time: %s"), time_string);
|
||||
} else {
|
||||
data[0] = (dt[16] - '0') * 100 + (dt[17] - '0') * 10 + (dt[18] - '0'); // year
|
||||
data[1] = (dt[12] - '0') * 10 + (dt[13] - '0'); // month
|
||||
data[2] = (dt[0] - '0') * 10 + (dt[1] - '0'); // hour
|
||||
data[3] = (dt[9] - '0') * 10 + (dt[10] - '0'); // day
|
||||
data[4] = (dt[3] - '0') * 10 + (dt[4] - '0'); // min
|
||||
data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec
|
||||
data[6] = (dt[20] - '0'); // day of week
|
||||
data[7] = (dt[22] - '0') + 2; // DST and flag
|
||||
}
|
||||
if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35 || (flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) {
|
||||
LOG_INFO(F("Setting date and time"));
|
||||
write_command(6, 0, data, 8, 0);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "mqtt.h"
|
||||
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -94,6 +95,7 @@ class Thermostat : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ uint8_t EMSdevice::decode_brand(uint8_t value) {
|
||||
}
|
||||
}
|
||||
|
||||
// print human friendly description of the EMS device
|
||||
// returns string of a human friendly description of the EMS device
|
||||
std::string EMSdevice::to_string() const {
|
||||
std::string str(160, '\0');
|
||||
|
||||
@@ -153,6 +153,17 @@ std::string EMSdevice::to_string() const {
|
||||
return str;
|
||||
}
|
||||
|
||||
// returns out brand + device name
|
||||
std::string EMSdevice::to_string_short() const {
|
||||
std::string str(160, '\0');
|
||||
if (brand_ == Brand::NO_BRAND) {
|
||||
snprintf_P(&str[0], str.capacity() + 1, PSTR("%s: %s"), device_type_name().c_str(), name_.c_str());
|
||||
} else {
|
||||
snprintf_P(&str[0], str.capacity() + 1, PSTR("%s: %s %s"), device_type_name().c_str(), brand_to_string().c_str(), name_.c_str());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// prints the header for the section
|
||||
void EMSdevice::show_values(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("%s: %s"), device_type_name().c_str(), to_string().c_str());
|
||||
@@ -198,7 +209,7 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
|
||||
Mqtt::show_topic_handlers(shell, this->device_id_);
|
||||
}
|
||||
|
||||
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_function_p f) {
|
||||
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) {
|
||||
LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_);
|
||||
Mqtt::subscribe(this->device_id_, topic, f);
|
||||
}
|
||||
|
||||
@@ -102,12 +102,22 @@ class EMSdevice {
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline uint8_t unique_id() const {
|
||||
return unique_id_;
|
||||
}
|
||||
|
||||
inline void unique_id(uint8_t unique_id) {
|
||||
unique_id_ = unique_id;
|
||||
}
|
||||
|
||||
std::string brand_to_string() const;
|
||||
static uint8_t decode_brand(uint8_t value);
|
||||
|
||||
std::string to_string() const;
|
||||
void show_telegram_handlers(uuid::console::Shell & shell);
|
||||
void show_mqtt_handlers(uuid::console::Shell & shell);
|
||||
std::string to_string_short() const;
|
||||
|
||||
void show_telegram_handlers(uuid::console::Shell & shell);
|
||||
void show_mqtt_handlers(uuid::console::Shell & shell);
|
||||
|
||||
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
|
||||
void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p cb);
|
||||
@@ -119,13 +129,14 @@ class EMSdevice {
|
||||
|
||||
void read_command(const uint16_t type_id);
|
||||
|
||||
void register_mqtt_topic(const std::string & topic, mqtt_function_p f);
|
||||
void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f);
|
||||
|
||||
// virtual functions overrules by derived classes
|
||||
virtual void show_values(uuid::console::Shell & shell) = 0;
|
||||
virtual void publish_values() = 0;
|
||||
virtual bool updated_values() = 0;
|
||||
virtual void add_context_menu() = 0;
|
||||
virtual void device_info(JsonArray & root) = 0;
|
||||
|
||||
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
@@ -165,6 +176,37 @@ class EMSdevice {
|
||||
}
|
||||
}
|
||||
|
||||
// takes a value from an ems device and creates a nested json (name, value)
|
||||
// which can be passed to the web UI
|
||||
template <typename Value>
|
||||
static void render_value_json(JsonArray & json,
|
||||
const std::string & prefix,
|
||||
const __FlashStringHelper * name,
|
||||
Value & value,
|
||||
const __FlashStringHelper * suffix,
|
||||
const uint8_t format = 0) {
|
||||
char buffer[15];
|
||||
if (Helpers::render_value(buffer, value, format) == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject dataElement = json.createNestedObject();
|
||||
|
||||
// copy flash into std::strings to ensure arduinojson can reference them without a copy
|
||||
|
||||
if (suffix != nullptr) {
|
||||
std::string text(20, '\0');
|
||||
snprintf_P(&text[0], text.capacity() + 1, PSTR("%s%s"), buffer, uuid::read_flash_string(suffix).c_str());
|
||||
dataElement["value"] = text;
|
||||
} else {
|
||||
dataElement["value"] = buffer;
|
||||
}
|
||||
|
||||
std::string text2(100, '\0');
|
||||
snprintf_P(&text2[0], text2.capacity() + 1, PSTR("%s%s"), prefix.c_str(), uuid::read_flash_string(name).c_str());
|
||||
dataElement["name"] = text2;
|
||||
}
|
||||
|
||||
static void print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const __FlashStringHelper * value);
|
||||
static void print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value);
|
||||
|
||||
@@ -229,6 +271,7 @@ class EMSdevice {
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS_2 = (1 << 6); // 6th bit set if older models
|
||||
|
||||
private:
|
||||
uint8_t unique_id_;
|
||||
uint8_t device_type_ = DeviceType::UNKNOWN;
|
||||
uint8_t device_id_ = 0;
|
||||
uint8_t product_id_ = 0;
|
||||
|
||||
@@ -39,9 +39,8 @@ ESP8266React EMSESP::esp8266React(&webServer, &dummyFS);
|
||||
EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
|
||||
#endif
|
||||
|
||||
EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
|
||||
EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
|
||||
EMSESPScanDevicesService EMSESP::emsespScanDevicesService = EMSESPScanDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
|
||||
EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
|
||||
EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
|
||||
|
||||
std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices
|
||||
std::vector<emsesp::EMSESP::Device_record> EMSESP::device_library_; // libary of all our known EMS devices so far
|
||||
@@ -63,6 +62,7 @@ uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; /
|
||||
uint8_t EMSESP::watch_ = 0; // trace off
|
||||
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
|
||||
uint32_t EMSESP::last_fetch_ = 0;
|
||||
uint8_t EMSESP::unique_id_count_ = 0;
|
||||
|
||||
// for a specific EMS device go and request data values
|
||||
// or if device_id is 0 it will fetch from all our known and active devices
|
||||
@@ -110,13 +110,17 @@ void EMSESP::watch_id(uint16_t watch_id) {
|
||||
|
||||
// change the tx_mode
|
||||
// resets all counters and bumps the UART
|
||||
void EMSESP::reset_tx(uint8_t const tx_mode) {
|
||||
txservice_.telegram_read_count(0);
|
||||
txservice_.telegram_write_count(0);
|
||||
txservice_.telegram_fail_count(0);
|
||||
if (tx_mode) {
|
||||
EMSuart::stop();
|
||||
EMSuart::start(tx_mode);
|
||||
void EMSESP::reset_tx() {
|
||||
// get the tx_mode
|
||||
uint8_t tx_mode;
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode = settings.tx_mode; });
|
||||
|
||||
EMSuart::stop();
|
||||
EMSuart::start(tx_mode);
|
||||
txservice_.start();
|
||||
|
||||
// force a fetch for all new values, unless Tx is set to off
|
||||
if (tx_mode != 0) {
|
||||
EMSESP::fetch_device_values();
|
||||
}
|
||||
}
|
||||
@@ -151,7 +155,22 @@ uint8_t EMSESP::bus_status() {
|
||||
// show the EMS bus status plus both Rx and Tx queues
|
||||
void EMSESP::show_ems(uuid::console::Shell & shell) {
|
||||
// EMS bus information
|
||||
if (rxservice_.bus_connected()) {
|
||||
switch (bus_status()) {
|
||||
case BUS_STATUS_OFFLINE:
|
||||
shell.printfln(F("EMS Bus is disconnected."));
|
||||
break;
|
||||
case BUS_STATUS_TX_ERRORS:
|
||||
shell.printfln(F("EMS Bus is connected, but Tx is not stable."));
|
||||
break;
|
||||
case BUS_STATUS_CONNECTED:
|
||||
default:
|
||||
shell.printfln(F("EMS Bus is connected."));
|
||||
break;
|
||||
}
|
||||
|
||||
shell.println();
|
||||
|
||||
if (bus_status() != BUS_STATUS_OFFLINE) {
|
||||
uint8_t success_rate = 0;
|
||||
if (rxservice_.telegram_error_count()) {
|
||||
success_rate = ((float)rxservice_.telegram_error_count() / (float)rxservice_.telegram_count()) * 100;
|
||||
@@ -165,8 +184,6 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
|
||||
shell.printfln(F(" #write requests sent: %d"), txservice_.telegram_write_count());
|
||||
shell.printfln(F(" #corrupted telegrams: %d (%d%%)"), rxservice_.telegram_error_count(), success_rate);
|
||||
shell.printfln(F(" #tx fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_fail_count());
|
||||
} else {
|
||||
shell.printfln(F("EMS Bus is disconnected."));
|
||||
}
|
||||
|
||||
shell.println();
|
||||
@@ -231,7 +248,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
|
||||
}
|
||||
}
|
||||
|
||||
// show Dallas sensors
|
||||
// show Dallas temperature sensors
|
||||
void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
if (sensor_devices().empty()) {
|
||||
return;
|
||||
@@ -240,7 +257,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
char valuestr[8] = {0}; // for formatting temp
|
||||
shell.printfln(F("External temperature sensors:"));
|
||||
for (const auto & device : sensor_devices()) {
|
||||
shell.printfln(F(" Sensor ID %s: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c_, 2));
|
||||
shell.printfln(F(" ID: %s, Temperature: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c, 2));
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
@@ -474,6 +491,20 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
return found;
|
||||
}
|
||||
|
||||
// calls the device handler's function to populate a json doc with device info
|
||||
void EMSESP::device_info(const uint8_t unique_id, JsonObject & root) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->unique_id() == unique_id) {
|
||||
root["deviceName"] = emsdevice->to_string_short(); // can;t use c_str() because of scope
|
||||
JsonArray data = root.createNestedArray("deviceData");
|
||||
emsdevice->device_info(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return true if we have this device already registered
|
||||
bool EMSESP::device_exists(const uint8_t device_id) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
@@ -513,6 +544,9 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
|
||||
// shell.printf(F("[factory ID: %d] "), device_class.first);
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
shell.printf(F("[id=%d] "), emsdevice->unique_id());
|
||||
#endif
|
||||
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
|
||||
if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) {
|
||||
shell.printf(F(" ** master device **"));
|
||||
@@ -584,6 +618,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
|
||||
} else {
|
||||
emsdevices.push_back(
|
||||
EMSFactory::add(device_p->device_type, device_id, device_p->product_id, version, uuid::read_flash_string(device_p->name), device_p->flags, brand));
|
||||
emsdevices.back()->unique_id(++unique_id_count_);
|
||||
LOG_DEBUG(F("Adding new device with device ID 0x%02X with product ID %d and version %s"), device_id, product_id, version.c_str());
|
||||
// go and fetch its data,
|
||||
fetch_device_values(device_id);
|
||||
@@ -629,14 +664,14 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
}
|
||||
|
||||
// are we waiting for a response from a recent Tx Read or Write?
|
||||
uint8_t op = EMSbus::tx_waiting();
|
||||
if (op != Telegram::Operation::NONE) {
|
||||
uint8_t tx_state = EMSbus::tx_state();
|
||||
if (tx_state != Telegram::Operation::NONE) {
|
||||
bool tx_successful = false;
|
||||
EMSbus::tx_waiting(Telegram::Operation::NONE); // reset Tx wait state
|
||||
EMSbus::tx_state(Telegram::Operation::NONE); // reset Tx wait state
|
||||
// txservice_.print_last_tx();
|
||||
|
||||
// if we're waiting on a Write operation, we want a single byte 1 or 4
|
||||
if ((op == Telegram::Operation::TX_WRITE) && (length == 1)) {
|
||||
if ((tx_state == Telegram::Operation::TX_WRITE) && (length == 1)) {
|
||||
if (first_value == TxService::TX_WRITE_SUCCESS) {
|
||||
LOG_DEBUG(F("Last Tx write successful"));
|
||||
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
|
||||
@@ -649,7 +684,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
txservice_.send_poll(); // close the bus
|
||||
txservice_.reset_retry_count();
|
||||
}
|
||||
} else if (op == Telegram::Operation::TX_READ) {
|
||||
} else if (tx_state == Telegram::Operation::TX_READ) {
|
||||
// got a telegram with data in it. See if the src/dest matches that from the last one we sent and continue to process it
|
||||
uint8_t src = data[0];
|
||||
uint8_t dest = data[1];
|
||||
@@ -664,13 +699,15 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
|
||||
// if Tx wasn't successful, retry or just give up
|
||||
if (!tx_successful) {
|
||||
txservice_.retry_tx(op, data, length);
|
||||
txservice_.retry_tx(tx_state, data, length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check for poll
|
||||
if (length == 1) {
|
||||
EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
char s[4];
|
||||
if (first_value & 0x80) {
|
||||
@@ -684,7 +721,6 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
// check for poll to us, if so send top message from Tx queue immediately and quit
|
||||
// if ht3 poll must be ems_bus_id else if Buderus poll must be (ems_bus_id | 0x80)
|
||||
if ((first_value ^ 0x80 ^ rxservice_.ems_mask()) == txservice_.ems_bus_id()) {
|
||||
EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active
|
||||
txservice_.send();
|
||||
}
|
||||
// send remote room temperature if active
|
||||
@@ -741,15 +777,17 @@ void EMSESP::loop() {
|
||||
|
||||
// if we're doing an OTA upload, skip MQTT and EMS
|
||||
if (system_.upload_status()) {
|
||||
#if defined(ESP32)
|
||||
delay(10); // slow down OTA update to avoid getting killed by task watchdog (task_wdt)
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
system_.loop(); // does LED and checks system health, and syslog service
|
||||
mqtt_.loop(); // starts mqtt, and sends out anything in the queue
|
||||
rxservice_.loop(); // process what ever is in the rx queue
|
||||
txservice_.loop(); // check that the Tx is all ok
|
||||
shower_.loop(); // check for shower on/off
|
||||
sensors_.loop(); // this will also send out via MQTT
|
||||
mqtt_.loop(); // sends out anything in the queue via MQTT
|
||||
console_.loop(); // telnet/serial console
|
||||
|
||||
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
||||
|
||||
16
src/emsesp.h
16
src/emsesp.h
@@ -37,7 +37,6 @@
|
||||
#include "EMSESPStatusService.h"
|
||||
#include "EMSESPDevicesService.h"
|
||||
#include "EMSESPSettingsService.h"
|
||||
#include "EMSESPScanDevicesService.h"
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "emsfactory.h"
|
||||
@@ -82,6 +81,8 @@ class EMSESP {
|
||||
static void send_raw_telegram(const char * data);
|
||||
static bool device_exists(const uint8_t device_id);
|
||||
|
||||
static void device_info(const uint8_t unique_id, JsonObject & root);
|
||||
|
||||
static uint8_t count_devices(const uint8_t device_type);
|
||||
|
||||
static uint8_t actual_master_thermostat();
|
||||
@@ -95,7 +96,7 @@ class EMSESP {
|
||||
|
||||
static void add_context_menus();
|
||||
|
||||
static void reset_tx(uint8_t const tx_mode);
|
||||
static void reset_tx();
|
||||
|
||||
static void incoming_telegram(uint8_t * data, const uint8_t length);
|
||||
|
||||
@@ -146,11 +147,10 @@ class EMSESP {
|
||||
static TxService txservice_;
|
||||
|
||||
// web controllers
|
||||
static ESP8266React esp8266React;
|
||||
static EMSESPSettingsService emsespSettingsService;
|
||||
static EMSESPStatusService emsespStatusService;
|
||||
static EMSESPDevicesService emsespDevicesService;
|
||||
static EMSESPScanDevicesService emsespScanDevicesService;
|
||||
static ESP8266React esp8266React;
|
||||
static EMSESPSettingsService emsespSettingsService;
|
||||
static EMSESPStatusService emsespStatusService;
|
||||
static EMSESPDevicesService emsespDevicesService;
|
||||
|
||||
static uuid::log::Logger logger() {
|
||||
return logger_;
|
||||
@@ -182,6 +182,8 @@ class EMSESP {
|
||||
static uint16_t watch_id_;
|
||||
static uint8_t watch_;
|
||||
static bool tap_water_active_;
|
||||
|
||||
static uint8_t unique_id_count_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -89,6 +89,7 @@ class ConcreteEMSFactory : EMSFactory {
|
||||
ConcreteEMSFactory(const uint8_t device_type) {
|
||||
EMSFactory::registerFactory(device_type, this);
|
||||
}
|
||||
|
||||
auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand) const
|
||||
-> EMSdevice * {
|
||||
return new DerivedClass(device_type, device_id, product_id, version, name, flags, brand);
|
||||
|
||||
195
src/mqtt.cpp
195
src/mqtt.cpp
@@ -35,7 +35,7 @@ std::string Mqtt::hostname_;
|
||||
uint8_t Mqtt::mqtt_qos_;
|
||||
uint16_t Mqtt::publish_time_;
|
||||
|
||||
std::vector<Mqtt::MQTTFunction> Mqtt::mqtt_functions_;
|
||||
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
||||
uint16_t Mqtt::mqtt_publish_fails_ = 0;
|
||||
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
|
||||
uint16_t Mqtt::mqtt_message_id_ = 0;
|
||||
@@ -51,62 +51,56 @@ Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMess
|
||||
packet_id_ = 0;
|
||||
}
|
||||
|
||||
MqttMessage::MqttMessage(uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
|
||||
MqttMessage::MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
|
||||
: operation(operation)
|
||||
, topic(topic)
|
||||
, payload(payload)
|
||||
, retain(retain) {
|
||||
}
|
||||
|
||||
Mqtt::MQTTFunction::MQTTFunction(uint8_t device_id, const std::string && topic, mqtt_function_p mqtt_function)
|
||||
Mqtt::MQTTSubFunction::MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction)
|
||||
: device_id_(device_id)
|
||||
, topic_(topic)
|
||||
, mqtt_function_(mqtt_function) {
|
||||
, mqtt_subfunction_(mqtt_subfunction) {
|
||||
}
|
||||
|
||||
// subscribe to an MQTT topic, and store the associated callback function
|
||||
void Mqtt::subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb) {
|
||||
/*
|
||||
// We don't want to store the whole topic string in our lookup, just the last cmd, as this can take up too much memory
|
||||
// strip out everything until the last /
|
||||
size_t topic_pos = topic.find_last_of("/"); // returns npos which is -1
|
||||
topic_pos += 1; // skip the /
|
||||
*/
|
||||
void Mqtt::subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb) {
|
||||
auto message = queue_subscribe_message(topic); // add subscription to queue. The hostname will automatically be appended
|
||||
|
||||
// convert the topic to it's full path, so either prefixed with the hostname unless hardcoded like 'homeassistant'
|
||||
char full_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
make_topic(full_topic, topic);
|
||||
if (message == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// the message will contain the full topic, with the hostname prefixed
|
||||
// check if we already have the topic subscribed, if so don't add it again
|
||||
bool exists = false;
|
||||
if (!mqtt_functions_.empty()) {
|
||||
for (const auto & mqtt_function : mqtt_functions_) {
|
||||
if ((mqtt_function.device_id_ == device_id) && (strcmp(mqtt_function.topic_.c_str(), full_topic) == 0)) {
|
||||
if (!mqtt_subfunctions_.empty()) {
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
if ((mqtt_subfunction.device_id_ == device_id) && (strcmp(mqtt_subfunction.topic_.c_str(), message->topic.c_str()) == 0)) {
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
mqtt_functions_.emplace_back(device_id, std::move(full_topic), cb); // register a call back function for a specific telegram type
|
||||
mqtt_subfunctions_.emplace_back(device_id, std::move(message->topic), cb); // register a call back function for a specific telegram type
|
||||
}
|
||||
|
||||
queue_subscribe_message(topic); // add subscription to queue
|
||||
}
|
||||
|
||||
// subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device
|
||||
void Mqtt::subscribe(const std::string & topic, mqtt_function_p cb) {
|
||||
void Mqtt::subscribe(const std::string & topic, mqtt_subfunction_p cb) {
|
||||
subscribe(0, topic, cb); // no device_id needed, if generic to EMS-ESP
|
||||
}
|
||||
|
||||
// resubscribe to all MQTT topics again
|
||||
void Mqtt::resubscribe() {
|
||||
if (mqtt_functions_.empty()) {
|
||||
if (mqtt_subfunctions_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto & mqtt_function : mqtt_functions_) {
|
||||
queue_subscribe_message(mqtt_function.topic_);
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
queue_message(Operation::SUBSCRIBE, mqtt_subfunction.topic_, "", false, true); // no payload, no topic prefixing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +114,7 @@ void Mqtt::loop() {
|
||||
}
|
||||
|
||||
uint32_t currentMillis = uuid::get_uptime();
|
||||
|
||||
// create publish messages for each of the EMS device values, adding to queue
|
||||
if (publish_time_ && (currentMillis - last_publish_ > publish_time_)) {
|
||||
last_publish_ = currentMillis;
|
||||
@@ -146,8 +141,8 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
|
||||
|
||||
// show subscriptions
|
||||
shell.printfln(F("MQTT subscriptions:"));
|
||||
for (const auto & mqtt_function : mqtt_functions_) {
|
||||
shell.printfln(F(" %s"), mqtt_function.topic_.c_str());
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
shell.printfln(F(" %s"), mqtt_subfunction.topic_.c_str());
|
||||
}
|
||||
shell.println();
|
||||
|
||||
@@ -206,19 +201,11 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
strlcpy(message, payload, len + 1);
|
||||
LOG_DEBUG(F("[DEBUG] Received %s => %s (length %d)"), topic, message, len);
|
||||
|
||||
/*
|
||||
// strip out everything until the last /
|
||||
char * topic_magnitude = strrchr(topic, '/');
|
||||
if (topic_magnitude != nullptr) {
|
||||
topic = topic_magnitude + 1;
|
||||
}
|
||||
*/
|
||||
|
||||
// see if we have this topic in our subscription list, then call its callback handler
|
||||
// note: this will pick the first topic that matches, so for multiple devices of the same type it's gonna fail. Not sure if this is going to be an issue?
|
||||
for (const auto & mf : mqtt_functions_) {
|
||||
for (const auto & mf : mqtt_subfunctions_) {
|
||||
if (strcmp(topic, mf.topic_.c_str()) == 0) {
|
||||
(mf.mqtt_function_)(message);
|
||||
(mf.mqtt_subfunction_)(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -229,15 +216,17 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
|
||||
// print all the topics related to a specific device_id
|
||||
void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id) {
|
||||
if (std::count_if(mqtt_functions_.cbegin(), mqtt_functions_.cend(), [=](MQTTFunction const & mqtt_function) { return device_id == mqtt_function.device_id_; })
|
||||
if (std::count_if(mqtt_subfunctions_.cbegin(),
|
||||
mqtt_subfunctions_.cend(),
|
||||
[=](MQTTSubFunction const & mqtt_subfunction) { return device_id == mqtt_subfunction.device_id_; })
|
||||
== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
shell.print(F(" Subscribed MQTT topics: "));
|
||||
for (const auto & mqtt_function : mqtt_functions_) {
|
||||
if (mqtt_function.device_id_ == device_id) {
|
||||
shell.printf(F("%s "), mqtt_function.topic_.c_str());
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
if (mqtt_subfunction.device_id_ == device_id) {
|
||||
shell.printf(F("%s "), mqtt_subfunction.topic_.c_str());
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
@@ -268,24 +257,7 @@ void Mqtt::on_publish(uint16_t packetId) {
|
||||
mqtt_messages_.pop_front(); // always remove from queue, regardless if there was a successful ACK
|
||||
}
|
||||
|
||||
// builds up a topic by prefixing the hostname
|
||||
// unless it's hardcoded like "homeassistant"
|
||||
char * Mqtt::make_topic(char * result, const std::string & topic) {
|
||||
// check for homesassistant
|
||||
if (strncmp(topic.c_str(), "homeassistant/", 13) == 0) {
|
||||
strlcpy(result, topic.c_str(), MQTT_TOPIC_MAX_SIZE);
|
||||
return result;
|
||||
}
|
||||
|
||||
strlcpy(result, hostname_.c_str(), MQTT_TOPIC_MAX_SIZE);
|
||||
strlcat(result, "/", MQTT_TOPIC_MAX_SIZE);
|
||||
strlcat(result, topic.c_str(), MQTT_TOPIC_MAX_SIZE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Mqtt::start() {
|
||||
|
||||
mqttClient_ = EMSESP::esp8266React.getMqttClient();
|
||||
|
||||
// get the hostname, which we'll use to prefix to all topics
|
||||
@@ -298,15 +270,36 @@ void Mqtt::start() {
|
||||
});
|
||||
|
||||
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
|
||||
mqttClient_->setWill(make_topic(will_topic_, "status"), 1, true, "offline"); // with qos 1, retain true
|
||||
|
||||
mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
|
||||
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
|
||||
LOG_INFO(F("MQTT disconnected: TCP"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
|
||||
LOG_INFO(F("MQTT disconnected: Identifier Rejected"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
|
||||
LOG_INFO(F("MQTT disconnected: Server unavailable"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
|
||||
LOG_INFO(F("MQTT disconnected: Malformed credentials"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
|
||||
LOG_INFO(F("MQTT disconnected: Not authorized"));
|
||||
}
|
||||
});
|
||||
|
||||
// create will_topic with the hostname prefixed. It has to be static because asyncmqttclient destroys the reference
|
||||
static char will_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
strlcpy(will_topic, hostname_.c_str(), MQTT_TOPIC_MAX_SIZE);
|
||||
strlcat(will_topic, "/", MQTT_TOPIC_MAX_SIZE);
|
||||
strlcat(will_topic, "status", MQTT_TOPIC_MAX_SIZE);
|
||||
mqttClient_->setWill(will_topic, 1, true, "offline"); // with qos 1, retain true
|
||||
|
||||
mqttClient_->onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
||||
on_message(topic, payload, len);
|
||||
mqttClient_->onPublish([this](uint16_t packetId) { on_publish(packetId); });
|
||||
});
|
||||
|
||||
// add the system MQTT subscriptions
|
||||
Mqtt::subscribe("cmd", System::mqtt_commands);
|
||||
// Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void Mqtt::set_publish_time(uint16_t publish_time) {
|
||||
@@ -334,47 +327,50 @@ void Mqtt::on_connect() {
|
||||
|
||||
resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics
|
||||
|
||||
// add the system MQTT subscriptions, only if its a fresh start with no previous subscriptions
|
||||
if (mqtt_subfunctions_.empty()) {
|
||||
Mqtt::subscribe("cmd", System::mqtt_commands);
|
||||
}
|
||||
|
||||
LOG_INFO(F("MQTT connected"));
|
||||
}
|
||||
|
||||
// add MQTT message to queue, payload is a string
|
||||
void Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) {
|
||||
// can't have bogus topics, but empty payloads are ok
|
||||
// add sub or pub task to the queue. When the message is created, the topic will have
|
||||
// automatically the hostname prefixed.
|
||||
std::shared_ptr<const MqttMessage>
|
||||
Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix) {
|
||||
if (topic.empty()) {
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// prefix the hostname to the topic
|
||||
char full_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
make_topic(full_topic, topic);
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(Operation::PUBLISH, full_topic, payload, retain);
|
||||
// take the topic and prefix the hostname, unless its for HA
|
||||
std::shared_ptr<MqttMessage> message;
|
||||
if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0) || no_prefix) {
|
||||
// leave topic as it is
|
||||
message = std::make_shared<MqttMessage>(operation, topic, payload, retain);
|
||||
} else {
|
||||
// prefix the hostname
|
||||
std::string full_topic = Mqtt::hostname_ + "/" + topic;
|
||||
message = std::make_shared<MqttMessage>(operation, full_topic, payload, retain);
|
||||
}
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
|
||||
mqtt_messages_.pop_front();
|
||||
}
|
||||
|
||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||
|
||||
return mqtt_messages_.back().content_; // this is because the message has been moved
|
||||
}
|
||||
|
||||
// add MQTT message to queue, payload is a string
|
||||
std::shared_ptr<const MqttMessage> Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) {
|
||||
return queue_message(Operation::PUBLISH, topic, payload, retain);
|
||||
}
|
||||
|
||||
// add MQTT subscribe message to queue
|
||||
void Mqtt::queue_subscribe_message(const std::string & topic) {
|
||||
if (topic.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(Operation::SUBSCRIBE, topic, "", false);
|
||||
#ifdef DEBUG
|
||||
LOG_DEBUG(F("Adding a subscription for %s"), topic.c_str());
|
||||
#endif
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
|
||||
mqtt_messages_.pop_front();
|
||||
}
|
||||
|
||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||
std::shared_ptr<const MqttMessage> Mqtt::queue_subscribe_message(const std::string & topic) {
|
||||
return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload
|
||||
}
|
||||
|
||||
// MQTT Publish, using a specific retain flag
|
||||
@@ -383,9 +379,8 @@ void Mqtt::publish(const std::string & topic, const std::string & payload, bool
|
||||
}
|
||||
|
||||
void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) {
|
||||
// convert json to string
|
||||
std::string payload_text;
|
||||
serializeJson(payload, payload_text);
|
||||
serializeJson(payload, payload_text); // convert json to string
|
||||
queue_publish_message(topic, payload_text, retain);
|
||||
}
|
||||
|
||||
@@ -413,6 +408,26 @@ void Mqtt::process_queue() {
|
||||
return;
|
||||
}
|
||||
|
||||
// show queue - Debug only
|
||||
/*
|
||||
Serial.printf("MQTT queue:\n\r");
|
||||
for (const auto & message : mqtt_messages_) {
|
||||
auto content = message.content_;
|
||||
if (content->operation == Operation::PUBLISH) {
|
||||
// Publish messages
|
||||
Serial.printf(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)\n\r",
|
||||
message.id_,
|
||||
content->topic.c_str(),
|
||||
content->payload.c_str(),
|
||||
message.packet_id_,
|
||||
message.retry_count_);
|
||||
} else {
|
||||
// Subscribe messages
|
||||
Serial.printf(" [%02d] (Sub) topic=%s\n\r", message.id_, content->topic.c_str());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// fetch first from queue and create the full topic name
|
||||
auto mqtt_message = mqtt_messages_.front();
|
||||
auto message = mqtt_message.content_;
|
||||
|
||||
36
src/mqtt.h
36
src/mqtt.h
@@ -43,11 +43,11 @@ using uuid::console::Shell;
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using mqtt_function_p = std::function<void(const char * message)>;
|
||||
using mqtt_subfunction_p = std::function<void(const char * message)>;
|
||||
using namespace std::placeholders; // for `_1`
|
||||
|
||||
struct MqttMessage {
|
||||
MqttMessage(uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
||||
MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
||||
~MqttMessage() = default;
|
||||
|
||||
const uint8_t operation;
|
||||
@@ -68,8 +68,8 @@ class Mqtt {
|
||||
|
||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100;
|
||||
|
||||
static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb);
|
||||
static void subscribe(const std::string & topic, mqtt_function_p cb);
|
||||
static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb);
|
||||
static void subscribe(const std::string & topic, mqtt_subfunction_p cb);
|
||||
static void resubscribe();
|
||||
|
||||
static void publish(const std::string & topic, const std::string & payload, bool retain = false);
|
||||
@@ -100,6 +100,8 @@ class Mqtt {
|
||||
mqtt_publish_fails_ = 0;
|
||||
}
|
||||
|
||||
static std::string hostname_;
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -125,36 +127,36 @@ class Mqtt {
|
||||
static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads
|
||||
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
|
||||
|
||||
static void queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
|
||||
static void queue_subscribe_message(const std::string & topic);
|
||||
static std::shared_ptr<const MqttMessage> queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix = false);
|
||||
static std::shared_ptr<const MqttMessage> queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
|
||||
static std::shared_ptr<const MqttMessage> queue_subscribe_message(const std::string & topic);
|
||||
|
||||
void on_publish(uint16_t packetId);
|
||||
void on_message(char * topic, char * payload, size_t len);
|
||||
static char * make_topic(char * result, const std::string & topic);
|
||||
void process_queue();
|
||||
void process_all_queue();
|
||||
|
||||
static uint16_t mqtt_publish_fails_;
|
||||
|
||||
class MQTTFunction {
|
||||
// function handlers for MQTT subscriptions
|
||||
class MQTTSubFunction {
|
||||
public:
|
||||
MQTTFunction(uint8_t device_id, const std::string && topic, mqtt_function_p mqtt_function);
|
||||
~MQTTFunction() = default;
|
||||
MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction);
|
||||
~MQTTSubFunction() = default;
|
||||
|
||||
uint8_t device_id_; // which device ID owns this
|
||||
std::string topic_;
|
||||
mqtt_function_p mqtt_function_;
|
||||
const uint8_t device_id_; // which device ID owns this
|
||||
const std::string topic_;
|
||||
mqtt_subfunction_p mqtt_subfunction_;
|
||||
};
|
||||
|
||||
static std::vector<MQTTFunction> mqtt_functions_; // list of mqtt subscribe callbacks for all devices
|
||||
static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices
|
||||
|
||||
uint32_t last_mqtt_poll_ = 0;
|
||||
uint32_t last_publish_ = 0;
|
||||
|
||||
// settings, copied over
|
||||
static std::string hostname_;
|
||||
static uint8_t mqtt_qos_;
|
||||
static uint16_t publish_time_;
|
||||
static uint8_t mqtt_qos_;
|
||||
static uint16_t publish_time_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -65,6 +65,10 @@ void Roomctrl::check(const uint8_t addr, const uint8_t * data) {
|
||||
if (hc_ > 3) {
|
||||
return;
|
||||
}
|
||||
// no reply if the temperature is not set
|
||||
if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) {
|
||||
return;
|
||||
}
|
||||
// reply to writes with write nack byte
|
||||
if (addr & 0x80) { // it's a write to us
|
||||
nack_write(); // we don't accept writes.
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
|
||||
MAKE_PSTR(logger_name, "sensors")
|
||||
|
||||
#ifdef ESP32
|
||||
#define YIELD
|
||||
#else
|
||||
#define YIELD yield()
|
||||
#endif
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON};
|
||||
@@ -63,10 +69,9 @@ void Sensors::loop() {
|
||||
if (time_now - last_activity_ >= READ_INTERVAL_MS) {
|
||||
// LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug
|
||||
if (bus_.reset()) {
|
||||
yield();
|
||||
YIELD;
|
||||
bus_.skip();
|
||||
bus_.write(CMD_CONVERT_TEMP);
|
||||
|
||||
state_ = State::READING;
|
||||
} else {
|
||||
// no sensors found
|
||||
@@ -80,20 +85,15 @@ void Sensors::loop() {
|
||||
// LOG_DEBUG(F("Scanning for sensors")); // uncomment for debug
|
||||
bus_.reset_search();
|
||||
found_.clear();
|
||||
|
||||
state_ = State::SCANNING;
|
||||
last_activity_ = time_now;
|
||||
state_ = State::SCANNING;
|
||||
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
|
||||
LOG_ERROR(F("Sensor read timeout"));
|
||||
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = time_now;
|
||||
state_ = State::IDLE;
|
||||
}
|
||||
} else if (state_ == State::SCANNING) {
|
||||
if (time_now - last_activity_ > SCAN_TIMEOUT_MS) {
|
||||
LOG_ERROR(F("Sensor scan timeout"));
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = time_now;
|
||||
state_ = State::IDLE;
|
||||
} else {
|
||||
uint8_t addr[ADDR_LEN] = {0};
|
||||
|
||||
@@ -107,7 +107,7 @@ void Sensors::loop() {
|
||||
case TYPE_DS1822:
|
||||
case TYPE_DS1825:
|
||||
found_.emplace_back(addr);
|
||||
found_.back().temperature_c_ = get_temperature_c(addr);
|
||||
found_.back().temperature_c = get_temperature_c(addr);
|
||||
|
||||
/*
|
||||
// comment out for debugging
|
||||
@@ -127,11 +127,15 @@ void Sensors::loop() {
|
||||
}
|
||||
} else {
|
||||
bus_.depower();
|
||||
devices_ = std::move(found_);
|
||||
if ((found_.size() >= devices_.size()) || (retrycnt_ > 5)) {
|
||||
devices_ = std::move(found_);
|
||||
retrycnt_ = 0;
|
||||
} else {
|
||||
retrycnt_++;
|
||||
}
|
||||
found_.clear();
|
||||
// LOG_DEBUG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = time_now;
|
||||
state_ = State::IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,17 +159,17 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
YIELD;
|
||||
uint8_t scratchpad[SCRATCHPAD_LEN] = {0};
|
||||
bus_.select(addr);
|
||||
bus_.write(CMD_READ_SCRATCHPAD);
|
||||
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
|
||||
yield();
|
||||
YIELD;
|
||||
if (!bus_.reset()) {
|
||||
LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
YIELD;
|
||||
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
|
||||
LOG_WARNING(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"),
|
||||
scratchpad[0],
|
||||
@@ -202,7 +206,8 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
return (float)raw_value / 16;
|
||||
uint32_t raw = (raw_value *625) / 100; // round to 0.01
|
||||
return (float)raw / 100;
|
||||
#else
|
||||
return NAN;
|
||||
#endif
|
||||
@@ -253,7 +258,7 @@ void Sensors::publish_values() {
|
||||
StaticJsonDocument<100> doc;
|
||||
for (const auto & device : devices_) {
|
||||
char s[5];
|
||||
doc["temp"] = Helpers::render_value(s, device.temperature_c_, 2);
|
||||
doc["temp"] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
char topic[60]; // sensors{1-n}
|
||||
strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F
|
||||
strlcat(topic, device.to_string().c_str(), 60);
|
||||
@@ -279,7 +284,7 @@ void Sensors::publish_values() {
|
||||
for (const auto & device : devices_) {
|
||||
if (mqtt_format_ == MQTT_format::CUSTOM) {
|
||||
char s[5];
|
||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c_, 2);
|
||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
} else {
|
||||
char sensorID[10]; // sensor{1-n}
|
||||
strlcpy(sensorID, "sensor", 10);
|
||||
@@ -287,7 +292,7 @@ void Sensors::publish_values() {
|
||||
strlcat(sensorID, Helpers::itoa(s, i++), 10);
|
||||
JsonObject dataSensor = doc.createNestedObject(sensorID);
|
||||
dataSensor["id"] = device.to_string();
|
||||
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c_, 2);
|
||||
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class Sensors {
|
||||
uint64_t id() const;
|
||||
std::string to_string() const;
|
||||
|
||||
float temperature_c_ = NAN;
|
||||
float temperature_c = NAN;
|
||||
|
||||
private:
|
||||
const uint64_t id_;
|
||||
@@ -90,7 +90,7 @@ class Sensors {
|
||||
static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds
|
||||
static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds
|
||||
static constexpr uint32_t READ_TIMEOUT_MS = 2000; // 2 seconds
|
||||
static constexpr uint32_t SCAN_TIMEOUT_MS = 30000; // 30 seconds
|
||||
static constexpr uint32_t SCAN_TIMEOUT_MS = 3000; // 3 seconds
|
||||
|
||||
static constexpr uint8_t CMD_CONVERT_TEMP = 0x44;
|
||||
static constexpr uint8_t CMD_READ_SCRATCHPAD = 0xBE;
|
||||
@@ -111,6 +111,8 @@ class Sensors {
|
||||
std::vector<Device> devices_;
|
||||
|
||||
uint8_t mqtt_format_;
|
||||
uint8_t retrycnt_ = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
122
src/system.cpp
122
src/system.cpp
@@ -24,8 +24,10 @@
|
||||
MAKE_PSTR_WORD(passwd)
|
||||
MAKE_PSTR_WORD(hostname)
|
||||
MAKE_PSTR_WORD(wifi)
|
||||
MAKE_PSTR_WORD(reconnect)
|
||||
MAKE_PSTR_WORD(ssid)
|
||||
MAKE_PSTR_WORD(heartbeat)
|
||||
MAKE_PSTR_WORD(users)
|
||||
|
||||
MAKE_PSTR(host_fmt, "Host = %s")
|
||||
MAKE_PSTR(hostname_fmt, "WiFi Hostname = %s")
|
||||
@@ -141,7 +143,6 @@ void System::mqtt_commands(const char * message) {
|
||||
// restart EMS-ESP
|
||||
void System::restart() {
|
||||
LOG_NOTICE("Restarting system...");
|
||||
|
||||
Shell::loop_all();
|
||||
delay(1000); // wait a second
|
||||
#if defined(ESP8266)
|
||||
@@ -151,10 +152,19 @@ void System::restart() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// saves all settings
|
||||
void System::wifi_reconnect() {
|
||||
LOG_NOTICE("The wifi will reconnect...");
|
||||
Shell::loop_all();
|
||||
delay(1000); // wait a second
|
||||
EMSESP::emsespSettingsService.save(); // local settings
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->callUpdateHandlers("local"); // in case we've changed ssid or password
|
||||
}
|
||||
|
||||
// format fs
|
||||
// format the FS. Wipes everything.
|
||||
void System::format(uuid::console::Shell & shell) {
|
||||
auto msg = F("Formatting file system. This will also reset all settings to their defaults");
|
||||
auto msg = F("Formatting file system. This will reset all settings to their defaults");
|
||||
shell.logger().warning(msg);
|
||||
shell.flush();
|
||||
|
||||
@@ -189,19 +199,17 @@ void System::syslog_init() {
|
||||
});
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
syslog_.start(); // syslog service
|
||||
syslog_.start(); // syslog service re-start
|
||||
|
||||
// configure syslog
|
||||
IPAddress addr;
|
||||
|
||||
if (!addr.fromString(syslog_host_.c_str())) {
|
||||
addr = (uint32_t)0;
|
||||
}
|
||||
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { syslog_.hostname(wifiSettings.hostname.c_str()); });
|
||||
syslog_.log_level((uuid::log::Level)syslog_level_);
|
||||
syslog_.mark_interval(syslog_mark_interval_);
|
||||
syslog_.destination(addr);
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { syslog_.hostname(wifiSettings.hostname.c_str()); });
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -220,32 +228,32 @@ void System::start() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// fetch settings
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode_ = settings.tx_mode; });
|
||||
// fetch system heartbeat
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat_ = settings.system_heartbeat; });
|
||||
|
||||
syslog_init(); // init SysLog
|
||||
// print boot message
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read(
|
||||
[&](WiFiSettings & wifiSettings) { LOG_INFO(F("System %s booted (EMS-ESP version %s)"), wifiSettings.hostname.c_str(), EMSESP_APP_VERSION); });
|
||||
|
||||
#if defined(ESP32)
|
||||
LOG_INFO(F("System booted (EMS-ESP version %s ESP32)"), EMSESP_APP_VERSION);
|
||||
#else
|
||||
LOG_INFO(F("System booted (EMS-ESP version %s)"), EMSESP_APP_VERSION);
|
||||
#endif
|
||||
syslog_init(); // init SysLog
|
||||
|
||||
if (LED_GPIO) {
|
||||
pinMode(LED_GPIO, OUTPUT); // LED pin, 0 means disabled
|
||||
}
|
||||
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode_ = settings.tx_mode; });
|
||||
#ifndef EMSESP_FORCE_SERIAL
|
||||
if (tx_mode_) {
|
||||
EMSuart::start(tx_mode_); // start UART, if tx_mode is not 0
|
||||
}
|
||||
EMSuart::start(tx_mode_); // start UART
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns true if OTA is uploading
|
||||
bool System::upload_status() {
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
return false;
|
||||
#else
|
||||
return upload_status_ || Update.isRunning();
|
||||
#endif
|
||||
}
|
||||
|
||||
void System::upload_status(bool in_progress) {
|
||||
@@ -372,10 +380,23 @@ int8_t System::wifi_quality() {
|
||||
return 2 * (dBm + 100);
|
||||
}
|
||||
|
||||
void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.print(F("Uptime: "));
|
||||
shell.print(uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3));
|
||||
// print users to console
|
||||
void System::show_users(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("Users:"));
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
|
||||
for (User user : securitySettings.users) {
|
||||
shell.printfln(F(" username: %s, password: %s, is_admin: %s"), user.username.c_str(), user.password.c_str(), user.admin ? F("yes") : F("no"));
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
shell.println();
|
||||
}
|
||||
|
||||
void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("Uptime: %s"), uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3).c_str());
|
||||
|
||||
#if defined(ESP8266)
|
||||
shell.printfln(F("Chip ID: 0x%08x"), ESP.getChipId());
|
||||
@@ -389,7 +410,7 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("Sketch size: %u bytes (%u bytes free)"), ESP.getSketchSize(), ESP.getFreeSketchSpace());
|
||||
shell.printfln(F("Reset reason: %s"), ESP.getResetReason().c_str());
|
||||
shell.printfln(F("Reset info: %s"), ESP.getResetInfo().c_str());
|
||||
|
||||
shell.println();
|
||||
shell.printfln(F("Free heap: %lu bytes"), (unsigned long)ESP.getFreeHeap());
|
||||
shell.printfln(F("Free mem: %d %%"), free_mem());
|
||||
shell.printfln(F("Maximum free block size: %lu bytes"), (unsigned long)ESP.getMaxFreeBlockSize());
|
||||
@@ -408,21 +429,19 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
switch (WiFi.status()) {
|
||||
case WL_IDLE_STATUS:
|
||||
shell.printfln(F("WiFi: idle"));
|
||||
shell.printfln(F("WiFi: Idle"));
|
||||
break;
|
||||
|
||||
case WL_NO_SSID_AVAIL:
|
||||
shell.printfln(F("WiFi: network not found"));
|
||||
shell.printfln(F("WiFi: Network not found"));
|
||||
break;
|
||||
|
||||
case WL_SCAN_COMPLETED:
|
||||
shell.printfln(F("WiFi: network scan complete"));
|
||||
shell.printfln(F("WiFi: Network scan complete"));
|
||||
break;
|
||||
|
||||
case WL_CONNECTED: {
|
||||
shell.printfln(F("WiFi: connected"));
|
||||
shell.println();
|
||||
|
||||
shell.printfln(F("WiFi: Connected"));
|
||||
shell.printfln(F("SSID: %s"), WiFi.SSID().c_str());
|
||||
shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str());
|
||||
shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality());
|
||||
@@ -432,27 +451,26 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
#elif defined(ESP32)
|
||||
shell.printfln(F("Hostname: %s"), WiFi.getHostname());
|
||||
#endif
|
||||
shell.println();
|
||||
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
|
||||
shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(WiFi.gatewayIP()).c_str());
|
||||
shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(WiFi.dnsIP()).c_str());
|
||||
} break;
|
||||
|
||||
case WL_CONNECT_FAILED:
|
||||
shell.printfln(F("WiFi: connection failed"));
|
||||
shell.printfln(F("WiFi: Connection failed"));
|
||||
break;
|
||||
|
||||
case WL_CONNECTION_LOST:
|
||||
shell.printfln(F("WiFi: connection lost"));
|
||||
shell.printfln(F("WiFi: Connection lost"));
|
||||
break;
|
||||
|
||||
case WL_DISCONNECTED:
|
||||
shell.printfln(F("WiFi: disconnected"));
|
||||
shell.printfln(F("WiFi: Disconnected"));
|
||||
break;
|
||||
|
||||
case WL_NO_SHIELD:
|
||||
default:
|
||||
shell.printfln(F("WiFi: unknown"));
|
||||
shell.printfln(F("WiFi: Unknown"));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -479,6 +497,13 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
restart();
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wifi), F_(reconnect)},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
wifi_reconnect();
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(format)},
|
||||
@@ -535,8 +560,9 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
flash_string_vector{F_(set), F_(wifi), F_(hostname)},
|
||||
flash_string_vector{F_(name_mandatory)},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
shell.println("Note, connection will be reset...");
|
||||
Console::loop();
|
||||
shell.println("The wifi connection will be reset...");
|
||||
Shell::loop_all();
|
||||
delay(1000); // wait a second
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.hostname = arguments.front().c_str();
|
||||
@@ -550,14 +576,11 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
flash_string_vector{F_(set), F_(wifi), F_(ssid)},
|
||||
flash_string_vector{F_(name_mandatory)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
shell.println("Note, connection will be reset...");
|
||||
Console::loop();
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.ssid = arguments.front().c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->updateWithoutPropagation([&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.ssid = arguments.front().c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
shell.println("Use `wifi reconnect` to apply the new settings");
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
@@ -570,13 +593,12 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
[password1](Shell & shell, bool completed, const std::string & password2) {
|
||||
if (completed) {
|
||||
if (password1 == password2) {
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->updateWithoutPropagation(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.password = password2.c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
shell.println(F("WiFi password updated"));
|
||||
});
|
||||
shell.println("Use `wifi reconnect` to apply the new settings");
|
||||
} else {
|
||||
shell.println(F("Passwords do not match"));
|
||||
}
|
||||
@@ -612,6 +634,11 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
flash_string_vector{F_(show), F_(mqtt)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(show), F_(users)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { System::show_users(shell); });
|
||||
|
||||
|
||||
// enter the context
|
||||
Console::enter_custom_context(shell, context);
|
||||
@@ -621,8 +648,7 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
void System::check_upgrade() {
|
||||
// check for v1.9. It uses SPIFFS and only on the ESP8266
|
||||
#if defined(ESP8266)
|
||||
|
||||
Serial.begin(115200); // TODO remove
|
||||
Serial.begin(115200); // TODO remove, just for debugging
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
@@ -50,8 +50,8 @@ class System {
|
||||
static void mqtt_commands(const char * message);
|
||||
|
||||
static uint8_t free_mem();
|
||||
static void upload_status(bool in_progress);
|
||||
static bool upload_status();
|
||||
static void upload_status(bool in_progress);
|
||||
static bool upload_status();
|
||||
|
||||
void syslog_init();
|
||||
|
||||
@@ -97,6 +97,8 @@ class System {
|
||||
void system_check();
|
||||
|
||||
static void show_system(uuid::console::Shell & shell);
|
||||
static void show_users(uuid::console::Shell & shell);
|
||||
static void wifi_reconnect();
|
||||
static int8_t wifi_quality();
|
||||
|
||||
bool system_healthy_ = false;
|
||||
|
||||
@@ -42,8 +42,8 @@ uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time
|
||||
bool EMSbus::bus_connected_ = false; // start assuming the bus hasn't been connected
|
||||
uint8_t EMSbus::ems_mask_ = EMS_MASK_UNSET; // unset so its triggered when booting, the its 0x00=buderus, 0x80=junker/ht3
|
||||
uint8_t EMSbus::ems_bus_id_ = EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
uint8_t EMSbus::tx_waiting_ = Telegram::Operation::NONE;
|
||||
bool EMSbus::tx_active_ = false;
|
||||
uint8_t EMSbus::tx_mode_ = EMSESP_DEFAULT_TX_MODE;
|
||||
uint8_t EMSbus::tx_state_ = Telegram::Operation::NONE;
|
||||
|
||||
uuid::log::Logger EMSbus::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
@@ -82,9 +82,10 @@ Telegram::Telegram(const uint8_t operation,
|
||||
|
||||
// returns telegram's message data bytes in hex
|
||||
std::string Telegram::to_string() const {
|
||||
if (message_length == 0) {
|
||||
if (this->message_length == 0) {
|
||||
return read_flash_string(F("<empty>"));
|
||||
}
|
||||
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
uint8_t length = 0;
|
||||
data[0] = this->src ^ RxService::ems_mask();
|
||||
@@ -100,8 +101,7 @@ std::string Telegram::to_string() const {
|
||||
data[2] = this->type_id;
|
||||
length = 5;
|
||||
}
|
||||
}
|
||||
if (this->operation == Telegram::Operation::TX_WRITE) {
|
||||
} else if (this->operation == Telegram::Operation::TX_WRITE) {
|
||||
data[1] = this->dest;
|
||||
if (this->type_id > 0xFF) {
|
||||
data[2] = 0xFF;
|
||||
@@ -115,7 +115,12 @@ std::string Telegram::to_string() const {
|
||||
for (uint8_t i = 0; i < this->message_length; i++) {
|
||||
data[length++] = this->message_data[i];
|
||||
}
|
||||
} else {
|
||||
for (uint8_t i = 0; i < this->message_length; i++) {
|
||||
data[length++] = this->message_data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return Helpers::data_to_hex(data, length);
|
||||
}
|
||||
|
||||
@@ -160,17 +165,18 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
|
||||
// validate the CRC
|
||||
uint8_t crc = calculate_crc(data, length - 1);
|
||||
|
||||
if ((data[length - 1] != crc) && (EMSESP::watch() != EMSESP::Watch::WATCH_OFF)) {
|
||||
LOG_ERROR(F("Rx: %s %s(CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET);
|
||||
if (data[length - 1] != crc) {
|
||||
increment_telegram_error_count();
|
||||
if (EMSESP::watch() != EMSESP::Watch::WATCH_OFF) {
|
||||
LOG_ERROR(F("Rx: %s %s(CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// since it's a valid telegram, work out the ems mask
|
||||
// we check the 1st byte, which assumed is the src ID and see if the MSB (8th bit) is set
|
||||
// this is used to identify if the protocol should be Junkers/HT3 or Buderus
|
||||
// this only happens once with the first rx telegram is processed
|
||||
// this only happens once with the first valid rx telegram is processed
|
||||
if (ems_mask() == EMS_MASK_UNSET) {
|
||||
ems_mask(data[0]);
|
||||
}
|
||||
@@ -251,48 +257,51 @@ void TxService::flush_tx_queue() {
|
||||
|
||||
// start and initialize Tx
|
||||
void TxService::start() {
|
||||
// grab the bus ID
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { ems_bus_id(settings.ems_bus_id); });
|
||||
// grab the bus ID and tx_mode
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
ems_bus_id(settings.ems_bus_id);
|
||||
tx_mode(settings.tx_mode);
|
||||
});
|
||||
|
||||
// reset counters
|
||||
telegram_read_count(0);
|
||||
telegram_write_count(0);
|
||||
telegram_fail_count(0);
|
||||
|
||||
// send first Tx request to bus master (boiler) for its registered devices
|
||||
// this will be added to the queue and sent during the first tx loop()
|
||||
read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
}
|
||||
|
||||
// Tx loop
|
||||
// here we check if the Tx is not full and report an error
|
||||
void TxService::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if ((uuid::get_uptime() - last_tx_check_) > TX_LOOP_WAIT) {
|
||||
last_tx_check_ = uuid::get_uptime();
|
||||
if (!tx_active() && (EMSbus::bus_connected())) {
|
||||
LOG_ERROR(F("Tx is not active. Please check settings and the circuit connection."));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// sends a 1 byte poll which is our own device ID
|
||||
void TxService::send_poll() {
|
||||
//LOG_DEBUG(F("Ack %02X"),ems_bus_id() ^ ems_mask());
|
||||
EMSuart::send_poll(ems_bus_id() ^ ems_mask());
|
||||
if (tx_mode()) {
|
||||
EMSuart::send_poll(ems_bus_id() ^ ems_mask());
|
||||
}
|
||||
}
|
||||
|
||||
// Process the next telegram on the Tx queue
|
||||
// This is sent when we receieve a poll request
|
||||
void TxService::send() {
|
||||
// don't process if we don't have a connection to the EMS bus
|
||||
// or we're in read-only mode
|
||||
if (!bus_connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there's nothing in the queue to transmit, send back a poll and quit
|
||||
// unless tx_mode is 0
|
||||
if (tx_telegrams_.empty()) {
|
||||
send_poll();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're in read-only mode (tx_mode 0) forget the Tx call
|
||||
if (tx_mode() == 0) {
|
||||
tx_telegrams_.pop_front();
|
||||
return;
|
||||
}
|
||||
|
||||
// send next telegram in the queue (which is actually a list!)
|
||||
send_telegram(tx_telegrams_.front());
|
||||
|
||||
@@ -380,12 +389,12 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
||||
|
||||
if (status == EMS_TX_STATUS_ERR) {
|
||||
LOG_ERROR(F("Failed to transmit Tx via UART."));
|
||||
increment_telegram_fail_count(); // another Tx fail
|
||||
tx_waiting(Telegram::Operation::NONE); // nothing send, tx not in wait state
|
||||
increment_telegram_fail_count(); // another Tx fail
|
||||
tx_state(Telegram::Operation::NONE); // nothing send, tx not in wait state
|
||||
return;
|
||||
}
|
||||
|
||||
tx_waiting(telegram->operation); // tx now in a wait state
|
||||
tx_state(telegram->operation); // tx now in a wait state
|
||||
}
|
||||
|
||||
// send an array of bytes as a telegram
|
||||
@@ -399,7 +408,7 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
|
||||
}
|
||||
telegram_raw[length] = calculate_crc(telegram_raw, length); // apppend CRC
|
||||
|
||||
tx_waiting(Telegram::Operation::NONE); // no post validation needed
|
||||
tx_state(Telegram::Operation::NONE); // no post validation needed
|
||||
|
||||
// send the telegram to the UART Tx
|
||||
uint16_t status = EMSuart::transmit(telegram_raw, length);
|
||||
|
||||
@@ -126,11 +126,38 @@ class EMSbus {
|
||||
public:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet
|
||||
static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3
|
||||
static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus
|
||||
static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet
|
||||
static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3
|
||||
static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus
|
||||
static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning
|
||||
|
||||
static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning
|
||||
static bool is_ht3() {
|
||||
return (ems_mask_ == EMS_MASK_HT3);
|
||||
}
|
||||
|
||||
static uint8_t ems_mask() {
|
||||
return ems_mask_;
|
||||
}
|
||||
|
||||
static void ems_mask(uint8_t ems_mask) {
|
||||
ems_mask_ = ems_mask & 0x80; // only keep the MSB (8th bit)
|
||||
}
|
||||
|
||||
static uint8_t tx_mode() {
|
||||
return tx_mode_;
|
||||
}
|
||||
|
||||
static void tx_mode(uint8_t tx_mode) {
|
||||
tx_mode_ = tx_mode;
|
||||
}
|
||||
|
||||
static uint8_t ems_bus_id() {
|
||||
return ems_bus_id_;
|
||||
}
|
||||
|
||||
static void ems_bus_id(uint8_t ems_bus_id) {
|
||||
ems_bus_id_ = ems_bus_id;
|
||||
}
|
||||
|
||||
static bool bus_connected() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -143,53 +170,17 @@ class EMSbus {
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool is_ht3() {
|
||||
return (ems_mask_ == EMS_MASK_HT3);
|
||||
}
|
||||
|
||||
static uint8_t protocol() {
|
||||
return ems_mask_;
|
||||
}
|
||||
|
||||
static uint8_t ems_mask() {
|
||||
return ems_mask_;
|
||||
}
|
||||
|
||||
static void ems_mask(uint8_t ems_mask) {
|
||||
ems_mask_ = ems_mask & 0x80; // only keep the MSB (8th bit)
|
||||
}
|
||||
|
||||
static uint8_t ems_bus_id() {
|
||||
return ems_bus_id_;
|
||||
}
|
||||
|
||||
static void ems_bus_id(uint8_t ems_bus_id) {
|
||||
ems_bus_id_ = ems_bus_id;
|
||||
}
|
||||
|
||||
// sets the flag for EMS bus connected
|
||||
static void last_bus_activity(uint32_t timestamp) {
|
||||
last_bus_activity_ = timestamp;
|
||||
bus_connected_ = true;
|
||||
}
|
||||
|
||||
static bool tx_active() {
|
||||
return tx_active_;
|
||||
static uint8_t tx_state() {
|
||||
return tx_state_;
|
||||
}
|
||||
static void tx_active(bool tx_active) {
|
||||
tx_active_ = tx_active;
|
||||
}
|
||||
|
||||
static uint8_t tx_waiting() {
|
||||
return tx_waiting_;
|
||||
}
|
||||
static void tx_waiting(uint8_t tx_waiting) {
|
||||
tx_waiting_ = tx_waiting;
|
||||
|
||||
// if NONE, then it's been reset which means we have an active Tx
|
||||
if ((tx_waiting == Telegram::Operation::NONE) && !(tx_active_)) {
|
||||
tx_active_ = true;
|
||||
}
|
||||
static void tx_state(uint8_t tx_state) {
|
||||
tx_state_ = tx_state;
|
||||
}
|
||||
|
||||
static uint8_t calculate_crc(const uint8_t * data, const uint8_t length);
|
||||
@@ -201,8 +192,8 @@ class EMSbus {
|
||||
static bool bus_connected_; // start assuming the bus hasn't been connected
|
||||
static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80
|
||||
static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings
|
||||
static uint8_t tx_waiting_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
|
||||
static bool tx_active_; // is true is we have a working Tx connection
|
||||
static uint8_t tx_mode_; // local copy of the tx mode
|
||||
static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
|
||||
};
|
||||
|
||||
class RxService : public EMSbus {
|
||||
@@ -269,7 +260,6 @@ class TxService : public EMSbus {
|
||||
~TxService() = default;
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
void send();
|
||||
|
||||
void add(const uint8_t operation,
|
||||
@@ -364,8 +354,7 @@ class TxService : public EMSbus {
|
||||
private:
|
||||
uint8_t tx_telegram_id_ = 0; // queue counter
|
||||
|
||||
static constexpr uint32_t TX_LOOP_WAIT = 10000; // when to check if Tx is up and running (10 sec)
|
||||
uint32_t last_tx_check_ = 0;
|
||||
uint32_t last_tx_check_ = 0;
|
||||
|
||||
std::deque<QueuedTxTelegram> tx_telegrams_;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
|
||||
#include "test.h"
|
||||
|
||||
@@ -692,3 +693,5 @@ void Test::dummy_mqtt_commands(const char * message) {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
|
||||
#ifndef EMSESP_TEST_H
|
||||
#define EMSESP_TEST_H
|
||||
|
||||
@@ -51,3 +53,5 @@ class Test {
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,8 +34,8 @@ static hw_timer_t * timer = NULL;
|
||||
bool drop_next_rx = true;
|
||||
uint8_t tx_mode_ = 0xFF;
|
||||
uint8_t emsTxBuf[EMS_MAXBUFFERSIZE];
|
||||
uint8_t emsTxBufIdx;
|
||||
uint8_t emsTxBufLen;
|
||||
uint8_t emsTxBufIdx = 0;
|
||||
uint8_t emsTxBufLen = 0;
|
||||
uint32_t emsTxWait;
|
||||
|
||||
/*
|
||||
@@ -88,12 +88,15 @@ void IRAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
|
||||
portENTER_CRITICAL(&mux);
|
||||
if (emsTxBufIdx < emsTxBufLen) {
|
||||
EMS_UART.fifo.rw_byte = emsTxBuf[emsTxBufIdx];
|
||||
if (emsTxBufIdx == 1) {
|
||||
timerAlarmWrite(timer, emsTxWait, true);
|
||||
}
|
||||
} else if (emsTxBufIdx == emsTxBufLen) {
|
||||
EMS_UART.conf0.txd_inv = 1;
|
||||
timerAlarmWrite(timer, EMSUART_TX_WAIT_BRK, true);
|
||||
timerAlarmWrite(timer, EMSUART_TX_BRK_TIMER, true);
|
||||
} else if (emsTxBufIdx == emsTxBufLen + 1) {
|
||||
// delayMicroseconds(EMSUART_TX_WAIT_BRK);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
emsTxBufLen = 0;
|
||||
timerAlarmDisable(timer);
|
||||
}
|
||||
emsTxBufIdx++;
|
||||
@@ -122,28 +125,31 @@ void EMSuart::start(const uint8_t tx_mode) {
|
||||
uart_set_pin(EMSUART_UART, EMSUART_TXPIN, EMSUART_RXPIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags
|
||||
EMS_UART.idle_conf.tx_brk_num = 11; // breaklength 11 bit
|
||||
EMS_UART.idle_conf.tx_brk_num = 10; // breaklength 10 bit
|
||||
EMS_UART.idle_conf.rx_idle_thrhd = 256;
|
||||
drop_next_rx = true;
|
||||
buf_handle = xRingbufferCreate(128, RINGBUF_TYPE_NOSPLIT);
|
||||
uart_isr_register(EMSUART_UART, emsuart_rx_intr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);
|
||||
xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
|
||||
timer = timerBegin(1, 80, true); // timer prescale to 1 µs, countup
|
||||
timer = timerBegin(0, 80, true); // timer prescale to 1 us, countup
|
||||
timerAttachInterrupt(timer, &emsuart_tx_timer_intr_handler, true); // Timer with edge interrupt
|
||||
restart();
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop, disables interrupt
|
||||
* Stop, disable interrupt
|
||||
*/
|
||||
void EMSuart::stop() {
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
// timerAlarmDisable(timer);
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
EMS_UART.conf0.txd_inv = 0; // stop break
|
||||
if (emsTxBufLen > 0) {
|
||||
timerAlarmDisable(timer);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Restart Interrupt
|
||||
* Restart uart and make mode dependent configs.
|
||||
*/
|
||||
void EMSuart::restart() {
|
||||
if (EMS_UART.int_raw.brk_det) { // we received a break in the meantime
|
||||
@@ -153,7 +159,11 @@ void EMSuart::restart() {
|
||||
EMS_UART.int_ena.brk_det = 1; // activate only break
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 0;
|
||||
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10);
|
||||
if (tx_mode_ > 100) {
|
||||
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ - 90);
|
||||
} else {
|
||||
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10);
|
||||
}
|
||||
if(tx_mode_ == EMS_TXMODE_NEW) {
|
||||
EMS_UART.conf0.txd_brk = 1;
|
||||
} else {
|
||||
@@ -161,44 +171,11 @@ void EMSuart::restart() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a 11-bit break by inverting the tx-port
|
||||
*/
|
||||
void EMSuart::tx_brk() {
|
||||
EMS_UART.conf0.txd_inv = 1;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_BRK);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a 1-byte poll, ending with a <BRK>
|
||||
*/
|
||||
void EMSuart::send_poll(const uint8_t data) {
|
||||
if (tx_mode_ > 5) { // timer controlled modes
|
||||
emsTxBuf[0] = data;
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 1;
|
||||
timerAlarmWrite(timer, emsTxWait, true); // start timer with autoreload
|
||||
timerAlarmEnable(timer); // first interrupt comes immediately
|
||||
} else if (tx_mode_ == EMS_TXMODE_DEFAULT) {
|
||||
volatile uint8_t _usrxc = EMS_UART.status.rxfifo_cnt;
|
||||
uint16_t timeoutcnt = EMSUART_TX_TIMEOUT;
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
while ((EMS_UART.status.rxfifo_cnt == _usrxc) && (--timeoutcnt > 0)) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT);
|
||||
}
|
||||
tx_brk();
|
||||
} else if (tx_mode_ == EMS_TXMODE_EMSPLUS) {
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
|
||||
tx_brk();
|
||||
} else if (tx_mode_ == EMS_TXMODE_HT3) {
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
tx_brk();
|
||||
} else {
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
}
|
||||
transmit(&data, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -217,7 +194,11 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
}
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = len;
|
||||
timerAlarmWrite(timer, emsTxWait, true); // start with autoreload
|
||||
if (tx_mode_ > 100 && len > 1) {
|
||||
timerAlarmWrite(timer, EMSUART_TX_WAIT_REPLY, true);
|
||||
} else {
|
||||
timerAlarmWrite(timer, emsTxWait, true); // start with autoreload
|
||||
}
|
||||
timerAlarmEnable(timer);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
@@ -234,7 +215,9 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
|
||||
}
|
||||
tx_brk();
|
||||
EMS_UART.conf0.txd_inv = 1; // send <brk>
|
||||
delayMicroseconds(EMSUART_TX_BRK_PLUS);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -243,11 +226,13 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
}
|
||||
tx_brk();
|
||||
EMS_UART.conf0.txd_inv = 1; // send <brk>
|
||||
delayMicroseconds(EMSUART_TX_BRK_HT3);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
// mode 1
|
||||
// mode 1: wait for echo after each byte
|
||||
// flush fifos -- not supported in ESP32 uart #2!
|
||||
// EMS_UART.conf0.rxfifo_rst = 1;
|
||||
// EMS_UART.conf0.txfifo_rst = 1;
|
||||
@@ -259,7 +244,9 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
|
||||
}
|
||||
}
|
||||
tx_brk();
|
||||
EMS_UART.conf0.txd_inv = 1;
|
||||
delayMicroseconds(EMSUART_TX_BRK_EMS);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,19 +46,25 @@
|
||||
#define EMS_TXMODE_NEW 4 // for michael's testing
|
||||
|
||||
// LEGACY
|
||||
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
|
||||
#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144
|
||||
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
|
||||
|
||||
// Timer controlled modes
|
||||
#define EMSUART_TX_BRK_TIMER (EMSUART_TX_BIT_TIME * 10 + 28) // 10.25 bit times
|
||||
#define EMSUART_TX_WAIT_REPLY 100000 // delay 100ms after first byte
|
||||
|
||||
// EMS 1.0
|
||||
#define EMSUART_TX_BUSY_WAIT (EMSUART_TX_BIT_TIME / 8) // 13
|
||||
#define EMSUART_TX_TIMEOUT (32 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT) // 256
|
||||
#define EMSUART_TX_TIMEOUT (20 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT)
|
||||
#define EMSUART_TX_BRK_EMS (EMSUART_TX_BIT_TIME * 10)
|
||||
|
||||
// HT3/Junkers - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) plus 7 bit delay. The -8 is for lag compensation.
|
||||
// since we use a faster processor the lag is negligible
|
||||
#define EMSUART_TX_WAIT_HT3 (EMSUART_TX_BIT_TIME * 17) // 1768
|
||||
#define EMSUART_TX_BRK_HT3 (EMSUART_TX_BIT_TIME * 11)
|
||||
|
||||
// EMS+ - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) and delay of another Bytetime.
|
||||
#define EMSUART_TX_WAIT_PLUS (EMSUART_TX_BIT_TIME * 20) // 2080
|
||||
#define EMSUART_TX_BRK_PLUS (EMSUART_TX_BIT_TIME * 11)
|
||||
|
||||
|
||||
// customize the GPIO pins for RX and TX here
|
||||
@@ -90,7 +96,6 @@ class EMSuart {
|
||||
static void emsuart_recvTask(void * para);
|
||||
static void IRAM_ATTR emsuart_rx_intr_handler(void * para);
|
||||
static void IRAM_ATTR emsuart_tx_timer_intr_handler();
|
||||
static void tx_brk();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -95,10 +95,10 @@ void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
|
||||
timer1_write(emsTxWait);
|
||||
} else if (emsTxBufIdx == emsTxBufLen) {
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
|
||||
timer1_write(EMSUART_TX_WAIT_BRK * 5);
|
||||
timer1_write(EMSUART_TX_BRK_TIMER);
|
||||
} else {
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset <BRK>
|
||||
sending_ = false;
|
||||
sending_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,6 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_flush_fifos() {
|
||||
* init UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode + 10); // bittimes wait between bytes
|
||||
if (tx_mode_ != 0xFF) { // it's a restart no need to configure uart
|
||||
tx_mode_ = tx_mode;
|
||||
restart();
|
||||
@@ -129,7 +128,6 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
}
|
||||
pEMSRxBuf = paEMSRxBuf[0]; // reset EMS Rx Buffer
|
||||
|
||||
ETS_UART_INTR_DISABLE();
|
||||
ETS_UART_INTR_ATTACH(nullptr, nullptr);
|
||||
|
||||
// pin settings
|
||||
@@ -149,22 +147,14 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127)
|
||||
// see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
|
||||
//
|
||||
// change: we set UCFFT to 1 to get an immediate indicator about incoming traffic.
|
||||
// Otherwise, we're only noticed by UCTOT or RxBRK!
|
||||
// change: don't care, we do not use these interrupts
|
||||
USC1(EMSUART_UART) = 0; // reset config first
|
||||
USC1(EMSUART_UART) = 0; // reset config
|
||||
// USC1(EMSUART_UART) = (0x7F << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts
|
||||
|
||||
// set interrupts for triggers
|
||||
USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
|
||||
USIE(EMSUART_UART) = 0; // disable all interrupts
|
||||
|
||||
// enable rx break, fifo full and timeout.
|
||||
// but not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes
|
||||
// change: we don't care about Rx Timeout - it may lead to wrong readouts
|
||||
// change:we don't care about Fifo full and read only on break-detect
|
||||
USIE(EMSUART_UART) = (1 << UIBD) | (0 << UIFF) | (0 << UITO);
|
||||
|
||||
// set up interrupt callbacks for Rx
|
||||
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen);
|
||||
|
||||
@@ -175,13 +165,12 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
system_uart_swap();
|
||||
|
||||
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
|
||||
// ETS_UART_INTR_ENABLE();
|
||||
drop_next_rx = true;
|
||||
|
||||
// for sending with large delay in EMS+ mode we use a timer interrupt
|
||||
timer1_attachInterrupt(emsuart_tx_timer_intr_handler); // Add ISR Function
|
||||
timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); // 5 MHz timer
|
||||
USIE(EMSUART_UART) = (1 << UIBD);
|
||||
timer1_attachInterrupt(emsuart_tx_timer_intr_handler);
|
||||
|
||||
restart();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -190,7 +179,9 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::stop() {
|
||||
USIE(EMSUART_UART) = 0;
|
||||
// timer1_disable();
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear BRK bit
|
||||
timer1_disable();
|
||||
sending_ = false;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -198,67 +189,25 @@ void ICACHE_FLASH_ATTR EMSuart::stop() {
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::restart() {
|
||||
if (USIR(EMSUART_UART) & ((1 << UIBD))) {
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the <brk> detect interrupt
|
||||
drop_next_rx = true;
|
||||
}
|
||||
if (tx_mode_ > 100) {
|
||||
emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode_ - 90);
|
||||
} else {
|
||||
emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode_ + 10); // bittimes wait to next bytes
|
||||
}
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 0;
|
||||
// timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE);
|
||||
USIE(EMSUART_UART) = (1 << UIBD);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a BRK signal
|
||||
* Which is a 11-bit set of zero's (11 cycles)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::tx_brk() {
|
||||
// make sure Tx FIFO is empty
|
||||
while (((USS(EMSUART_UART) >> USTXC) & 0xFF)) {
|
||||
}
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set bit
|
||||
// also for EMS+ there is no need to wait longer, we are finished and can free the bus.
|
||||
delayMicroseconds(EMSUART_TX_WAIT_BRK); // 1144
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear BRK bit
|
||||
timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE);
|
||||
USIE(EMSUART_UART) = (1 << UIBD); // enable <brk> interrupt
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a 1-byte poll, ending with a <BRK>
|
||||
* It's a bit dirty. there is no special wait logic per tx_mode type, fifo flushes or error checking
|
||||
*/
|
||||
void EMSuart::send_poll(uint8_t data) {
|
||||
// reset tx-brk, just in case it is accidentally set
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
sending_ = true;
|
||||
|
||||
if (tx_mode_ >= 5) { // timer controlled modes
|
||||
emsTxBuf[0] = data;
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 1;
|
||||
timer1_write(emsTxWait);
|
||||
} else if (tx_mode_ == EMS_TXMODE_NEW) { // hardware controlled modes
|
||||
USF(EMSUART_UART) = data;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK);
|
||||
} else if (tx_mode_ == EMS_TXMODE_HT3) {
|
||||
USF(EMSUART_UART) = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
} else if (tx_mode_ == EMS_TXMODE_EMSPLUS) {
|
||||
USF(EMSUART_UART) = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
} else {
|
||||
// tx_mode 1
|
||||
volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF;
|
||||
USF(EMSUART_UART) = data;
|
||||
uint16_t timeoutcnt = EMSUART_TX_TIMEOUT;
|
||||
while ((((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) && (--timeoutcnt > 0)) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
}
|
||||
void ICACHE_FLASH_ATTR EMSuart::send_poll(uint8_t data) {
|
||||
transmit(&data, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -270,19 +219,21 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
if (len == 0 || len >= EMS_MAXBUFFERSIZE) {
|
||||
return EMS_TX_STATUS_ERR; // nothing or to much to send
|
||||
}
|
||||
// reset tx-brk, just in case it is accidentally set
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
sending_ = true;
|
||||
|
||||
// timer controlled modes with extra delay
|
||||
if (tx_mode_ >= 5) {
|
||||
sending_ = true;
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
emsTxBuf[i] = buf[i];
|
||||
}
|
||||
USF(EMSUART_UART) = buf[0]; // send first byte
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = len;
|
||||
timer1_write(emsTxWait);
|
||||
if (tx_mode_ > 100 && len > 1) {
|
||||
timer1_write(EMSUART_TX_WAIT_REPLY); // large delay after first byte
|
||||
} else {
|
||||
timer1_write(emsTxWait);
|
||||
}
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -291,7 +242,7 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
}
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end, clear by interrupt
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -301,8 +252,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS); // 2070
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set break
|
||||
delayMicroseconds(EMSUART_TX_BRK_PLUS);
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -316,8 +268,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
// wait until bits are sent on wire
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set break bit
|
||||
delayMicroseconds(EMSUART_TX_BRK_HT3);
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -346,9 +299,7 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
*
|
||||
*/
|
||||
|
||||
// disable rx interrupt
|
||||
// clear Rx status register, resetting the Rx FIFO and flush it
|
||||
// ETS_UART_INTR_DISABLE();
|
||||
emsuart_flush_fifos();
|
||||
|
||||
// send the bytes along the serial line
|
||||
@@ -361,18 +312,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
|
||||
}
|
||||
}
|
||||
|
||||
// we got the whole telegram in the Rx buffer
|
||||
// on Rx-BRK (bus collision), we simply enable Rx and leave it
|
||||
// otherwise we send the final Tx-BRK
|
||||
// worst case, we'll see an additional Rx-BRK...
|
||||
// neither bus collision nor timeout - send terminating BRK signal
|
||||
if (!(USIS(EMSUART_UART) & (1 << UIBD))) {
|
||||
// no bus collision - send terminating BRK signal
|
||||
tx_brk();
|
||||
}
|
||||
// ETS_UART_INTR_ENABLE(); // open up the FIFO again to start receiving
|
||||
sending_ = false;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // snd break
|
||||
delayMicroseconds(EMSUART_TX_BRK_EMS);
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
return EMS_TX_STATUS_OK; // send the Tx ok status back
|
||||
}
|
||||
|
||||
|
||||
@@ -40,20 +40,26 @@
|
||||
#define EMS_TXMODE_NEW 4 // for michael's testing
|
||||
|
||||
// LEGACY
|
||||
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
|
||||
#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144
|
||||
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
|
||||
|
||||
// TIMER modes
|
||||
#define EMSUART_TX_BRK_TIMER (EMSUART_TX_BIT_TIME * 52) // > 10 bittimes for timer modes
|
||||
#define EMSUART_TX_WAIT_REPLY 500000 // delay 100ms after first byte
|
||||
|
||||
// EMS 1.0
|
||||
#define EMSUART_TX_BUSY_WAIT (EMSUART_TX_BIT_TIME / 8) // 13
|
||||
// #define EMSUART_TX_TIMEOUT (22 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT) // 176
|
||||
#define EMSUART_TX_TIMEOUT (32 * 8) // 256 for tx_mode 1 - see https://github.com/proddy/EMS-ESP/issues/398#issuecomment-645886277
|
||||
// #define EMSUART_TX_TIMEOUT (32 * 8) // 256 for tx_mode 1 - see https://github.com/proddy/EMS-ESP/issues/398#issuecomment-645886277
|
||||
#define EMSUART_TX_TIMEOUT (220 * 8) // 1760 as in v1.9 (180 ms)
|
||||
#define EMSUART_TX_BRK_EMS (EMSUART_TX_BIT_TIME * 10)
|
||||
|
||||
// HT3/Junkers - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) plus 7 bit delay. The -8 is for lag compensation.
|
||||
// since we use a faster processor the lag is negligible
|
||||
#define EMSUART_TX_WAIT_HT3 (EMSUART_TX_BIT_TIME * 17) // 1768
|
||||
#define EMSUART_TX_BRK_HT3 (EMSUART_TX_BIT_TIME * 11)
|
||||
|
||||
// EMS+ - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) and delay of another Bytetime.
|
||||
#define EMSUART_TX_WAIT_PLUS (EMSUART_TX_BIT_TIME * 20) // 2080
|
||||
#define EMSUART_TX_BRK_PLUS (EMSUART_TX_BIT_TIME * 11)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -83,7 +89,6 @@ class EMSuart {
|
||||
static void ICACHE_RAM_ATTR emsuart_rx_intr_handler(void * para);
|
||||
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events);
|
||||
static void ICACHE_FLASH_ATTR emsuart_flush_fifos();
|
||||
static void ICACHE_FLASH_ATTR tx_brk();
|
||||
static void ICACHE_RAM_ATTR emsuart_tx_timer_intr_handler();
|
||||
static bool sending_;
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "2.0.0b7"
|
||||
#define EMSESP_APP_VERSION "2.0.0b11"
|
||||
|
||||
Reference in New Issue
Block a user