diff --git a/interface/src/components/layout/LayoutMenuItem.tsx b/interface/src/components/layout/LayoutMenuItem.tsx index bc891e330..04da15106 100644 --- a/interface/src/components/layout/LayoutMenuItem.tsx +++ b/interface/src/components/layout/LayoutMenuItem.tsx @@ -3,10 +3,10 @@ import { Link, useLocation } from 'react-router-dom'; import { ListItem, ListItemButton, ListItemIcon, ListItemText, SvgIconProps } from '@mui/material'; -import { grey } from '@mui/material/colors'; - import { routeMatches } from 'utils'; +import { grey } from '@mui/material/colors'; + interface LayoutMenuItemProps { icon: React.ComponentType; label: string; @@ -17,13 +17,15 @@ interface LayoutMenuItemProps { const LayoutMenuItem: FC = ({ icon: Icon, label, to, disabled }) => { const { pathname } = useLocation(); + const selected = routeMatches(to, pathname); + return ( - - - + + + - {label} + {label} ); diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index b2e4cc678..5fa222f3d 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -307,7 +307,15 @@ const de: Translation = { BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate STAY: 'Stay', // TODO translate - LEAVE: 'Leave' // TODO translate + LEAVE: 'Leave', // TODO translate + SCHEDULER: 'Scheduler', // TODO translate + SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP.', // TODO translate + SCHEDULER_HELP_2: 'The NTP service needs to be active if using the calendar.', // TODO translate + SCHEDULE: 'Schedule', // TODO translate + TIME: 'Time', // TODO translate + TIMER: 'Timer', // TODO translate + WEEKLY: 'Weekly', // TODO translate + SCHEDULE_SAVED: 'Schedule updated' // TODO translate }; export default de; diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 4790cb68b..63c4f2f93 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -307,7 +307,15 @@ const en: Translation = { BLOCK_NAVIGATE_1: 'You have unsaved changes', BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', STAY: 'Stay', - LEAVE: 'Leave' + LEAVE: 'Leave', + SCHEDULER: 'Scheduler', + SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP.', + SCHEDULER_HELP_2: 'The NTP service needs to be active if using the calendar.', + SCHEDULE: 'Schedule', + TIME: 'Time', + TIMER: 'Timer', + WEEKLY: 'Weekly', + SCHEDULE_SAVED: 'Schedule updated' }; export default en; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 515d6b51a..028078da3 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -307,7 +307,15 @@ const fr: Translation = { BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate STAY: 'Stay', // TODO translate - LEAVE: 'Leave' // TODO translate + LEAVE: 'Leave', // TODO translate + SCHEDULER: 'Scheduler', // TODO translate + SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP.', // TODO translate + SCHEDULER_HELP_2: 'The NTP service needs to be active if using the calendar.', // TODO translate + SCHEDULE: 'Schedule', // TODO translate + TIME: 'Time', // TODO translate + TIMER: 'Timer', // TODO translate + WEEKLY: 'Weekly', // TODO translate + SCHEDULE_SAVED: 'Schedule updated' // TODO translate }; export default fr; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index e613c7ba7..5a2f33f16 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -307,7 +307,15 @@ const nl: Translation = { BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate STAY: 'Stay', // TODO translate - LEAVE: 'Leave' // TODO translate + LEAVE: 'Leave', // TODO translate + SCHEDULER: 'Scheduler', // TODO translate + SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP.', // TODO translate + SCHEDULER_HELP_2: 'The NTP service needs to be active if using the calendar.', // TODO translate + SCHEDULE: 'Schedule', // TODO translate + TIME: 'Time', // TODO translate + TIMER: 'Timer', // TODO translate + WEEKLY: 'Weekly', // TODO translate + SCHEDULE_SAVED: 'Schedule updated' // TODO translate }; export default nl; diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 2aba0ac87..8ee684b00 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -307,7 +307,15 @@ const no: Translation = { BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate STAY: 'Stay', // TODO translate - LEAVE: 'Leave' // TODO translate + LEAVE: 'Leave', // TODO translate + SCHEDULER: 'Scheduler', // TODO translate + SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP.', // TODO translate + SCHEDULER_HELP_2: 'The NTP service needs to be active if using the calendar.', // TODO translate + SCHEDULE: 'Schedule', // TODO translate + TIME: 'Time', // TODO translate + TIMER: 'Timer', // TODO translate + WEEKLY: 'Weekly', // TODO translate + SCHEDULE_SAVED: 'Schedule updated' // TODO translate }; export default no; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index faa58a60a..f584388b5 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -307,7 +307,15 @@ const pl: BaseTranslation = { BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate STAY: 'Stay', // TODO translate - LEAVE: 'Leave' // TODO translate + LEAVE: 'Leave', // TODO translate + SCHEDULER: 'Scheduler', // TODO translate + SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP.', // TODO translate + SCHEDULER_HELP_2: 'The NTP service needs to be active if using the calendar.', // TODO translate + SCHEDULE: 'Schedule', // TODO translate SCHEDULE: 'Schedule', // TODO translate + TIME: 'Time', // TODO translate + TIMER: 'Timer', // TODO translate + WEEKLY: 'Weekly', // TODO translate + SCHEDULE_SAVED: 'Schedule updated' // TODO translate }; export default pl; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 3c5918163..f4ab275b4 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -307,7 +307,15 @@ const sv: Translation = { BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate STAY: 'Stay', // TODO translate - LEAVE: 'Leave' // TODO translate + LEAVE: 'Leave', // TODO translate + SCHEDULER: 'Scheduler', // TODO translate + SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP.', // TODO translate + SCHEDULER_HELP_2: 'The NTP service needs to be active if using the calendar.', // TODO translate + SCHEDULE: 'Schedule', // TODO translate + TIME: 'Time', // TODO translate + TIMER: 'Timer', // TODO translate + WEEKLY: 'Weekly', // TODO translate + SCHEDULE_SAVED: 'Schedule updated' // TODO translate }; export default sv; diff --git a/interface/src/project/Settings.tsx b/interface/src/project/Settings.tsx index a08b6641c..ca26a6583 100644 --- a/interface/src/project/Settings.tsx +++ b/interface/src/project/Settings.tsx @@ -9,6 +9,7 @@ import { useI18nContext } from 'i18n/i18n-react'; import SettingsApplication from './SettingsApplication'; import SettingsCustomization from './SettingsCustomization'; +import SettingsScheduler from './SettingsScheduler'; const Settings: FC = () => { const { LL } = useI18nContext(); @@ -21,10 +22,12 @@ const Settings: FC = () => { + } /> } /> + } /> } /> diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 8ea6766b3..7862c132d 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -104,6 +104,7 @@ const SettingsCustomization: FC = () => { .th { border-bottom: 1px solid #565656; font-weight: 500; + height: 32px; } &:nth-of-type(1) .th { @@ -352,6 +353,7 @@ const SettingsCustomization: FC = () => { variant="outlined" fullWidth value={selectedDevice} + disabled={numChanges !== 0} onChange={changeSelectedDevice} margin="normal" select @@ -495,11 +497,7 @@ const SettingsCustomization: FC = () => {
{LL.OPTIONS()} - - - + {LL.NAME(1)} {LL.MIN()} {LL.MAX()} {LL.VALUE(0)} @@ -522,7 +520,7 @@ const SettingsCustomization: FC = () => { if (de.m & DeviceEntityMask.DV_WEB_EXCLUDE) { de.m = de.m & ~DeviceEntityMask.DV_FAVORITE; } - setMasks(['']); + setMasks(['']); // forces a refresh }} > @@ -609,6 +607,14 @@ const SettingsCustomization: FC = () => { {numChanges !== 0 && ( + + + + + + + ); + } + }; + + return ( + + {blocker ? : null} + + {LL.SCHEDULER_HELP_1()} + {LL.SCHEDULER_HELP_2()} + + {renderSchedule()} + {renderEditSchedule()} + + + {numChanges !== 0 && ( + + + + + )} + + + + ); +}; + +export default SettingsScheduler; diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index b0fe02a93..6de15adf2 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -16,7 +16,9 @@ import { WriteValue, WriteSensor, WriteAnalog, - SensorData + SensorData, + ScheduleItem, + Schedule } from './types'; export function restart(): AxiosPromise { @@ -94,3 +96,11 @@ export function getSettings(): AxiosPromise { export function getCustomizations(): AxiosPromise { return AXIOS.get('/getCustomizations'); } + +export function readSchedule(): AxiosPromise { + return AXIOS.get('/getSchedule'); +} + +export function writeSchedule(schedule: Schedule): AxiosPromise { + return AXIOS.post('/writeSchedule', schedule); +} diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index cb6ec3648..f5d7d5c74 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -303,3 +303,36 @@ export enum DeviceEntityMask { DV_FAVORITE = 8, DV_DELETED = 128 } + +export interface ScheduleItem { + id: string; // unique index + active: boolean; + deleted?: boolean; // optional + flags: number; + time: string; + cmd: string; + value: string; + description?: string; // optional + o_active?: boolean; + o_deleted?: boolean; + o_flags?: number; + o_time?: string; + o_cmd?: string; + o_value?: string; + o_description?: string; +} + +export interface Schedule { + schedule: ScheduleItem[]; +} + +export enum ScheduleFlag { + SCHEDULE_MON = 1, + SCHEDULE_TUE = 2, + SCHEDULE_WED = 4, + SCHEDULE_THU = 8, + SCHEDULE_FRI = 16, + SCHEDULE_SAT = 32, + SCHEDULE_SUN = 64, + SCHEDULE_TIMER = 128 +} diff --git a/interface/src/utils/time.ts b/interface/src/utils/time.ts index d6977cccc..0343bc6c7 100644 --- a/interface/src/utils/time.ts +++ b/interface/src/utils/time.ts @@ -15,24 +15,3 @@ export const formatDateTime = (dateTime: string) => { export const formatLocalDateTime = (date: Date) => { return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1).substr(0, 19); }; - -// export const pluralize = (count: number, noun: string) => -// `${Intl.NumberFormat().format(count)} ${noun}${count !== 1 ? 's' : ''}`; - -// export const formatDurationSec = (duration_sec: number) => { -// if (duration_sec === 0) { -// return ' '; -// } -// const roundTowardsZero = duration_sec > 0 ? Math.floor : Math.ceil; -// return ( -// ', ' + -// roundTowardsZero(duration_sec / 86400) + -// 'd ' + -// (roundTowardsZero(duration_sec / 3600) % 24) + -// 'h ' + -// (roundTowardsZero(duration_sec / 60) % 60) + -// 'm ' + -// (roundTowardsZero(duration_sec) % 60) + -// 's' -// ); -// }; diff --git a/mock-api/server.js b/mock-api/server.js index 9a88c6700..fb8586885 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -320,6 +320,7 @@ const EMSESP_WRITE_SENSOR_ENDPOINT = REST_ENDPOINT_ROOT + 'writeSensor'; const EMSESP_WRITE_ANALOG_ENDPOINT = REST_ENDPOINT_ROOT + 'writeAnalog'; const EMSESP_CUSTOM_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities'; const EMSESP_RESET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'resetCustomizations'; +const EMSESP_WRITE_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeSchedule'; settings = { locale: 'en', @@ -598,6 +599,46 @@ const emsesp_devicedata_4 = { ] }; +// SCHEDULE +let emsesp_schedule = [ + { + id: '1', + active: true, + flags: 31, + time: '07:30', + cmd: 'hc1/mode', + value: 'day', + description: 'Turn on central heating in morning' + }, + { + id: '2', + active: true, + flags: 31, + time: '23:00', + cmd: 'hc1/mode', + value: 'night', + description: 'Turn off central heating for the night' + }, + { + id: '3', + active: true, + flags: 128, + time: '00:01', + cmd: 'thermostat/hc2/seltemp', + value: '20', + description: 'Force thermostat temperature to 20 degrees every minute' + }, + { + id: '4', + active: false, + flags: 85, + time: '04:00', + cmd: 'system/restart', + value: '', + description: 'auto restart EMS-EPS at 4am every other day' + } +]; + // CUSTOMIZATIONS const emsesp_deviceentities_1 = [ @@ -1020,6 +1061,12 @@ rest_server.post(EMSESP_CUSTOM_ENTITIES_ENDPOINT, (req, res) => { res.sendStatus(200); }); +rest_server.post(EMSESP_WRITE_SCHEDULE_ENDPOINT, (req, res) => { + console.log('write schedule'); + console.log(req.body.schedule); + res.sendStatus(200); +}); + rest_server.post(EMSESP_WRITE_VALUE_ENDPOINT, (req, res) => { const devicevalue = req.body.devicevalue; const id = req.body.id; @@ -1308,11 +1355,17 @@ rest_server.get(GET_SETTINGS_ENDPOINT, (req, res) => { const GET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'getCustomizations'; rest_server.get(GET_CUSTOMIZATIONS_ENDPOINT, (req, res) => { - console.log('Customizations:'); + console.log('Customization'); // not implemented yet res.sendStatus(200); }); +const GET_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'getSchedule'; +rest_server.get(GET_SCHEDULE_ENDPOINT, (req, res) => { + console.log('Sending Schedule data'); + res.json(emsesp_schedule); +}); + // start server const expressServer = rest_server.listen(port, () => console.log(`EMS-ESP REST API server running on http://localhost:${port}/api`)