This commit is contained in:
Johannes Wagner
2020-08-04 17:44:05 +02:00
81 changed files with 2205 additions and 2930 deletions

View File

@@ -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

View File

@@ -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\"

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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} />}

View File

@@ -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}&deg;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,8 +214,7 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
onScanDevicesConfirmed = () => {
this.setState({ processing: true });
redirectingAuthorizedFetch(SCANDEVICES_ENDPOINT, { method: 'POST' })
.then(response => {
redirectingAuthorizedFetch(SCANDEVICES_ENDPOINT).then(response => {
if (response.status === 200) {
this.props.enqueueSnackbar("Device scan is starting...", { variant: 'info' });
this.setState({ processing: false, confirmScanDevices: false });
@@ -171,11 +228,83 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
});
}
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}>

View File

@@ -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[];
}

View File

@@ -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;
}

View File

@@ -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.
#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

View File

@@ -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);

View File

@@ -1,9 +1,9 @@
#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) {
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) {
@@ -17,11 +17,9 @@ WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, Securit
// 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),
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);
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,11 +29,10 @@ 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() {
@@ -80,12 +77,16 @@ void WiFiSettingsService::manageSTA() {
// 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
}
// 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());
}
@@ -102,7 +103,7 @@ void WiFiSettingsService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t i
}
}
#elif defined(ESP8266)
void WiFiSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
void WiFiSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) {
WiFi.disconnect(true);
}
#endif

View File

@@ -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 */
}

View File

@@ -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

View File

@@ -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;
@@ -47,7 +42,6 @@ class ChunkPrint : public Print {
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;
}
};

View File

@@ -24,50 +24,73 @@ struct AsyncMqttClientMessageProperties {
bool retain;
};
namespace AsyncMqttClientInternals {
// user callbacks
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(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();
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;
@@ -77,67 +100,19 @@ class AsyncMqttClient {
uint32_t _lastPingRequestTime;
char _generatedClientId[18 + 1]; // esp8266-abc123 and esp32-abcdef123456
IPAddress _ip;
const char* _host;
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;
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();
};
#endif

View File

@@ -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

View File

@@ -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() {
@@ -83,6 +83,8 @@ class ESP8266React {
private:
DummySettingsService _settings;
SecuritySettingsService _securitySettingsService;
AsyncMqttClient * _mqttClient;
};
@@ -93,8 +95,6 @@ class EMSESPSettingsService {
void begin();
private:
// HttpEndpoint<EMSESPSettings> _httpEndpoint;
// FSPersistence<EMSESPSettings> _fsPersistence;
};
#endif

View File

@@ -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,89 +23,9 @@ 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;
@@ -124,61 +33,7 @@ class AsyncWebServerRequest {
private:
AsyncClient * _client;
AsyncWebServer * _server;
AsyncWebHandler * _handler;
AsyncWebServerResponse * _response;
ArDisconnectHandler _onDisconnectfn;
String _temp;
uint8_t _parseState;
uint8_t _version;
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,157 +44,30 @@ 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 * 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);
AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()) {
// AsyncWebServerResponse *a = new AsyncWebServerResponse()
return 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;
};
/*
* 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;
@@ -348,18 +76,7 @@ class 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,51 +97,18 @@ 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;
@@ -432,90 +116,22 @@ typedef std::function<void(AsyncWebServerRequest * request, uint8_t * data, size
class AsyncWebServer {
protected:
AsyncServer _server;
AsyncCallbackWebHandler * _catchAllHandler;
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

View File

@@ -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>();

View File

@@ -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);
}
};

View File

@@ -4,7 +4,6 @@
#include <Arduino.h>
#include <Features.h>
#include <ESPAsyncWebServer.h>
// #include <ESPUtils.h>
#include <AsyncJson.h>
#include <list>
@@ -27,36 +26,43 @@ class User {
bool admin;
public:
User(String username, String password, bool admin) : username(username), password(password), admin(admin) {
User(String username, String password, bool admin)
: username(username)
, password(password)
, admin(admin) {
}
};
class Authentication {
public:
User* user;
User * user;
boolean authenticated;
public:
Authentication(User& user) : user(new User(user)), authenticated(true) {
Authentication(User & user)
: user(new User(user))
, authenticated(true) {
}
Authentication() : user(nullptr), authenticated(false) {
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) {
static bool NONE_REQUIRED(Authentication & authentication) {
return true;
};
static bool IS_AUTHENTICATED(Authentication& authentication) {
static bool IS_AUTHENTICATED(Authentication & authentication) {
return authentication.authenticated;
};
static bool IS_ADMIN(Authentication& authentication) {
static bool IS_ADMIN(Authentication & authentication) {
return authentication.authenticated && authentication.user->admin;
};
};
@@ -64,39 +70,14 @@ class AuthenticationPredicates {
class SecurityManager {
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 Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
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 ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
};
#endif // end SecurityManager_h

View 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

View 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

View File

@@ -18,21 +18,23 @@ enum class StateUpdateResult {
};
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 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){};
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove)
: _id(++currentUpdatedHandlerId)
, _cb(cb)
, _allowRemove(allowRemove){};
} StateUpdateHandlerInfo_t;
template <class T>
@@ -40,11 +42,13 @@ class StatefulService {
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
@@ -67,7 +71,7 @@ class StatefulService {
}
}
StateUpdateResult update(std::function<StateUpdateResult(T&)> stateUpdater, const String& originId) {
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const String & originId) {
beginTransaction();
StateUpdateResult result = stateUpdater(_state);
endTransaction();
@@ -77,14 +81,14 @@ class StatefulService {
return result;
}
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T&)> stateUpdater) {
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) {
StateUpdateResult update(JsonObject & jsonObject, JsonStateUpdater<T> stateUpdater, const String & originId) {
beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction();
@@ -94,27 +98,27 @@ class StatefulService {
return result;
}
StateUpdateResult updateWithoutPropagation(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater) {
StateUpdateResult updateWithoutPropagation(JsonObject & jsonObject, JsonStateUpdater<T> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction();
return result;
}
void read(std::function<void(T&)> stateReader) {
void read(std::function<void(T &)> stateReader) {
beginTransaction();
stateReader(_state);
endTransaction();
}
void read(JsonObject& jsonObject, JsonStateReader<T> stateReader) {
void read(JsonObject & jsonObject, JsonStateReader<T> stateReader) {
beginTransaction();
stateReader(_state, jsonObject);
endTransaction();
}
void callUpdateHandlers(const String& originId) {
for (const StateUpdateHandlerInfo_t& updateHandler : _updateHandlers) {
void callUpdateHandlers(const String & originId) {
for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) {
updateHandler._cb(originId);
}
}

View File

@@ -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;
/* Copy as many bytes as will fit. */
if (nleft != 0) {
while (--nleft != 0) {
if ((*dst++ = *src++) == '\0')
break;
}
}
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++ = '-';
/* 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++)
;
}
*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++ = '.';
}
number -= digit;
number *= 10.0;
}
// make sure the string is terminated
*out = 0;
return s;
}
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;
* 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--;
}
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);
s++;
}
}
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());
}
*d = '\0';
double String::toDouble(void) const
{
if (buffer) return atof(buffer);
return 0;
return (dlen + (s - src)); /* count does not include NUL */
}

View File

@@ -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

View File

@@ -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

View File

@@ -1,3 +0,0 @@
[ZoneTransfer]
LastWriterPackageFamilyName=Microsoft.MSPaint_8wekyb3d8bbwe
ZoneId=3

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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["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;
}
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;
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

View File

@@ -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
@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,7 +191,7 @@ class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPSh
class Console {
public:
static void loop();
void loop();
void start();
uuid::log::Level log_level();

View File

@@ -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);
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,

View File

@@ -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,10 +121,21 @@ 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)
void process_UBAParameterWW(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);

View File

@@ -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() {
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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();

View File

@@ -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));
@@ -204,7 +230,7 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
}
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
telegram->read_bitvalue(collectorOnOff_, 3, 0);
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) {

View File

@@ -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;

View File

@@ -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

View File

@@ -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();

View File

@@ -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,10 +1549,29 @@ 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];
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
@@ -1480,7 +1579,8 @@ void Thermostat::set_datetime(const char * dt) {
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
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);

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -102,10 +102,20 @@ 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;
std::string to_string_short() const;
void show_telegram_handlers(uuid::console::Shell & shell);
void show_mqtt_handlers(uuid::console::Shell & shell);
@@ -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;

View File

@@ -41,7 +41,6 @@ EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&web
EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
EMSESPScanDevicesService EMSESP::emsespScanDevicesService = EMSESPScanDevicesService(&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) {
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)

View File

@@ -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);
@@ -150,7 +151,6 @@ class EMSESP {
static EMSESPSettingsService emsespSettingsService;
static EMSESPStatusService emsespStatusService;
static EMSESPDevicesService emsespDevicesService;
static EMSESPScanDevicesService emsespScanDevicesService;
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

View File

@@ -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);

View File

@@ -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_;

View File

@@ -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,34 +127,34 @@ 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_;
};

View File

@@ -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.

View File

@@ -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;
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
LOG_ERROR(F("Sensor read timeout"));
state_ = State::IDLE;
last_activity_ = time_now;
}
} 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;
} 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();
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;
}
}
}
@@ -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);
}
}

View File

@@ -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

View File

@@ -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) {
EMSESP::esp8266React.getWiFiSettingsService()->updateWithoutPropagation([&](WiFiSettings & wifiSettings) {
wifiSettings.ssid = arguments.front().c_str();
return StateUpdateResult::CHANGED;
},
"local");
});
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"

View File

@@ -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;

View File

@@ -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());
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());
@@ -381,11 +390,11 @@ 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
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);

View File

@@ -129,9 +129,36 @@ class EMSbus {
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 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
if ((uuid::get_uptime() - last_bus_activity_) > EMS_BUS_TIMEOUT) {
@@ -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 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 uint8_t tx_state() {
return tx_state_;
}
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,7 +354,6 @@ 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;
std::deque<QueuedTxTelegram> tx_telegrams_;

View File

@@ -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

View File

@@ -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

View File

@@ -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.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;
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;
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;
}

View File

@@ -47,18 +47,24 @@
// LEGACY
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144
// 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

View File

@@ -95,7 +95,7 @@ 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;
@@ -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;
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
}

View File

@@ -41,19 +41,25 @@
// LEGACY
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144
// 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_;
};

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.0.0b7"
#define EMSESP_APP_VERSION "2.0.0b11"