Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add time condition in scenes #1151

Merged
merged 7 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions front/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"preact-router": "^3.2.1",
"qrcode": "^1.4.2",
"react-big-calendar": "^0.22.1",
"react-datepicker": "^2.13.0",
"react-datepicker": "^3.8.0",
"react-select": "^3.0.8",
"set-value": "^3.0.0",
"tabler-ui": "0.0.32",
Expand Down
9 changes: 8 additions & 1 deletion front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,12 @@
"orText": "OR",
"orButton": "+ OR"
},
"checkTime": {
"description": "The scene will continue only if current time is between the selected limits, in the selected days. If a field is left empty, it'll not be taken into account.",
"beforeLabel": "Before",
"afterLabel": "After",
"daysOfTheWeekLabel": "Weekdays"
},
"userPresence": {
"userLabel": "User",
"houseLabel": "House",
Expand Down Expand Up @@ -962,7 +968,8 @@
"stop": "Stop service"
},
"condition": {
"only-continue-if": "Only continue if"
"only-continue-if": "Only continue if",
"check-time": "Time condition"
},
"user": {
"set-seen-at-home": "User seen at home",
Expand Down
9 changes: 8 additions & 1 deletion front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,12 @@
"orText": "OU",
"orButton": "+ OU"
},
"checkTime": {
"description": "La scène continuera si l'heure actuelle est comprise entre les bornes spécifiées, aux jours spécifiés. Si une condition est vide, elle ne sera pas prise en compte.",
"beforeLabel": "Avant",
"afterLabel": "Après",
"daysOfTheWeekLabel": "Jour de la semaine"
},
"userPresence": {
"userLabel": "Utilisateur",
"houseLabel": "Maison",
Expand Down Expand Up @@ -962,7 +968,8 @@
"stop": "Arrêter le service"
},
"condition": {
"only-continue-if": "Continuer seulement si"
"only-continue-if": "Continuer seulement si",
"check-time": "Condition temporelle"
},
"user": {
"set-seen-at-home": "Utilisateur vu à la maison",
Expand Down
13 changes: 12 additions & 1 deletion front/src/routes/scene/edit-scene/ActionCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import TurnOnOffSwitchParams from './actions/TurnOnOffSwitchParams';
import UserPresence from './actions/UserPresence';
import HttpRequest from './actions/HttpRequest';
import CheckUserPresence from './actions/CheckUserPresence';
import CheckTime from './actions/CheckTime';

const deleteActionFromColumn = (columnIndex, rowIndex, deleteAction) => () => {
deleteAction(columnIndex, rowIndex);
Expand All @@ -32,7 +33,8 @@ const ACTION_ICON = {
[ACTIONS.USER.SET_SEEN_AT_HOME]: 'fe fe-home',
[ACTIONS.USER.SET_OUT_OF_HOME]: 'fe fe-home',
[ACTIONS.HTTP.REQUEST]: 'fe fe-link',
[ACTIONS.USER.CHECK_PRESENCE]: 'fe fe-home'
[ACTIONS.USER.CHECK_PRESENCE]: 'fe fe-home',
[ACTIONS.CONDITION.CHECK_TIME]: 'fe fe-watch'
};

const ActionCard = ({ children, ...props }) => (
Expand Down Expand Up @@ -186,6 +188,15 @@ const ActionCard = ({ children, ...props }) => (
setVariables={props.setVariables}
/>
)}
{props.action.type === ACTIONS.CONDITION.CHECK_TIME && (
<CheckTime
action={props.action}
columnIndex={props.columnIndex}
index={props.index}
updateActionProperty={props.updateActionProperty}
setVariables={props.setVariables}
/>
)}
</div>
</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions front/src/routes/scene/edit-scene/actions/CheckTime.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.clearButtonCustom::after {
background-color: #485056;
}
119 changes: 119 additions & 0 deletions front/src/routes/scene/edit-scene/actions/CheckTime.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Select from 'react-select';
import { Component } from 'preact';
import DatePicker from 'react-datepicker';
import { connect } from 'unistore/preact';
import { format } from 'date-fns';
import { Text, Localizer } from 'preact-i18n';
import get from 'get-value';

import 'react-datepicker/dist/react-datepicker.css';
import style from './CheckTime.css';

import fr from 'date-fns/locale/fr';

const weekDays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];

@connect('user', {})
class CheckTime extends Component {
handleBeforeTimeChange = time => {
const timeFormatted = time ? format(time, 'HH:mm') : undefined;
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'before', timeFormatted);
};
handleBeforeAfterChange = time => {
const timeFormatted = time ? format(time, 'HH:mm') : undefined;
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'after', timeFormatted);
};
handleDayOfTheWeekChange = options => {
const values = options ? options.map(option => option.value) : undefined;
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'days_of_the_week', values);
};

render(props, state) {
const language = get(this.props, 'user.language');
const localeSet = language === 'fr' ? fr : 'en';
const before = this.props.action.before
? new Date().setHours(this.props.action.before.substr(0, 2), this.props.action.before.substr(3, 2))
: null;
const after = this.props.action.after
? new Date().setHours(this.props.action.after.substr(0, 2), this.props.action.after.substr(3, 2))
: null;
const daysOfTheWeekOptions = weekDays.map(weekDay => ({
value: weekDay,
label: <Text id={`editScene.triggersCard.scheduledTrigger.daysOfTheWeek.${weekDay}`} />
}));
const selectedWeekDaysOptions = this.props.action.days_of_the_week
? this.props.action.days_of_the_week.map(day => daysOfTheWeekOptions.find(option => option.value === day))
: [];
return (
<div>
<p>
<small>
<Text id="editScene.actionsCard.checkTime.description" />
</small>
</p>
<div class="row">
<div class="col">
<div class="form-group">
<div class="form-label">
<Text id="editScene.actionsCard.checkTime.afterLabel" />
</div>
<Localizer>
<DatePicker
selected={after}
className="form-control"
clearButtonClassName={style.clearButtonCustom}
locale={localeSet}
onChange={this.handleBeforeAfterChange}
placeholderText={<Text id="editScene.actionsCard.checkTime.afterLabel" />}
showTimeSelect
showTimeSelectOnly
isClearable
timeIntervals={5}
timeCaption={<Text id="editScene.actionsCard.checkTime.afterLabel" />}
dateFormat="HH:mm"
/>
</Localizer>
</div>
</div>
<div class="col">
<div class="form-group">
<div class="form-label">
<Text id="editScene.actionsCard.checkTime.beforeLabel" />
</div>
<Localizer>
<DatePicker
selected={before}
className="form-control"
clearButtonClassName={style.clearButtonCustom}
locale={localeSet}
onChange={this.handleBeforeTimeChange}
placeholderText={<Text id="editScene.actionsCard.checkTime.beforeLabel" />}
showTimeSelect
showTimeSelectOnly
isClearable
timeIntervals={5}
timeCaption={<Text id="editScene.actionsCard.checkTime.beforeLabel" />}
dateFormat="HH:mm"
/>
</Localizer>
</div>
</div>
</div>
<div class="form-group">
<div class="form-label">
<Text id="editScene.actionsCard.checkTime.daysOfTheWeekLabel" />
</div>
<Select
defaultValue={[]}
isMulti
value={selectedWeekDaysOptions}
onChange={this.handleDayOfTheWeekChange}
options={daysOfTheWeekOptions}
/>
</div>
</div>
);
}
}

export default CheckTime;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const ACTION_LIST = [
ACTIONS.USER.SET_SEEN_AT_HOME,
ACTIONS.USER.SET_OUT_OF_HOME,
ACTIONS.USER.CHECK_PRESENCE,
ACTIONS.HTTP.REQUEST
ACTIONS.HTTP.REQUEST,
ACTIONS.CONDITION.CHECK_TIME
];

const TRANSLATIONS = ACTION_LIST.reduce((acc, action) => {
Expand Down
45 changes: 45 additions & 0 deletions server/lib/scene/scene.actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ const Promise = require('bluebird');
const Handlebars = require('handlebars');
const set = require('set-value');
const get = require('get-value');
const dayjs = require('dayjs');
const utc = require('dayjs/plugin/utc');
const timezone = require('dayjs/plugin/timezone');
const { ACTIONS, DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../utils/constants');
const { getDeviceFeature } = require('../../utils/device');
const { AbortScene } = require('../../utils/coreErrors');
const { compare } = require('../../utils/compare');
const { parseJsonIfJson } = require('../../utils/json');
const logger = require('../../utils/logger');

dayjs.extend(utc);
dayjs.extend(timezone);

const actionsFunc = {
[ACTIONS.DEVICE.SET_VALUE]: async (self, action, scope, columnIndex, rowIndex) => {
let device;
Expand Down Expand Up @@ -128,6 +134,45 @@ const actionsFunc = {
throw new AbortScene('CONDITION_NOT_VERIFIED');
}
},
[ACTIONS.CONDITION.CHECK_TIME]: async (self, action, scope) => {
const now = dayjs.tz(dayjs(), self.timezone);
if (action.before) {
const beforeDate = dayjs.tz(`${now.format('YYYY-MM-DD')} ${action.before}`, self.timezone);
const isBeforeCondition = now.isBefore(beforeDate);
if (!isBeforeCondition) {
logger.debug(
`Check time before: ${now.format('HH:mm')} > ${beforeDate.format('HH:mm')} condition is not verified.`,
);
throw new AbortScene('CONDITION_IS_BEFORE_HOUR_NOT_VERIFIED');
} else {
logger.debug(`Check time before: ${now.format('HH:mm')} < ${beforeDate.format('HH:mm')} condition is valid.`);
}
}
if (action.after) {
const afterDate = dayjs.tz(`${now.format('YYYY-MM-DD')} ${action.after}`, self.timezone);
const isAfterCondition = now.isAfter(afterDate);
if (!isAfterCondition) {
logger.debug(
`Check time after: ${now.format('HH:mm')} > ${afterDate.format('HH:mm')} condition is not verified.`,
);
throw new AbortScene('CONDITION_IS_AFTER_HOUR_NOT_VERIFIED');
} else {
logger.debug(`Check time after: ${now.format('HH:mm')} > ${afterDate.format('HH:mm')} condition is valid.`);
}
}
if (action.days_of_the_week) {
const currentDayOfTheWeek = now.format('dddd').toLowerCase();
const isCurrentDayInCondition = action.days_of_the_week.indexOf(currentDayOfTheWeek) !== -1;
if (!isCurrentDayInCondition) {
logger.debug(
`Condition isInDayOfWeek not verified. Current day of the week = ${currentDayOfTheWeek}. Allowed days = ${action.days_of_the_week.join(
',',
)}`,
);
throw new AbortScene('CONDITION_IS_IN_DAYS_OF_WEEK_NOT_VERIFIED');
}
}
},
[ACTIONS.USER.SET_SEEN_AT_HOME]: async (self, action) => {
await self.house.userSeen(action.house, action.user);
},
Expand Down
7 changes: 6 additions & 1 deletion server/lib/scene/scene.execute.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { executeActions } = require('./scene.executeActions');
const logger = require('../../utils/logger');
const { AbortScene } = require('../../utils/coreErrors');

/**
* @description Execute a scene by its selector.
Expand All @@ -17,7 +18,11 @@ function execute(sceneSelector, scope) {
try {
await executeActions(this, this.scenes[sceneSelector].actions, scope);
} catch (e) {
logger.error(e);
if (e instanceof AbortScene) {
logger.debug(e);
} else {
logger.error(e);
}
}
});
} catch (e) {
Expand Down
Loading