const triggers = require('../triggers');
const operations = require('./operation-builder');
const conditions = require('./condition-builder');
/**
* @callback ConditionCallback The callback function to determine if the condition is met.
* @returns {boolean} true if the condition is met, otherwise false
*/
/**
* Builder for rule Triggers
*
* @hideconstructor
*/
class TriggerBuilder {
constructor (builder) {
/** @private */
this._builder = builder;
}
/** @private */
_setTrigger (trigger) {
this.currentTrigger = trigger;
return this.currentTrigger;
}
/** @private */
_or () {
this._builder.addTrigger(this.currentTrigger);
return this;
}
/** @private */
_then (fn) {
this._or();
return new operations.OperationBuilder(this._builder, fn);
}
/** @private */
_if (fn) {
this._or();
return new conditions.ConditionBuilder(this._builder, fn);
}
/**
* Specifies a channel event as a source for the rule to fire.
*
* @param {string} channelName the name of the channel
* @returns {ChannelTriggerConfig} the trigger config
*/
channel (channelName) {
return this._setTrigger(new ChannelTriggerConfig(channelName, this));
}
/**
* Specifies a cron schedule for the rule to fire.
*
* @param {string} cronExpression the cron expression
* @returns {CronTriggerConfig} the trigger config
*/
cron (cronExpression) {
return this._setTrigger(new CronTriggerConfig(cronExpression, this));
}
/**
* Specifies a time schedule for the rule to fire.
*
* @param {string} time the time expression (in `HH:mm`) defining the triggering schedule
* @returns {TimeOfDayTriggerConfig} the trigger config
*/
timeOfDay (time) {
return this._setTrigger(new TimeOfDayTriggerConfig(time, this));
}
/**
* Specifies an Item as the source of changes to trigger a rule.
*
* @param {string} itemName the name of the Item
* @returns {ItemTriggerConfig} the trigger config
*/
item (itemName) {
return this._setTrigger(new ItemTriggerConfig(itemName, false, this));
}
/**
* Specifies a group member as the source of changes to trigger a rule.
*
* @param {string} groupName the name of the group
* @returns {ItemTriggerConfig} the trigger config
*/
memberOf (groupName) {
return this._setTrigger(new ItemTriggerConfig(groupName, true, this));
}
/**
* Specifies a Thing status event as a source for the rule to fire.
*
* @param {string} thingUID the UID of the Thing
* @returns {ThingTriggerConfig} the trigger config
*/
thing (thingUID) {
return this._setTrigger(new ThingTriggerConfig(thingUID, this));
}
/**
* Specifies a system event as a source for the rule to fire.
*
* @memberof TriggerBuilder
* @returns {SystemTriggerConfig} the trigger config
*/
system () {
return this._setTrigger(new SystemTriggerConfig(this));
}
/**
* Specifies a DateTime Item whose (optional) date and time schedule the rule to fire.
*
* @param {string} itemName the name of the Item to monitor for change
* @returns {DateTimeTriggerConfig} the trigger config
*/
dateTime (itemName) {
return this._setTrigger(new DateTimeTriggerConfig(itemName, this));
}
}
/**
* {RuleBuilder} RuleBuilder triggers
* @memberof TriggerBuilder
*/
class TriggerConf {
constructor (triggerBuilder) {
/** @private */
this.triggerBuilder = triggerBuilder;
}
/**
* Add an additional Trigger
*
* @returns {TriggerBuilder}
*/
or () {
return this.triggerBuilder._or();
}
/**
* Move to the rule operations
*
* @param {*} [fn] the optional function to execute
* @returns {operations.OperationBuilder}
*/
then (fn) {
return this.triggerBuilder._then(fn);
}
/**
* Move to the rule condition
*
* @param {ConditionCallback} [fn] the optional function to execute
* @returns {conditions.ConditionBuilder}
*/
if (fn) {
return this.triggerBuilder._if(fn);
}
}
/**
* @memberof TriggerBuilder
* @extends TriggerConf
* @hideconstructor
*/
class ChannelTriggerConfig extends TriggerConf {
constructor (channelName, triggerBuilder) {
super(triggerBuilder);
this.channelName = channelName;
this._toOHTriggers = () => [triggers.ChannelEventTrigger(this.channelName, this.eventName)];
}
/** @private */
describe (compact) {
if (compact) {
return this.channelName + (this.eventName ? `:${this.eventName}` : '');
} else {
return `matches channel "${this.channelName}"` + (this.eventName ? `for event ${this.eventName}` : '');
}
}
/**
* trigger a specific event name
*
* @param {string} eventName
* @returns {ChannelTriggerConfig}
*/
to (eventName) {
return this.triggered(eventName);
}
/**
* trigger a specific event name
*
* @param {string} eventName
* @returns {ChannelTriggerConfig}
*/
triggered (eventName) {
this.eventName = eventName || '';
return this;
}
/** @private */
_complete () {
return typeof (this.eventName) !== 'undefined';
}
}
/**
* Cron based trigger
*
* @memberof TriggerBuilder
* @extends TriggerConf
* @hideconstructor
*/
class CronTriggerConfig extends TriggerConf {
constructor (timeStr, triggerBuilder) {
super(triggerBuilder);
/** @private */
this.timeStr = timeStr;
/** @private */
this._complete = () => true;
/** @private */
this._toOHTriggers = () => [triggers.GenericCronTrigger(this.timeStr)];
/** @private */
this.describe = (compact) => compact ? `cron_${this.timeStr}` : `matches cron "${this.timeStr}"`;
}
}
/**
* Time of day based trigger
*
* @memberof TriggerBuilder
* @extends TriggerConf
* @hideconstructor
*/
class TimeOfDayTriggerConfig extends TriggerConf {
constructor (timeStr, triggerBuilder) {
super(triggerBuilder);
/** @private */
this.timeStr = timeStr;
/** @private */
this._complete = () => true;
/** @private */
this._toOHTriggers = () => [triggers.TimeOfDayTrigger(this.timeStr)];
/** @private */
this.describe = (compact) => compact ? `timeOfDay_${this.timeStr}` : `matches time of day "${this.timeStr}"`;
}
}
/**
* Item based trigger
*
* @memberof TriggerBuilder
* @extends TriggerConf
* @hideconstructor
*/
class ItemTriggerConfig extends TriggerConf {
constructor (itemOrName, isGroup, triggerBuilder) {
super(triggerBuilder);
this.type = isGroup ? 'memberOf' : 'Item';
if (typeof itemOrName !== 'string') {
itemOrName = itemOrName.name;
}
/** @private */
this.item_name = itemOrName;
/** @private */
this.describe = () => `${this.type} ${this.item_name} changed`;
this.of = this.to; // receivedCommand().of(..)
}
/**
* Item to
*
* @param {*} value this Item should be triggered to
* @returns {ItemTriggerConfig}
*/
to (value) {
this.to_value = value;
return this;
}
/**
* Item from
* @param {*} value this items should be triggered from
* @returns {ItemTriggerConfig}
*/
from (value) {
if (this.op_type !== 'changed') {
throw Error('.from(..) only available for .changed()');
}
this.from_value = value;
return this;
}
/**
* Item changed to OFF
*
* @returns {ItemTriggerConfig}
*/
toOff () {
return this.to('OFF');
}
/**
* Item changed to ON
*
* @returns {ItemTriggerConfig}
*/
toOn () {
return this.to('ON');
}
/**
* Item changed from OFF
*
* @returns {ItemTriggerConfig}
*/
fromOff () {
return this.from('OFF');
}
/**
* Item changed from ON
*
* @returns {ItemTriggerConfig}
*/
fromOn () {
return this.from('ON');
}
/**
* Item received command
*
* @returns {ItemTriggerConfig}
*/
receivedCommand () {
this.op_type = 'receivedCommand';
return this;
}
/**
* Item received update
*
* @returns {ItemTriggerConfig}
*/
receivedUpdate () {
this.op_type = 'receivedUpdate';
return this;
}
/**
* Item changed state
*
* @returns {ItemTriggerConfig}
*/
changed () {
this.op_type = 'changed';
return this;
}
/**
* For timespan
* @param {*} timespan
* @returns {ItemTriggerConfig}
*/
for (timespan) {
return new operations.TimingItemStateOperation(this, timespan);
}
/** @private */
_complete () {
return typeof (this.op_type) !== 'undefined';
}
/** @private */
describe (compact) {
switch (this.op_type) {
case 'changed':
if (compact) {
let transition = this.from_value + '=>' || '';
if (this.to_value) {
transition = (transition || '=>') + this.to_value;
}
return `${this.item_name} ${transition}/Δ`;
} else {
let transition = 'changed';
if (this.from_value) {
transition += ` from ${this.from_value}`;
}
if (this.to_value) {
transition += ` to ${this.to_value}`;
}
return `${this.item_name} ${transition}`;
}
case 'receivedCommand':
return compact ? `${this.item_name}/⌘` : `${this.type} ${this.item_name} received command`;
case 'receivedUpdate':
return compact ? `${this.item_name}/↻` : `${this.type} ${this.item_name} received update`;
default:
throw Error('Unknown operation type: ' + this.op_type);
}
}
/** @private */
_toOHTriggers () {
if (this.type === 'memberOf') {
switch (this.op_type) {
case 'changed':
return [triggers.GroupStateChangeTrigger(this.item_name, this.from_value, this.to_value)];
case 'receivedCommand':
return [triggers.GroupCommandTrigger(this.item_name, this.to_value)];
case 'receivedUpdate':
return [triggers.GroupStateUpdateTrigger(this.item_name, this.to_value)];
default:
throw Error('Unknown operation type: ' + this.op_type);
}
} else {
switch (this.op_type) {
case 'changed':
return [triggers.ItemStateChangeTrigger(this.item_name, this.from_value, this.to_value)];
case 'receivedCommand':
return [triggers.ItemCommandTrigger(this.item_name, this.to_value)];
case 'receivedUpdate':
return [triggers.ItemStateUpdateTrigger(this.item_name, this.to_value)];
default:
throw Error('Unknown operation type: ' + this.op_type);
}
}
}
/** @private */
_executeHook () {
const getReceivedCommand = (args) => args.receivedCommand;
if (this.op_type === 'receivedCommand') { // add the received command as 'it'
return function (next, args) {
const it = getReceivedCommand(args);
return next({
...args,
it
});
};
} else {
return null;
}
}
}
/**
* Thing based trigger
*
* @memberof TriggerBuilder
* @extends TriggerConf
* @hideconstructor
*/
class ThingTriggerConfig extends TriggerConf {
constructor (thingUID, triggerBuilder) {
super(triggerBuilder);
/** @private */
this.thingUID = thingUID;
}
/** @private */
_complete () {
return typeof (this.op_type) !== 'undefined';
}
/** @private */
describe (compact) {
switch (this.op_type) {
case 'changed':
let transition = 'changed'; // eslint-disable-line no-case-declarations
if (this.to_value) {
transition += ` to ${this.to_value}`;
}
if (this.from_value) {
transition += ` from ${this.from_value}`;
}
return `${this.thingUID} ${transition}`;
case 'updated':
return compact ? `${this.thingUID}/updated` : `Thing ${this.thingUID} received update`;
default:
throw Error('Unknown operation type: ' + this.op_type);
}
}
/**
* thing changed
*
* @returns {ThingTriggerConfig}
*/
changed () {
this.op_type = 'changed';
return this;
}
/**
* thing updates
*
* @returns {ThingTriggerConfig}
*/
updated () {
this.op_type = 'updated';
return this;
}
/**
* thing status changed from
*
* @returns {ThingTriggerConfig}
*/
from (value) {
if (this.op_type !== 'changed') {
throw Error('.from(..) only available for .changed()');
}
this.from_value = value;
return this;
}
/**
* thing status changed to
*
* @returns {ThingTriggerConfig}
*/
to (value) {
this.to_value = value;
return this;
}
/** @private */
_toOHTriggers () {
switch (this.op_type) {
case 'changed':
return [triggers.ThingStatusChangeTrigger(this.thingUID, this.to_value, this.from_value)];
case 'updated':
return [triggers.ThingStatusUpdateTrigger(this.thingUID, this.to_value)];
default:
throw Error('Unknown operation type: ' + this.op_type);
}
}
}
/**
* System based trigger
*
* @memberof TriggerBuilder
* @extends TriggerConf
* @hideconstructor
*/
class SystemTriggerConfig extends TriggerConf {
constructor (triggerBuilder) {
super(triggerBuilder);
this._toOHTriggers = () => [triggers.SystemStartlevelTrigger(this.level)];
this.describe = (compact) => compact ? `system:${this.level}` : `system level "${this.level}"`;
}
/** @private */
_complete () {
return typeof (this.level) !== 'undefined';
}
/**
* System trigger
*
* @returns {SystemTriggerConfig}
*/
rulesLoaded () {
return this.startLevel(40);
}
/**
* System trigger
*
* @returns {SystemTriggerConfig}
*/
ruleEngineStarted () {
return this.startLevel(50);
}
/**
* System trigger
*
* @returns {SystemTriggerConfig}
*/
userInterfacesStarted () {
return this.startLevel(70);
}
/**
* System trigger
*
* @returns {SystemTriggerConfig}
*/
thingsInitialized () {
return this.startLevel(80);
}
/**
* System trigger
*
* @returns {SystemTriggerConfig}
*/
startupComplete () {
return this.startLevel(100);
}
/**
* System trigger
*
* @param {number} level
* @returns {SystemTriggerConfig}
*/
startLevel (level) {
if (typeof (this.level) !== 'undefined') {
throw Error('Level already set');
}
this.level = level;
return this;
}
}
/**
* DateTime Item based trigger
*
* @memberof TriggerBuilder
* @extends TriggerConf
* @hideconstructor
*/
class DateTimeTriggerConfig extends TriggerConf {
constructor (itemName, triggerBuilder) {
super(triggerBuilder);
/** @private */
this._itemName = itemName;
/** @private */
this._timeOnly = false;
/** @private */
this._offset = 0;
/** @private */
this._complete = () => true;
/** @private */
this._toOHTriggers = () => [triggers.DateTimeTrigger(this._itemName, this._timeOnly, this._offset)];
/** @private */
this.describe = (compact) => compact ? `dateTime_${this._itemName}` : `matches ${this._timeOnly ? 'time' : 'date and time'} of Item "${this._itemName}"`;
}
/**
* Specifies whether only the time of the Item should be compared or the date and time.
*
* @param {boolean} [timeOnly=true]
* @returns {DateTimeTriggerConfig}
*/
timeOnly (timeOnly = true) {
this._timeOnly = timeOnly;
return this;
}
/**
* Specifies the offset in seconds to add to the time of the DateTime Item.
*
* @param {number} offset
* @returns {DateTimeTriggerConfig}
*/
withOffset (offset) {
this._offset = offset;
return this;
}
}
module.exports = {
CronTriggerConfig,
ChannelTriggerConfig,
ItemTriggerConfig,
ThingTriggerConfig,
SystemTriggerConfig,
TriggerBuilder
};