mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-07-29 01:52:51 +00:00
option to sync thermostat clock
This commit is contained in:
@@ -21,3 +21,4 @@ For more details go to [emsesp.org](https://emsesp.org/).
|
|||||||
|
|
||||||
- call compute value direct, enlarge TCP stack [#3127](https://github.com/emsesp/EMS-ESP32/discussions/3127)
|
- 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 no sync.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Grid,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -39,7 +40,7 @@ import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
|||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
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 NTPSettings = () => {
|
||||||
const {
|
const {
|
||||||
@@ -63,13 +64,17 @@ const NTPSettings = () => {
|
|||||||
|
|
||||||
// Memoized timezone select items for better performance
|
// Memoized timezone select items for better performance
|
||||||
const timeZoneItems = useTimeZoneSelectItems();
|
const timeZoneItems = useTimeZoneSelectItems();
|
||||||
|
const timeZoneItemsT = timeZoneSelectItemsT();
|
||||||
|
|
||||||
// Memoized selected timezone value
|
// Memoized selected timezone value
|
||||||
const selectedTzValue = useMemo(
|
const selectedTzValue = useMemo(
|
||||||
() => (data ? selectedTimeZone(data.tz_label, data.tz_format) : undefined),
|
() => (data ? selectedTimeZone(data.tz_label, data.tz_format) : undefined),
|
||||||
[data?.tz_label, data?.tz_format]
|
[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 [localTime, setLocalTime] = useState<string>('');
|
||||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||||
const [processing, setProcessing] = useState<boolean>(false);
|
const [processing, setProcessing] = useState<boolean>(false);
|
||||||
@@ -150,6 +155,18 @@ const NTPSettings = () => {
|
|||||||
[updateFormValue]
|
[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
|
// Memoize render content to prevent unnecessary re-renders
|
||||||
const renderContent = useMemo(() => {
|
const renderContent = useMemo(() => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -174,6 +191,7 @@ const NTPSettings = () => {
|
|||||||
label={LL.NTP_SERVER()}
|
label={LL.NTP_SERVER()}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
disabled={!data.enabled}
|
||||||
value={data.server}
|
value={data.server}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
@@ -192,7 +210,45 @@ const NTPSettings = () => {
|
|||||||
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
||||||
{timeZoneItems}
|
{timeZoneItems}
|
||||||
</ValidatedTextField>
|
</ValidatedTextField>
|
||||||
|
{data.enabled && (
|
||||||
|
<Grid container spacing={2} rowSpacing={0}>
|
||||||
|
<Grid>
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors || {}}
|
||||||
|
name="thermostat_option"
|
||||||
|
label="Sync EMS-Thermostat"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ width: '30ch' }}
|
||||||
|
value={data.thermostat_option}
|
||||||
|
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_option === 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' }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
|
||||||
{!data.enabled && !dirtyFlags.length && (
|
{!data.enabled && !dirtyFlags.length && (
|
||||||
<Box sx={{ flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
|
<Box sx={{ flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
|
||||||
@@ -243,6 +299,7 @@ const NTPSettings = () => {
|
|||||||
updateFormValue,
|
updateFormValue,
|
||||||
fieldErrors,
|
fieldErrors,
|
||||||
selectedTzValue,
|
selectedTzValue,
|
||||||
|
selectedTzValueT,
|
||||||
changeTimeZone,
|
changeTimeZone,
|
||||||
timeZoneItems,
|
timeZoneItems,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
|
|||||||
@@ -495,3 +495,7 @@ const precomputedTimeZoneItems = TIME_ZONE_LABELS.map((label) => (
|
|||||||
export function timeZoneSelectItems() {
|
export function timeZoneSelectItems() {
|
||||||
return precomputedTimeZoneItems;
|
return precomputedTimeZoneItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function timeZoneSelectItemsT() {
|
||||||
|
return precomputedTimeZoneItems;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export interface NTPSettingsType {
|
|||||||
server: string;
|
server: string;
|
||||||
tz_label: string;
|
tz_label: string;
|
||||||
tz_format: string;
|
tz_format: string;
|
||||||
|
thermostat_option: number;
|
||||||
|
tz_label_t: string;
|
||||||
|
tz_format_t: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Time {
|
export interface Time {
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ void NTPSettings::read(NTPSettings & settings, JsonObject root) {
|
|||||||
root["server"] = settings.server;
|
root["server"] = settings.server;
|
||||||
root["tz_label"] = settings.tzLabel;
|
root["tz_label"] = settings.tzLabel;
|
||||||
root["tz_format"] = settings.tzFormat;
|
root["tz_format"] = settings.tzFormat;
|
||||||
|
root["thermostat_option"] = settings.thermostat_option;
|
||||||
|
root["tz_label_t"] = settings.tzLabelT;
|
||||||
|
root["tz_format_t"] = settings.tzFormatT;
|
||||||
}
|
}
|
||||||
|
|
||||||
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
|
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.server = root["server"] | FACTORY_NTP_SERVER;
|
||||||
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||||
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||||
|
settings.thermostat_option = root["thermostat_option"] | 0;
|
||||||
|
settings.tzLabelT = root["tz_label_t"] | settings.tzLabel;
|
||||||
|
settings.tzFormatT = root["tz_format_t"] | settings.tzFormat;
|
||||||
return StateUpdateResult::CHANGED;
|
return StateUpdateResult::CHANGED;
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,9 @@ class NTPSettings {
|
|||||||
String tzLabel = FACTORY_NTP_TIME_ZONE_LABEL;
|
String tzLabel = FACTORY_NTP_TIME_ZONE_LABEL;
|
||||||
String tzFormat = FACTORY_NTP_TIME_ZONE_FORMAT;
|
String tzFormat = FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||||
String server = FACTORY_NTP_SERVER;
|
String server = FACTORY_NTP_SERVER;
|
||||||
|
uint8_t thermostat_option = 0;
|
||||||
|
String tzLabelT = FACTORY_NTP_TIME_ZONE_LABEL;
|
||||||
|
String tzFormatT = FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||||
|
|
||||||
static void read(NTPSettings & settings, JsonObject root);
|
static void read(NTPSettings & settings, JsonObject root);
|
||||||
static StateUpdateResult update(JsonObject root, NTPSettings & settings);
|
static StateUpdateResult update(JsonObject root, NTPSettings & settings);
|
||||||
|
|||||||
@@ -1738,7 +1738,25 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
|||||||
|
|
||||||
// check clock
|
// check clock
|
||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
tm * tm_ = localtime(&now);
|
tm * tm_;
|
||||||
|
bool sync = true;
|
||||||
|
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
|
||||||
|
switch (settings.thermostat_option) {
|
||||||
|
default:
|
||||||
|
sync = false;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
tm_ = localtime(&now);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
setenv("TZ", settings.tzFormatT.c_str(), 1);
|
||||||
|
tzset();
|
||||||
|
tm_ = localtime(&now);
|
||||||
|
setenv("TZ", settings.tzFormat.c_str(), 1);
|
||||||
|
tzset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
bool tset_ = tm_->tm_year > 110; // year 2010 and up, time is valid
|
bool tset_ = tm_->tm_year > 110; // year 2010 and up, time is valid
|
||||||
tm_->tm_year = (telegram->message_data[0] & 0x7F) + 100; // IVT
|
tm_->tm_year = (telegram->message_data[0] & 0x7F) + 100; // IVT
|
||||||
tm_->tm_mon = telegram->message_data[1] - 1;
|
tm_->tm_mon = telegram->message_data[1] - 1;
|
||||||
@@ -1756,11 +1774,12 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
|||||||
has_update(dateTime_, newdatetime, sizeof(dateTime_));
|
has_update(dateTime_, newdatetime, sizeof(dateTime_));
|
||||||
|
|
||||||
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
|
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
|
||||||
bool junkersclock = model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS;
|
// bool junkersclock = model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS;
|
||||||
time_t ttime = mktime(tm_); // thermostat time
|
time_t ttime = mktime(tm_); // thermostat time
|
||||||
// correct thermostat clock if we have valid ntp time, and could write the command
|
// 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_)) {
|
// if (sync && !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 (difference > 15 || difference < -15) {
|
||||||
if (setTimeRetry < 3) {
|
if (setTimeRetry < 3) {
|
||||||
if (!use_dst) {
|
if (!use_dst) {
|
||||||
@@ -3064,7 +3083,21 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
|
|||||||
uint8_t data[9];
|
uint8_t data[9];
|
||||||
if (dt == "ntp") {
|
if (dt == "ntp") {
|
||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
tm * tm_ = localtime(&now);
|
tm * tm_;
|
||||||
|
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
|
||||||
|
switch (settings.thermostat_option) {
|
||||||
|
default:
|
||||||
|
tm_ = localtime(&now);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
setenv("TZ", settings.tzFormatT.c_str(), 1);
|
||||||
|
tzset();
|
||||||
|
tm_ = localtime(&now);
|
||||||
|
setenv("TZ", settings.tzFormat.c_str(), 1);
|
||||||
|
tzset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
if (tm_->tm_year < 110) { // no valid time
|
if (tm_->tm_year < 110) { // no valid time
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3080,10 +3113,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
|
|||||||
data[5] = tm_->tm_sec;
|
data[5] = tm_->tm_sec;
|
||||||
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
|
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
|
data[7] = (id == 0) ? 2 : tm_->tm_isdst + 2; // set DST and flag for ext. clock
|
||||||
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
} else if (dt.length() == 23 || dt.length() == 21) {
|
||||||
data[7] = 0;
|
|
||||||
}
|
|
||||||
} else if (dt.length() == 23) {
|
|
||||||
data[0] = (dt[7] - '0') * 100 + (dt[8] - '0') * 10 + (dt[9] - '0'); // year
|
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[1] = (dt[3] - '0') * 10 + (dt[4] - '0'); // month
|
||||||
data[2] = (dt[11] - '0') * 10 + (dt[12] - '0'); // hour
|
data[2] = (dt[11] - '0') * 10 + (dt[12] - '0'); // hour
|
||||||
@@ -3091,10 +3121,23 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
|
|||||||
data[4] = (dt[14] - '0') * 10 + (dt[15] - '0'); // min
|
data[4] = (dt[14] - '0') * 10 + (dt[15] - '0'); // min
|
||||||
data[5] = (dt[17] - '0') * 10 + (dt[18] - '0'); // sec
|
data[5] = (dt[17] - '0') * 10 + (dt[18] - '0'); // sec
|
||||||
data[6] = (dt[20] - '0'); // day of week, Mo:0
|
data[6] = (dt[20] - '0'); // day of week, Mo:0
|
||||||
data[7] = (dt[22] - '0') + 2; // DST and flag
|
data[7] = dt.length() == 21 ? 2 : (dt[22] - '0') + 2; // DST and flag
|
||||||
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
} else if (dt.length() == 19 || dt.length() == 16) {
|
||||||
data[7] = 0;
|
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;
|
||||||
|
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 {
|
} else {
|
||||||
LOG_WARNING("Set date: invalid data, wrong length");
|
LOG_WARNING("Set date: invalid data, wrong length");
|
||||||
return false;
|
return false;
|
||||||
@@ -3106,6 +3149,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
|
|||||||
|
|
||||||
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||||
data[6]++; // Junkers use 1-7 for day of the week
|
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]);
|
// 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]);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#define EMSESP_APP_VERSION "3.8.3-dev.9"
|
#define EMSESP_APP_VERSION "3.8.3-dev.10"
|
||||||
|
|||||||
Reference in New Issue
Block a user