mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-09-13 13:44:10 +00:00
+11
-2
@@ -11,12 +11,21 @@ For more details go to [emsesp.org](https://emsesp.org/).
|
||||
## Fixed
|
||||
|
||||
- signed value for solarInfuence [#3077](https://github.com/emsesp/EMS-ESP32/issues/3077)
|
||||
- set bin file upload limit to 1M again [3086](https://github.com/emsesp/EMS-ESP32/issues/3086)
|
||||
- set bin file upload limit to 1M again [#3086](https://github.com/emsesp/EMS-ESP32/issues/3086)
|
||||
- check arithmetric operations on strings [#3127](https://github.com/emsesp/EMS-ESP32/discussions/3127)
|
||||
- prevent system crash during WiFi network discovery on ESP32-S3 hardware [#3128](https://github.com/emsesp/EMS-ESP32/pull/3128)
|
||||
- fix setting date/time on Junkers thermostats
|
||||
- offset of hpminflowtemp [#3144](https://github.com/emsesp/EMS-ESP32/issues/3144)
|
||||
- water circulation command [#3149](https://github.com/emsesp/EMS-ESP32/pull/3149)
|
||||
- start scheduler after customEntities, delay scheduler loop after startup, handling of `system/restart` command [#3146](https://github.com/emsesp/EMS-ESP32/issues/3146)
|
||||
- possible memory leaks fixed.
|
||||
- repeated startup schedules compute value
|
||||
- call `system/message` called from scheduler processes compute twice
|
||||
|
||||
## Changed
|
||||
|
||||
- call compute value direct, enlarge TCP stack [#3127](https://github.com/emsesp/EMS-ESP32/discussions/3127)
|
||||
- Dewtemperature for Easycontrol calculated by ems-esp [3135](https://github.com/emsesp/EMS-ESP32/issues/3135)
|
||||
- Dewtemperature for Easycontrol calculated by ems-esp [#3135](https://github.com/emsesp/EMS-ESP32/issues/3135)
|
||||
- add option for sync thermostat to ntp, allow different time zones [#3148](https://github.com/emsesp/EMS-ESP32/discussions/3148), **defaults to sync**.
|
||||
- block too many GET requests [mentioned in #3104](https://github.com/emsesp/EMS-ESP32/issues/3104)
|
||||
- set 10 sec timeout for http requests [#3104](https://github.com/emsesp/EMS-ESP32/issues/3104)
|
||||
|
||||
@@ -8146,7 +8146,7 @@ uint8
|
||||
| datetime | date/time | string | | false | DEVICE_DATA | 0 | 13 | 1 |
|
||||
| outdoortemp | outside temperature | int16 | C | false | DEVICE_DATA | 13 | 1 | 1/10 |
|
||||
| src1.currtemp | current room temperature | int16 | C | false | SRC | 0 | 1 | 1/10 |
|
||||
| src1.airhumidity | relative air humidity | int8 | % | false | SRC | 1 | 1 | 1 |
|
||||
| src1.airhumidity | relative air humidity | uint8 | % | false | SRC | 1 | 1 | 1 |
|
||||
| src1.dewtemperature | dew point temperature | int16 | C | false | SRC | 2 | 1 | 1/10 |
|
||||
uint8
|
||||
| src1.seltemp | selected room temperature | uint8 (>=5<=30) | C | true | SRC | 3 | 1 | 1/2 |
|
||||
|
||||
@@ -5861,7 +5861,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
|
||||
"MX400",connect,17,datetime,date/time,string, ,false,sensor.connect_date/time,sensor.connect_datetime,13,0,1,0,13
|
||||
"MX400",connect,17,outdoortemp,outside temperature,int16,C,false,sensor.connect_outside_temperature,sensor.connect_outdoortemp,13,0,1/10,13,1
|
||||
"MX400",connect,17,src1.currtemp,current room temperature,int16,C,false,sensor.connect_src1_current_room_temperature,sensor.connect_src1_currtemp,13,36,1/10,0,1
|
||||
"MX400",connect,17,src1.airhumidity,relative air humidity,int8,%,false,sensor.connect_src1_relative_air_humidity,sensor.connect_src1_airhumidity,13,36,1,1,1
|
||||
"MX400",connect,17,src1.airhumidity,relative air humidity,uint8,%,false,sensor.connect_src1_relative_air_humidity,sensor.connect_src1_airhumidity,13,36,1,1,1
|
||||
"MX400",connect,17,src1.dewtemperature,dew point temperature,int16,C,false,sensor.connect_src1_dew_point_temperature,sensor.connect_src1_dewtemperature,13,36,1/10,2,1
|
||||
"MX400",connect,17,src1.seltemp,selected room temperature,uint8 (>=5<=30),C,true,number.connect_src1_selected_room_temperature,number.connect_src1_seltemp,13,36,1/2,3,1
|
||||
"MX400",connect,17,src1.mode,operating mode,enum [off\|manual\|auto] (>=0<=0), ,true,select.connect_src1_operating_mode,select.connect_src1_mode,13,36,1,4,1
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -24,6 +24,7 @@ build_flags =
|
||||
-D FACTORY_NTP_TIME_ZONE_LABEL=\"Europe/Amsterdam\"
|
||||
-D FACTORY_NTP_TIME_ZONE_FORMAT=\"CET-1CEST,M3.5.0,M10.5.0/3\"
|
||||
-D FACTORY_NTP_SERVER=\"time.google.com\"
|
||||
-D FACTORY_NTP_THERMOSTAT_SYNC=0
|
||||
|
||||
; MQTT settings
|
||||
-D FACTORY_MQTT_ENABLED=false
|
||||
|
||||
+17
-17
@@ -26,8 +26,8 @@
|
||||
"@alova/adapter-xhr": "2.3.1",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@mui/icons-material": "^9.0.1",
|
||||
"@mui/material": "^9.0.1",
|
||||
"@mui/icons-material": "^9.2.0",
|
||||
"@mui/material": "^9.2.0",
|
||||
"@preact/compat": "^18.3.2",
|
||||
"@table-library/react-table-library": "4.1.15",
|
||||
"alova": "3.5.1",
|
||||
@@ -37,33 +37,33 @@
|
||||
"jwt-decode": "^4.0.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"mime-types": "^3.0.2",
|
||||
"preact": "^10.29.1",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-router": "^7.15.0",
|
||||
"preact": "^10.29.7",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-router": "^7.18.1",
|
||||
"react-toastify": "^11.1.0",
|
||||
"typesafe-i18n": "^5.27.1",
|
||||
"typescript": "^6.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/core": "^7.29.7",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@preact/compat": "^18.3.2",
|
||||
"@preact/preset-vite": "^2.10.5",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"@types/node": "^25.7.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/node": "^25.9.5",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"axe-core": "^4.11.4",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^10.3.0",
|
||||
"axe-core": "^4.12.1",
|
||||
"concurrently": "^9.2.4",
|
||||
"eslint": "^10.7.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier": "^3.9.5",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"terser": "^5.47.1",
|
||||
"typescript-eslint": "^8.59.3",
|
||||
"vite": "^8.0.12",
|
||||
"terser": "^5.49.0",
|
||||
"typescript-eslint": "^8.63.0",
|
||||
"vite": "^8.1.4",
|
||||
"vite-plugin-imagemin": "^0.6.1",
|
||||
"vite-tsconfig-paths": "^6.1.1"
|
||||
},
|
||||
|
||||
Generated
+647
-640
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Grid,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
@@ -39,7 +40,7 @@ import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
||||
import { ValidationError, validate } from 'validators';
|
||||
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
||||
|
||||
import { TIME_ZONES, selectedTimeZone, useTimeZoneSelectItems } from './TZ';
|
||||
import { TIME_ZONES, selectedTimeZone, useTimeZoneSelectItems, timeZoneSelectItemsT } from './TZ';
|
||||
|
||||
const NTPSettings = () => {
|
||||
const {
|
||||
@@ -63,13 +64,17 @@ const NTPSettings = () => {
|
||||
|
||||
// Memoized timezone select items for better performance
|
||||
const timeZoneItems = useTimeZoneSelectItems();
|
||||
const timeZoneItemsT = timeZoneSelectItemsT();
|
||||
|
||||
// Memoized selected timezone value
|
||||
const selectedTzValue = useMemo(
|
||||
() => (data ? selectedTimeZone(data.tz_label, data.tz_format) : undefined),
|
||||
[data?.tz_label, data?.tz_format]
|
||||
);
|
||||
|
||||
const selectedTzValueT = useMemo(
|
||||
() => (data ? selectedTimeZone(data.tz_label_t, data.tz_format_t) : undefined),
|
||||
[data?.tz_label_t, data?.tz_format_t]
|
||||
);
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
@@ -150,6 +155,18 @@ const NTPSettings = () => {
|
||||
[updateFormValue]
|
||||
);
|
||||
|
||||
const changeTimeZoneT = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
|
||||
...settings,
|
||||
tz_label_t: event.target.value,
|
||||
tz_format_t: TIME_ZONES[event.target.value]
|
||||
}));
|
||||
updateFormValue(event);
|
||||
},
|
||||
[updateFormValue]
|
||||
);
|
||||
|
||||
// Memoize render content to prevent unnecessary re-renders
|
||||
const renderContent = useMemo(() => {
|
||||
if (!data) {
|
||||
@@ -174,6 +191,7 @@ const NTPSettings = () => {
|
||||
label={LL.NTP_SERVER()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!data.enabled}
|
||||
value={data.server}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
@@ -192,7 +210,45 @@ const NTPSettings = () => {
|
||||
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
||||
{timeZoneItems}
|
||||
</ValidatedTextField>
|
||||
|
||||
{data.enabled && (
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="thermostat_sync"
|
||||
label="Sync EMS-Thermostat"
|
||||
variant="outlined"
|
||||
sx={{ width: '30ch' }}
|
||||
value={data.thermostat_sync}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>{LL.OFF()}</MenuItem>
|
||||
<MenuItem value={1}>{LL.ON()}</MenuItem >
|
||||
<MenuItem value={2} >{LL.USE()} {LL.TIME_ZONE()}</MenuItem >
|
||||
</ValidatedTextField >
|
||||
</Grid>
|
||||
<Grid>
|
||||
{data.thermostat_sync === 2 && (
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="tz_label_t"
|
||||
label={LL.TIME_ZONE() + ' Thermostat'}
|
||||
sx={{ width: '30ch' }}
|
||||
variant="outlined"
|
||||
value={selectedTzValueT}
|
||||
onChange={changeTimeZoneT}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
||||
{timeZoneItemsT}
|
||||
</ValidatedTextField>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
|
||||
{!data.enabled && !dirtyFlags.length && (
|
||||
<Box sx={{ flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
|
||||
@@ -243,6 +299,7 @@ const NTPSettings = () => {
|
||||
updateFormValue,
|
||||
fieldErrors,
|
||||
selectedTzValue,
|
||||
selectedTzValueT,
|
||||
changeTimeZone,
|
||||
timeZoneItems,
|
||||
dirtyFlags,
|
||||
|
||||
@@ -495,3 +495,7 @@ const precomputedTimeZoneItems = TIME_ZONE_LABELS.map((label) => (
|
||||
export function timeZoneSelectItems() {
|
||||
return precomputedTimeZoneItems;
|
||||
}
|
||||
|
||||
export function timeZoneSelectItemsT() {
|
||||
return precomputedTimeZoneItems;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ export interface NTPSettingsType {
|
||||
server: string;
|
||||
tz_label: string;
|
||||
tz_format: string;
|
||||
thermostat_sync: number;
|
||||
tz_label_t: string;
|
||||
tz_format_t: string;
|
||||
}
|
||||
|
||||
export interface Time {
|
||||
|
||||
@@ -81,6 +81,10 @@ class DummySettings {
|
||||
// NTP
|
||||
String server = "pool.ntp.org";
|
||||
String tzLabel = "Europe/London";
|
||||
String tzFormat = "GMT0BST,M3.5.0/1,M10.5.0";
|
||||
String tzLabelT = "Europe/Berlin";
|
||||
String tzFormatT = "CET-1CEST,M3.5.0,M10.5.0/3";
|
||||
uint8_t thermostat_sync = 0;
|
||||
|
||||
static void read(DummySettings & settings, JsonObject root) {};
|
||||
static void read(DummySettings & settings) {};
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson @ 7.4.3
|
||||
ESP32Async/AsyncTCP @ 3.4.10
|
||||
ESP32Async/ESPAsyncWebServer @ 3.11.0
|
||||
ESP32Async/ESPAsyncWebServer @ 3.11.2
|
||||
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
|
||||
|
||||
; builds the web interface only, not the firmware
|
||||
|
||||
@@ -90,6 +90,9 @@ void NTPSettings::read(NTPSettings & settings, JsonObject root) {
|
||||
root["server"] = settings.server;
|
||||
root["tz_label"] = settings.tzLabel;
|
||||
root["tz_format"] = settings.tzFormat;
|
||||
root["thermostat_sync"] = settings.thermostat_sync;
|
||||
root["tz_label_t"] = settings.tzLabelT;
|
||||
root["tz_format_t"] = settings.tzFormatT;
|
||||
}
|
||||
|
||||
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
|
||||
@@ -97,5 +100,8 @@ StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
|
||||
settings.server = root["server"] | FACTORY_NTP_SERVER;
|
||||
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||
settings.thermostat_sync = root["thermostat_sync"] | FACTORY_NTP_THERMOSTAT_SYNC;
|
||||
settings.tzLabelT = root["tz_label_t"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
settings.tzFormatT = root["tz_format_t"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -19,6 +19,10 @@
|
||||
#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_NTP_THERMOSTAT_SYNC
|
||||
#define FACTORY_NTP_THERMOSTAT_SYNC 1
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_NTP_SERVER
|
||||
#define FACTORY_NTP_SERVER "time.google.com"
|
||||
#endif
|
||||
@@ -34,6 +38,9 @@ class NTPSettings {
|
||||
String tzLabel = FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
String tzFormat = FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||
String server = FACTORY_NTP_SERVER;
|
||||
uint8_t thermostat_sync = FACTORY_NTP_THERMOSTAT_SYNC;
|
||||
String tzLabelT = FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
String tzFormatT = FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||
|
||||
static void read(NTPSettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, NTPSettings & settings);
|
||||
|
||||
@@ -546,11 +546,13 @@ bool AnalogSensor::update(uint8_t gpio, const char * org_name, double offset, do
|
||||
if (deleted) {
|
||||
LOG_DEBUG("Removing analog sensor GPIO %02d", gpio);
|
||||
EMSESP::system_.remove_gpio(gpio); // remove from used list only
|
||||
if (EMSESP::nvs_.isKey(AnalogCustomization.name)) {
|
||||
EMSESP::nvs_.remove(AnalogCustomization.name);
|
||||
}
|
||||
settings.analogCustomizations.remove(AnalogCustomization);
|
||||
} else {
|
||||
// update existing record
|
||||
if (!strcmp(name, AnalogCustomization.name)) {
|
||||
if (!strcmp(name, AnalogCustomization.name) && EMSESP::nvs_.isKey(AnalogCustomization.name)) {
|
||||
EMSESP::nvs_.remove(AnalogCustomization.name);
|
||||
}
|
||||
strlcpy(AnalogCustomization.name, name, sizeof(AnalogCustomization.name));
|
||||
|
||||
+1
-1
@@ -1800,8 +1800,8 @@ void EMSESP::start() {
|
||||
LOG_INFO("Library loaded: %d EMS devices, %d device entities, %s", device_library_.size(), EMSESP_TRANSLATION_COUNT, system_.languages_string().c_str());
|
||||
|
||||
webCustomizationService.begin(); // load the customizations
|
||||
webSchedulerService.begin(); // load the scheduler events
|
||||
webCustomEntityService.begin(); // load the custom telegram reads
|
||||
webSchedulerService.begin(); // load the scheduler events
|
||||
|
||||
// start telnet service if it's enabled
|
||||
// default idle is 10 minutes, default write timeout is 0 (automatic)
|
||||
|
||||
@@ -1033,4 +1033,17 @@ float Helpers::numericoperator2scalefactor(int8_t numeric_operator) {
|
||||
return -numeric_operator;
|
||||
}
|
||||
|
||||
int16_t Helpers::calc_dew(int16_t temp, uint8_t humi) {
|
||||
if (humi == EMS_VALUE_UINT8_NOTSET || temp == EMS_VALUE_INT16_NOTSET) {
|
||||
return EMS_VALUE_INT16_NOTSET;
|
||||
}
|
||||
const float k2 = 17.62;
|
||||
const float k3 = 243.12;
|
||||
const float t = (float)temp / 10;
|
||||
const float h = (float)humi / 100;
|
||||
int16_t dt = (10 * k3 * (((k2 * t) / (k3 + t)) + log(h)) / (((k2 * k3) / (k3 + t)) - log(h)));
|
||||
return dt;
|
||||
}
|
||||
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -84,6 +84,7 @@ class Helpers {
|
||||
static uint8_t count_items(const char * const * list);
|
||||
|
||||
static const char * translated_word(const char * const * strings, const bool force_en = false);
|
||||
static int16_t calc_dew(int16_t temp, uint8_t hum);
|
||||
|
||||
#ifdef EMSESP_STANDALONE
|
||||
static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
|
||||
|
||||
@@ -377,7 +377,7 @@ void Roomctrl::temperature(uint8_t addr, uint8_t dst, uint8_t hc) {
|
||||
|
||||
// send telegram 0x047B only for RC100H
|
||||
void Roomctrl::humidity(uint8_t addr, uint8_t dst, uint8_t hc) {
|
||||
int16_t dew = calc_dew(remotetemp_[hc], remotehum_[hc]);
|
||||
int16_t dew = Helpers::calc_dew(remotetemp_[hc], remotehum_[hc]);
|
||||
int8_t dew8 = EMS_VALUE_INT8_NOTSET;
|
||||
if (dew != EMS_VALUE_INT16_NOTSET) {
|
||||
dew8 = static_cast<int8_t>((dew >= 0 ? dew + 5 : dew - 5) / 10);
|
||||
@@ -391,8 +391,8 @@ void Roomctrl::humidity(uint8_t addr, uint8_t dst, uint8_t hc) {
|
||||
data[5] = 0x7B + hc;
|
||||
data[6] = static_cast<uint8_t>(dew8);
|
||||
data[7] = remotehum_[hc];
|
||||
data[8] = static_cast<uint8_t>(static_cast<uint16_t>(dew) >> 8);
|
||||
data[9] = static_cast<uint8_t>(static_cast<uint16_t>(dew) & 0xFF);
|
||||
data[8] = static_cast<uint8_t>(dew >> 8);
|
||||
data[9] = static_cast<uint8_t>(dew & 0x00FF);
|
||||
data[10] = EMSbus::calculate_crc(data, 10); // append CRC
|
||||
EMSuart::transmit(data, 11);
|
||||
}
|
||||
@@ -440,17 +440,4 @@ void Roomctrl::replyF7(uint8_t addr, uint8_t dst, uint8_t offset, uint8_t typehh
|
||||
EMSuart::transmit(data, 10);
|
||||
}
|
||||
|
||||
int16_t Roomctrl::calc_dew(int16_t temp, uint8_t humi) {
|
||||
if (humi == EMS_VALUE_UINT8_NOTSET || temp == EMS_VALUE_INT16_NOTSET) {
|
||||
return EMS_VALUE_INT16_NOTSET;
|
||||
}
|
||||
const float k2 = 17.62;
|
||||
const float k3 = 243.12;
|
||||
const float t = (float)temp / 10;
|
||||
const float h = (float)humi / 100;
|
||||
int16_t dt = (10 * k3 * (((k2 * t) / (k3 + t)) + log(h)) / (((k2 * k3) / (k3 + t)) - log(h)));
|
||||
return dt;
|
||||
}
|
||||
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -35,7 +35,6 @@ class Roomctrl {
|
||||
return (hc < 4 && remotetemp_[hc] != EMS_VALUE_INT16_NOTSET);
|
||||
}
|
||||
static void set_timeout(uint8_t t);
|
||||
static int16_t calc_dew(int16_t temp, uint8_t hum);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t SEND_INTERVAL = 15000; // 15 sec
|
||||
|
||||
@@ -39,17 +39,8 @@ std::deque<Token> exprToTokens(const std::string & expr) {
|
||||
i += (*p == '{') ? 1 : (*p == '}') ? -1 : 0;
|
||||
++p;
|
||||
}
|
||||
if (*p) {
|
||||
++p;
|
||||
}
|
||||
// Use string_view to avoid unnecessary string copies
|
||||
std::string_view s(b, p - b);
|
||||
auto n = s.find("\"\"");
|
||||
while (n != std::string_view::npos) {
|
||||
s.remove_prefix(n + 2);
|
||||
n = s.find("\"\"");
|
||||
}
|
||||
tokens.emplace_back(Token::Type::String, std::string(s), -3);
|
||||
const auto s = std::string(b, p);
|
||||
tokens.emplace_back(Token::Type::String, s, -3);
|
||||
if (*p == '\0') {
|
||||
--p;
|
||||
}
|
||||
@@ -707,7 +698,6 @@ std::string compute(const std::string & expr) {
|
||||
std::string cmd = expr_new.substr(f, e - f).c_str();
|
||||
JsonDocument doc;
|
||||
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
|
||||
HTTPClient * http = new HTTPClient;
|
||||
std::string url, header_s, value_s, method_s, key_s, keys_s;
|
||||
// search keys lower case
|
||||
for (JsonPair p : doc.as<JsonObject>()) {
|
||||
@@ -727,6 +717,12 @@ std::string compute(const std::string & expr) {
|
||||
keys_s = p.key().c_str();
|
||||
}
|
||||
}
|
||||
if (url.substr(0, 4) == "http") { // match http and https
|
||||
HTTPClient * http = new HTTPClient;
|
||||
#ifndef EMSESP_STANDALONE
|
||||
http->setConnectTimeout(10000);
|
||||
http->setTimeout(10000);
|
||||
#endif
|
||||
if (http->begin(url.c_str())) {
|
||||
int httpResult = 0;
|
||||
for (JsonPair p : doc[header_s].as<JsonObject>()) {
|
||||
@@ -777,6 +773,7 @@ std::string compute(const std::string & expr) {
|
||||
expr_new.replace(f, e - f, result.c_str());
|
||||
}
|
||||
http->end();
|
||||
}
|
||||
delete http;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -30,7 +30,7 @@
|
||||
|
||||
#include <HTTPClient.h>
|
||||
#include <map>
|
||||
|
||||
#include "shuntingYard.h" // for compute() used by the message
|
||||
#include "EMSESP_Version.h"
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
@@ -222,7 +222,7 @@ bool System::command_message(const char * value, const int8_t id, JsonObject out
|
||||
LOG_WARNING("Message is empty");
|
||||
return false; // must have a string value
|
||||
}
|
||||
std::string computed_value = EMSESP::webSchedulerService.compute_value(value);
|
||||
std::string computed_value = compute(value);
|
||||
LOG_INFO("Message: %s", computed_value.c_str()); // send to log
|
||||
Mqtt::queue_publish(F_(message), computed_value); // send to MQTT if enabled
|
||||
output["api_data"] = computed_value; // send to API
|
||||
@@ -1952,7 +1952,9 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
|
||||
LOG_ERROR("empty system command");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!strcmp(cmd, "restart")) { // restart is a command, not an entity
|
||||
return false;
|
||||
}
|
||||
// check for hardcoded "info"/"value"
|
||||
if (!strcmp(cmd, F_(info)) || !strcmp(cmd, F_(values))) {
|
||||
return command_info("", 0, output);
|
||||
|
||||
+94
-15
@@ -45,7 +45,8 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
||||
register_telegram_type(0x1230 + i, "Roomparams", false, MAKE_PF_CB(process_roomThermostatParam)); // fetch for active circuits
|
||||
register_telegram_type(0x1244 + i, "Roomdata", false, MAKE_PF_CB(process_roomThermostatData)); // broadcasted
|
||||
}
|
||||
register_telegram_type(0xDB65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule));
|
||||
register_telegram_type(0x0B65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule));
|
||||
register_telegram_type(0xF7, "MenuConfig", false, MAKE_PF_CB(process_roomConfig));
|
||||
// 0x2040, broadcast 36 bytes:
|
||||
// data: 0E 60 00 DF 0D AF 0A 46 0A 46 02 9A 1C 53 1C 53 12 AD 12 AD 00 00 13 C2
|
||||
// data: 1F 37 1F 37 00 00 00 00 18 97 11 27 (offset 24)
|
||||
@@ -88,11 +89,12 @@ void Connect::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
||||
void Connect::register_device_values_room(std::shared_ptr<Connect::RoomCircuit> room) {
|
||||
auto tag = DeviceValueTAG::TAG_SRC1 + room->room();
|
||||
register_device_value(tag, &room->temp_, DeviceValueType::INT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(roomTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(tag, &room->humidity_, DeviceValueType::INT8, FL_(airHumidity), DeviceValueUOM::PERCENT);
|
||||
register_device_value(tag, &room->humidity_, DeviceValueType::UINT8, FL_(airHumidity), DeviceValueUOM::PERCENT);
|
||||
register_device_value(tag, &room->dewtemp_, DeviceValueType::INT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(dewTemperature), DeviceValueUOM::DEGREES);
|
||||
register_device_value(
|
||||
tag, &room->seltemp_, DeviceValueType::UINT8, DeviceValueNumOp::DV_NUMOP_DIV2, FL_(selRoomTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_seltemp), 5, 30);
|
||||
register_device_value(tag, &room->mode_, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
|
||||
// register_device_value(tag, &room->coolmode_, DeviceValueType::BOOL, FL_(coolingOn), DeviceValueUOM::NONE, MAKE_CF_CB(set_coolmode));
|
||||
register_device_value(tag, &room->name_, DeviceValueType::STRING, FL_(name), DeviceValueUOM::NONE, MAKE_CF_CB(set_name));
|
||||
register_device_value(tag, &room->childlock_, DeviceValueType::BOOL, FL_(childlock), DeviceValueUOM::NONE, MAKE_CF_CB(set_childlock));
|
||||
register_device_value(tag, &room->icon_, DeviceValueType::ENUM, FL_(enum_icons), FL_(icon), DeviceValueUOM::NONE, MAKE_CF_CB(set_icon));
|
||||
@@ -133,16 +135,8 @@ void Connect::process_roomThermostat(std::shared_ptr<const Telegram> telegram) {
|
||||
if (!Mqtt::ha_enabled() || (Helpers::hasValue(rc->mode_) && Helpers::hasValue(rc->icon_))) {
|
||||
has_update(telegram, rc->seltemp_, 3);
|
||||
}
|
||||
|
||||
// calculate dew temperature
|
||||
if (rc->humidity_ >= 0 && rc->humidity_ <= 100) {
|
||||
const float k2 = 17.62;
|
||||
const float k3 = 243.12;
|
||||
const float t = (float)rc->temp_ / 10;
|
||||
const float h = (float)rc->humidity_ / 100;
|
||||
int16_t dt = (10 * k3 * (((k2 * t) / (k3 + t)) + log(h)) / (((k2 * k3) / (k3 + t)) - log(h)));
|
||||
has_update(rc->dewtemp_, dt);
|
||||
}
|
||||
has_update(rc->dewtemp_, Helpers::calc_dew(rc->temp_, rc->humidity_));
|
||||
}
|
||||
|
||||
// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 01 // icon in offset 0
|
||||
@@ -168,11 +162,33 @@ void Connect::process_roomThermostatSettings(std::shared_ptr<const Telegram> tel
|
||||
if (rc == nullptr) {
|
||||
return;
|
||||
}
|
||||
has_enumupdate(telegram, rc->mode_, 0, {3, 1, 0}); // modes off, manual auto
|
||||
// has_enumupdate(telegram, rc->mode_, 0, {3, 1, 0}); // modes off, manual auto
|
||||
// has_update(telegram, rc->mode_, 0); // modes: auto, heat, cool, off
|
||||
// has_update(telegram, rc->tempautotemp_, 1); // FF means off
|
||||
// has_update(telegram, rc->manualtemp_, 3);
|
||||
// has_update(telegram, rc->coolmode_, 4); // 01-cooling, 00-heating
|
||||
// has_update(telegram, rc->coolautotemp_, 5);
|
||||
// has_update(telegram, rc->cooltemp_, 6);
|
||||
has_update(telegram, rc->childlock_, 7);
|
||||
uint8_t mh = 0xFF;
|
||||
uint8_t mc = 0xFF;
|
||||
telegram->read_value(mh, 0);
|
||||
telegram->read_value(mc, 4);
|
||||
if (mc == 3) {
|
||||
rc->coolmode_ = 1;
|
||||
rc->mode_ = 0;
|
||||
} else if (mh == 3) {
|
||||
rc->coolmode_ = 0;
|
||||
rc->mode_ = 0;
|
||||
} else if (mc == 1) {
|
||||
rc->coolmode_ = 1;
|
||||
rc->mode_ = 1;
|
||||
} else if (mh == 1) {
|
||||
rc->coolmode_ = 0;
|
||||
rc->mode_ = 1;
|
||||
} else {
|
||||
rc->mode_ = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// unknown telegrams, needs fetch
|
||||
@@ -203,6 +219,53 @@ void Connect::process_roomSchedule(std::shared_ptr<const Telegram> telegram) {
|
||||
toggle_fetch(telegram->type_id, false); // fetch only once if all is initialized
|
||||
}
|
||||
|
||||
void Connect::process_roomConfig(std::shared_ptr<const Telegram> telegram) {
|
||||
if (telegram->offset == 0 && telegram->message_length > 3) {
|
||||
uint16_t t = 0;
|
||||
telegram->read_value(t, 1);
|
||||
if (t >= 0x0AB5 && t <= 0x0AB5 + 15) {
|
||||
auto rc = room_circuit(t - 0x0AB5);
|
||||
if (rc == nullptr) {
|
||||
return;
|
||||
}
|
||||
uint8_t bits = telegram->message_data[3];
|
||||
if (rc->mode_ == 2) {
|
||||
if (bits & 0x02) {
|
||||
rc->coolmode_ = 0;
|
||||
} else if (bits & 0x20) {
|
||||
rc->coolmode_ = 1;
|
||||
}
|
||||
}
|
||||
/*
|
||||
switch (bits & 0x6A) {
|
||||
case 0: // cooling off
|
||||
rc->mode_ = 0;
|
||||
rc->coolmode_ = 1;
|
||||
break;
|
||||
case 0x40: // manual cooling or heating off
|
||||
rc->mode_ = 1;
|
||||
rc->coolmode_ = 1;
|
||||
break;
|
||||
case 0x20: // auto cooling
|
||||
rc->mode_ = 2;
|
||||
rc->coolmode_ = 1;
|
||||
break;
|
||||
case 0x48: // manual heating
|
||||
case 0x08: // manual heating
|
||||
rc->mode_ = 1;
|
||||
rc->coolmode_ = 0;
|
||||
break;
|
||||
case 0x42: // auto heating
|
||||
case 0x02: // auto heating
|
||||
rc->mode_ = 2;
|
||||
rc->coolmode_ = 0;
|
||||
break;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings:
|
||||
|
||||
bool Connect::set_mode(const char * value, const int8_t id) {
|
||||
@@ -216,7 +279,7 @@ bool Connect::set_mode(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
write_command(0xBB5 + rc->room(), 0, v); // no validate, mode change is broadcasted
|
||||
write_command(0xBB5 + rc->room(), rc->coolmode_ == 1 ? 4 : 0, v); // no validate, mode change is broadcasted
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -226,10 +289,13 @@ bool Connect::set_seltemp(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
float v;
|
||||
if (Helpers::value2float(value, v)) {
|
||||
if (Helpers::value2temperature(value, v)) {
|
||||
// depends on mode, auto (2 for enum_mode2, 0 for enum_mode8) set in offset 1
|
||||
if (rc->coolmode_ == 1) {
|
||||
write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 5 : 6, v == -1 ? 0xFF : uint8_t(v * 2));
|
||||
} else {
|
||||
write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
|
||||
// write_command(0xBB5 + rc->room(), rc->mode_ == 0 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -257,6 +323,19 @@ bool Connect::set_name(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Connect::set_coolmode(const char * value, const int8_t id) {
|
||||
auto rc = room_circuit(id - DeviceValueTAG::TAG_SRC1);
|
||||
if (rc == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool b;
|
||||
if (Helpers::value2bool(value, b)) {
|
||||
write_command(0xBB5 + rc->room(), 4, b ? 1 : 0, 0xBB5 + rc->room());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Connect::set_childlock(const char * value, const int8_t id) {
|
||||
auto rc = room_circuit(id - DeviceValueTAG::TAG_SRC1);
|
||||
if (rc == nullptr) {
|
||||
|
||||
@@ -34,15 +34,18 @@ class Connect : public EMSdevice {
|
||||
}
|
||||
~RoomCircuit() = default;
|
||||
int16_t temp_;
|
||||
int8_t humidity_;
|
||||
uint8_t humidity_;
|
||||
uint8_t seltemp_;
|
||||
uint8_t mode_;
|
||||
char name_[51];
|
||||
int16_t dewtemp_;
|
||||
uint8_t childlock_;
|
||||
uint8_t icon_;
|
||||
uint8_t coolmode_;
|
||||
// uint8_t tempautotemp_;
|
||||
// uint8_t manualtemp_;
|
||||
// uint8_t coolautotemp_;
|
||||
// uint8_t cooltemp_;
|
||||
|
||||
uint8_t room() {
|
||||
return room_;
|
||||
@@ -62,9 +65,11 @@ class Connect : public EMSdevice {
|
||||
void process_roomThermostatParam(std::shared_ptr<const Telegram> telegram);
|
||||
void process_roomThermostatData(std::shared_ptr<const Telegram> telegram);
|
||||
void process_roomSchedule(std::shared_ptr<const Telegram> telegram);
|
||||
void process_roomConfig(std::shared_ptr<const Telegram> telegram);
|
||||
bool set_mode(const char * value, const int8_t id);
|
||||
bool set_seltemp(const char * value, const int8_t id);
|
||||
bool set_name(const char * value, const int8_t id);
|
||||
bool set_coolmode(const char * value, const int8_t id);
|
||||
bool set_childlock(const char * value, const int8_t id);
|
||||
bool set_icon(const char * value, const int8_t id);
|
||||
|
||||
|
||||
+76
-20
@@ -818,7 +818,8 @@ void Thermostat::process_RemoteHumidity(std::shared_ptr<const Telegram> telegram
|
||||
if (telegram->read_value(dew, 2)) {
|
||||
has_update(dewtemperature_, dew);
|
||||
} else if (telegram->offset == 0 && telegram->message_length < 4) {
|
||||
has_update(dewtemperature_, Roomctrl::calc_dew(tempsensor1_, humidity_));
|
||||
dew = Helpers::calc_dew(tempsensor1_, humidity_);
|
||||
has_update(dewtemperature_, dew);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1465,14 +1466,21 @@ void Thermostat::process_HPMode(std::shared_ptr<const Telegram> telegram) {
|
||||
}
|
||||
|
||||
// 0x467 ff HP settings
|
||||
// thermostat(0x10) -W-> Me(0x0B), ?(0x0467), data: 0A 15 00 01 04 (from #1187)
|
||||
// thermostat(0x10) W me(0x0B), HPSet(0x0467), data: 14 13 02 01 02 (from #3144)
|
||||
void Thermostat::process_HPSet(std::shared_ptr<const Telegram> telegram) {
|
||||
auto hc = heating_circuit(telegram);
|
||||
if (hc == nullptr) {
|
||||
return;
|
||||
}
|
||||
has_update(telegram, hc->dewoffset, 4); // 7-35°C
|
||||
if (hc->control == 3 || hc->control == 6) { // control with humidity
|
||||
has_update(telegram, hc->hpminflowtemp, 0); // 7-35°C
|
||||
} else {
|
||||
has_update(telegram, hc->hpminflowtemp, 1); // 7-35°C
|
||||
}
|
||||
// has_update(telegram, hc->roomtempoffset, 2); // 1-10K // not implemented, mentioned in #3144
|
||||
has_update(telegram, hc->roomtempdiff, 3); // 1-10K
|
||||
has_update(telegram, hc->hpminflowtemp, 0); // 2-10K
|
||||
has_update(telegram, hc->dewoffset, 4); // 2-10K
|
||||
}
|
||||
|
||||
// type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long
|
||||
@@ -1731,6 +1739,15 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
// check clock
|
||||
time_t now = time(nullptr);
|
||||
bool sync = false;
|
||||
// change to thermostat timezone if different
|
||||
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
|
||||
sync = settings.thermostat_sync != 0;
|
||||
if (settings.thermostat_sync == 2) {
|
||||
setenv("TZ", settings.tzFormatT.c_str(), 1);
|
||||
tzset();
|
||||
}
|
||||
});
|
||||
tm * tm_ = localtime(&now);
|
||||
bool tset_ = tm_->tm_year > 110; // year 2010 and up, time is valid
|
||||
tm_->tm_year = (telegram->message_data[0] & 0x7F) + 100; // IVT
|
||||
@@ -1749,19 +1766,26 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(dateTime_, newdatetime, sizeof(dateTime_));
|
||||
|
||||
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
|
||||
bool junkersclock = model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS;
|
||||
time_t ttime = mktime(tm_); // thermostat time
|
||||
tm_->tm_isdst = -1; // determine dst
|
||||
time_t ttime = mktime(tm_); // make thermostat time
|
||||
// change back emsesp timezone
|
||||
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
|
||||
if (settings.thermostat_sync == 2) {
|
||||
setenv("TZ", settings.tzFormat.c_str(), 1);
|
||||
tzset();
|
||||
}
|
||||
});
|
||||
// correct thermostat clock if we have valid ntp time, and could write the command
|
||||
if (!ivtclock && !junkersclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) {
|
||||
double difference = difftime(now, ttime);
|
||||
if (sync && !ivtclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) {
|
||||
int difference = difftime(now, ttime);
|
||||
if (difference > 15 || difference < -15) {
|
||||
if (setTimeRetry < 3) {
|
||||
if (!use_dst) {
|
||||
set_datetime("ntp", 0); // set from NTP without dst
|
||||
LOG_INFO("thermostat time correction from ntp, ignoring dst");
|
||||
LOG_INFO("thermostat time correction %d seconds from ntp, ignoring dst", difference);
|
||||
} else {
|
||||
set_datetime("ntp", -1); // set from NTP
|
||||
LOG_INFO("thermostat time correction from ntp");
|
||||
LOG_INFO("thermostat time correction %d seconds from ntp", difference);
|
||||
}
|
||||
setTimeRetry++;
|
||||
}
|
||||
@@ -1771,10 +1795,6 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
||||
}
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!tset_ && tm_->tm_year > 110) { // emsesp clock not set, but thermostat clock
|
||||
if (ivtclock) {
|
||||
tm_->tm_isdst = -1; // determine dst
|
||||
ttime = mktime(tm_); // thermostat time
|
||||
}
|
||||
struct timeval newnow = {.tv_sec = ttime, .tv_usec = 0};
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
// unknown how to set time on C3
|
||||
@@ -1925,7 +1945,11 @@ bool Thermostat::set_hpminflowtemp(const char * value, const int8_t id) {
|
||||
}
|
||||
int v;
|
||||
if (Helpers::value2temperature(value, v)) {
|
||||
if (hc->control == 3 || hc->control == 6) { // remotes with humidity
|
||||
write_command(hp_typeids[hc->hc()], 0, v, hp_typeids[hc->hc()]);
|
||||
} else {
|
||||
write_command(hp_typeids[hc->hc()], 1, v, hp_typeids[hc->hc()]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -3054,6 +3078,15 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
|
||||
if (dt == "ntp") {
|
||||
time_t now = time(nullptr);
|
||||
tm * tm_ = localtime(&now);
|
||||
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
|
||||
if (settings.thermostat_sync == 2) {
|
||||
setenv("TZ", settings.tzFormatT.c_str(), 1);
|
||||
tzset();
|
||||
tm_ = localtime(&now);
|
||||
setenv("TZ", settings.tzFormat.c_str(), 1);
|
||||
tzset();
|
||||
}
|
||||
});
|
||||
if (tm_->tm_year < 110) { // no valid time
|
||||
return false;
|
||||
}
|
||||
@@ -3069,10 +3102,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
|
||||
data[5] = tm_->tm_sec;
|
||||
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
|
||||
data[7] = (id == 0) ? 2 : tm_->tm_isdst + 2; // set DST and flag for ext. clock
|
||||
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||
data[7] = 0;
|
||||
}
|
||||
} else if (dt.length() == 23) {
|
||||
} else if (dt.length() == 23 || dt.length() == 21) {
|
||||
data[0] = (dt[7] - '0') * 100 + (dt[8] - '0') * 10 + (dt[9] - '0'); // year
|
||||
data[1] = (dt[3] - '0') * 10 + (dt[4] - '0'); // month
|
||||
data[2] = (dt[11] - '0') * 10 + (dt[12] - '0'); // hour
|
||||
@@ -3080,10 +3110,35 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
|
||||
data[4] = (dt[14] - '0') * 10 + (dt[15] - '0'); // min
|
||||
data[5] = (dt[17] - '0') * 10 + (dt[18] - '0'); // sec
|
||||
data[6] = (dt[20] - '0'); // day of week, Mo:0
|
||||
data[7] = (dt[22] - '0') + 2; // DST and flag
|
||||
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||
data[7] = 0;
|
||||
data[7] = dt.length() == 21 ? 2 : (dt[22] - '0') + 2; // DST and flag
|
||||
} else if (dt.length() == 19 || dt.length() == 16) {
|
||||
time_t now = time(nullptr);
|
||||
tm * tm_ = localtime(&now);
|
||||
data[0] = (dt[7] - '0') * 100 + (dt[8] - '0') * 10 + (dt[9] - '0'); // year
|
||||
tm_->tm_year = 100 + data[0];
|
||||
data[1] = (dt[3] - '0') * 10 + (dt[4] - '0'); // month
|
||||
tm_->tm_mon = data[1] - 1;
|
||||
tm_->tm_hour = data[2] = (dt[11] - '0') * 10 + (dt[12] - '0'); // hour
|
||||
tm_->tm_mday = data[3] = (dt[0] - '0') * 10 + (dt[1] - '0'); // day
|
||||
tm_->tm_min = data[4] = (dt[14] - '0') * 10 + (dt[15] - '0'); // min
|
||||
tm_->tm_sec = data[5] = dt.length() == 16 ? 0 : (dt[17] - '0') * 10 + (dt[18] - '0'); // sec
|
||||
tm_->tm_isdst = -1;
|
||||
// use thermostat time zone to determine day of week and daylight saving
|
||||
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
|
||||
if (settings.thermostat_sync == 2) {
|
||||
setenv("TZ", settings.tzFormatT.c_str(), 1);
|
||||
tzset();
|
||||
auto t = mktime(tm_);
|
||||
tm_ = localtime(&t);
|
||||
setenv("TZ", settings.tzFormat.c_str(), 1);
|
||||
tzset();
|
||||
} else {
|
||||
auto t = mktime(tm_);
|
||||
tm_ = localtime(&t);
|
||||
}
|
||||
});
|
||||
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
|
||||
} else {
|
||||
LOG_WARNING("Set date: invalid data, wrong length");
|
||||
return false;
|
||||
@@ -3095,6 +3150,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
|
||||
|
||||
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||
data[6]++; // Junkers use 1-7 for day of the week
|
||||
data[7] = 0; // no dst setting
|
||||
}
|
||||
|
||||
// LOG_INFO("Setting date and time: %02d.%02d.2%03d-%02d:%02d:%02d-%d-%d", data[3], data[1], data[0], data[2], data[4], data[5], data[6], data[7]);
|
||||
|
||||
@@ -361,8 +361,11 @@ bool Water::set_wwCircTc(const char * value, const int8_t id) {
|
||||
if (!Helpers::value2bool(value, b)) {
|
||||
return false;
|
||||
}
|
||||
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
|
||||
write_command(0x33B + dhw_, 4, b ? 0x01 : 0x00, 0x33B + dhw_);
|
||||
} else { // SM100
|
||||
write_command(0x7A5, 4, b ? 0xFF : 0x00, 0x7A5);
|
||||
return true;
|
||||
} return true;
|
||||
}
|
||||
|
||||
bool Water::set_wwKeepWarm(const char * value, const int8_t id) {
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.8.3-dev.8"
|
||||
#define EMSESP_APP_VERSION "3.8.3-dev.9"
|
||||
|
||||
@@ -35,6 +35,11 @@ WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * security
|
||||
// POST|GET api/{device}
|
||||
// POST|GET api/{device}/{entity}
|
||||
void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant json) {
|
||||
static uint64_t lastcall = 0;
|
||||
if (uuid::get_uptime_ms() - lastcall < 50 && request->method() == HTTP_GET) {
|
||||
request->send(429); //too many requests
|
||||
return;
|
||||
}
|
||||
JsonDocument input_doc; // has no body JSON so create dummy as empty input object
|
||||
JsonObject input;
|
||||
// if no body then treat it as a secure GET
|
||||
@@ -49,6 +54,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant j
|
||||
input["data"] = json.as<std::string>();
|
||||
}
|
||||
parse(request, input);
|
||||
lastcall = uuid::get_uptime_ms();
|
||||
}
|
||||
|
||||
// for POSTS accepting plain text data
|
||||
|
||||
@@ -77,8 +77,10 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
||||
if (entityItem.ram == 2) { // NVS
|
||||
char key[sizeof(entityItem.name) + 2];
|
||||
snprintf(key, sizeof(key), "c:%s", entityItem.name);
|
||||
if (EMSESP::nvs_.isKey(key)) {
|
||||
EMSESP::nvs_.remove(key);
|
||||
}
|
||||
}
|
||||
if (entityItem.ram) { // save name/value pairs for change checking
|
||||
doc[entityItem.name] = entityItem.value;
|
||||
}
|
||||
|
||||
@@ -76,8 +76,10 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
|
||||
for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
|
||||
char key[sizeof(scheduleItem.name) + 2];
|
||||
snprintf(key, sizeof(key), "s:%s", scheduleItem.name);
|
||||
if (EMSESP::nvs_.isKey(key)) {
|
||||
EMSESP::nvs_.remove(key);
|
||||
}
|
||||
}
|
||||
webScheduler.scheduleItems.clear();
|
||||
EMSESP::webSchedulerService.ha_reset();
|
||||
|
||||
@@ -345,7 +347,11 @@ uint8_t WebSchedulerService::count_entities(bool cmd_only) {
|
||||
// execute scheduled command
|
||||
bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
|
||||
std::string cmd = Helpers::toLower(command);
|
||||
|
||||
if (cmd == "system/message") {
|
||||
EMSESP::logger().info("Message: %s", data.c_str()); // send to log
|
||||
Mqtt::queue_publish(F_(message), data); // send to MQTT if enabled
|
||||
return true;
|
||||
}
|
||||
// check http commands. e.g.
|
||||
// tasmota(get): http://<tasmotaIP>/cm?cmnd=power%20ON
|
||||
// shelly(get): http://<shellyIP>/relais/0?turn=on
|
||||
@@ -353,6 +359,10 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
||||
JsonDocument doc;
|
||||
if (deserializeJson(doc, cmd) == DeserializationError::Ok) {
|
||||
HTTPClient * http = new HTTPClient;
|
||||
#ifndef EMSESP_STANDALONE
|
||||
http->setConnectTimeout(10000);
|
||||
http->setTimeout(10000);
|
||||
#endif
|
||||
std::string url = doc["url"] | "";
|
||||
// for a GET with parameters replace commands with values
|
||||
// don't search the complete url, it may contain a devicename in path
|
||||
@@ -401,6 +411,7 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
||||
return true;
|
||||
}
|
||||
// we can add other json tests here
|
||||
delete http;
|
||||
}
|
||||
|
||||
doc.clear();
|
||||
@@ -472,10 +483,6 @@ void WebSchedulerService::condition() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string WebSchedulerService::compute_value(const char * value) {
|
||||
return compute(value);
|
||||
}
|
||||
|
||||
// process any scheduled jobs
|
||||
void WebSchedulerService::loop() {
|
||||
// initialize static value on startup
|
||||
@@ -487,6 +494,10 @@ void WebSchedulerService::loop() {
|
||||
if (scheduleItems_->empty()) {
|
||||
return;
|
||||
}
|
||||
// do not execute any command in the first 60 secondes
|
||||
if (uuid::get_uptime_sec() < 60) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we have onChange events
|
||||
while (!cmd_changed_.empty()) {
|
||||
@@ -530,7 +541,8 @@ void WebSchedulerService::loop() {
|
||||
// retry startup commands not yet executed
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0
|
||||
&& scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) {
|
||||
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), scheduleItem.value.c_str()) ? 0xFF : scheduleItem.retry_cnt + 1;
|
||||
scheduleItem.retry_cnt =
|
||||
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())) ? 0xFF : scheduleItem.retry_cnt + 1;
|
||||
}
|
||||
// scheduled timer commands
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
|
||||
@@ -541,10 +553,10 @@ void WebSchedulerService::loop() {
|
||||
last_uptime_min = uptime_min;
|
||||
}
|
||||
|
||||
// check calender, sync to RTC, only execute if year is valid
|
||||
// check calender, sync to RTC, only execute if year is valid and uptime more than a minute
|
||||
time_t now = time(nullptr);
|
||||
tm * tm = localtime(&now);
|
||||
if (tm->tm_min != last_tm_min && tm->tm_year > 120) {
|
||||
if (tm->tm_min != last_tm_min && tm->tm_year > 120 && uuid::get_uptime_sec() > 60) {
|
||||
// find the real dow and minute from RTC
|
||||
uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday
|
||||
uint16_t real_min = tm->tm_hour * 60 + tm->tm_min;
|
||||
|
||||
@@ -89,7 +89,6 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
|
||||
bool onChange(const char * cmd);
|
||||
|
||||
std::string get_metrics_prometheus();
|
||||
std::string compute_value(const char * value);
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void load_test_data();
|
||||
|
||||
@@ -245,6 +245,7 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
|
||||
if (!ok) {
|
||||
EMSESP::logger().err("Action '%s' failed", action.c_str());
|
||||
request->send(400); // bad request
|
||||
delete response;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user