yunkong2.javascript/lib/sandbox.js

2365 lines
105 KiB
JavaScript
Raw Normal View History

2018-07-21 19:30:07 +08:00
'use strict';
// let context = {
// adapter,
// mods,
// errorLogFunction,
// subscriptions,
// subscribedPatterns,
// states,
// adapterSubs,
// objects,
// cacheObjectEnums,
// stateIds,
// logWithLineInfo,
// timers,
// enums,
// channels,
// devices,
// isEnums
// };
/**
* @typedef {Object} SandboxContext
* @property {Record<string, string[]>} channels
* @property {Record<string, string[]>} devices
* @property {string[]} stateIds
*/
/**
* @param {{[prop: string]: any} & SandboxContext} context
*/
function sandBox(script, name, verbose, debug, context) {
const consts = require('./consts');
const words = require('./words');
const eventObj = require('./eventObj');
const patternCompareFunctions = require('./patternCompareFunctions');
const nodeSchedule = require('node-schedule');
const adapter = context.adapter;
const mods = context.mods;
const states = context.states;
const objects = context.objects;
const timers = context.timers;
const enums = context.enums;
function errorInCallback(e) {
context.logError('Error in callback', e);
}
function unsubscribePattern(script, pattern) {
if (adapter.config.subscribe) {
if (script.subscribes[pattern]) {
script.subscribes[pattern]--;
if (!script.subscribes[pattern]) delete script.subscribes[pattern];
}
if (context.subscribedPatterns[pattern]) {
context.subscribedPatterns[pattern]--;
if (!context.subscribedPatterns[pattern]) {
adapter.unsubscribeForeignStates(pattern);
delete context.subscribedPatterns[pattern];
// if pattern was regex or with * some states will stay in RAM, but it is OK.
if (states[pattern]) delete states[pattern];
}
}
}
}
function subscribePattern(script, pattern) {
if (adapter.config.subscribe) {
if (!script.subscribes[pattern]) {
script.subscribes[pattern] = 1;
} else {
script.subscribes[pattern]++;
}
if (!context.subscribedPatterns[pattern]) {
context.subscribedPatterns[pattern] = 1;
adapter.subscribeForeignStates(pattern);
// request current value to deliver old value on change.
if (typeof pattern === 'string' && pattern.indexOf('*') === -1) {
adapter.getForeignState(pattern, function (err, state) {
if (state) states[pattern] = state;
});
} else {
adapter.getForeignStates(pattern, function (err, _states) {
if (_states) {
for (const id in _states) {
if (!_states.hasOwnProperty(id)) continue;
states[id] = _states[id];
}
}
});
}
} else {
context.subscribedPatterns[pattern]++;
}
}
}
/**
* @typedef PatternCompareFunctionArray
* @type {Array<any> & {logic?: string}}
*/
function getPatternCompareFunctions(pattern) {
let func;
/** @type {PatternCompareFunctionArray} */
const functions = [];
functions.logic = pattern.logic || 'and';
//adapter.log.info('## '+JSON.stringify(pattern));
for (const key in pattern) {
if (!pattern.hasOwnProperty(key)) continue;
if (key === 'logic') continue;
if (key === 'change' && pattern.change === 'any') continue;
if (!(func = patternCompareFunctions[key])) continue;
if (typeof (func = func(pattern)) !== 'function') continue;
functions.push(func);
}
return functions;
}
/** @typedef {{attr: string, value: string, idRegExp?: RegExp}} Selector */
/**
* Splits a selector string into attribute and value
* @param {string} selector The selector string to split
* @returns {Selector}
*/
function splitSelectorString(selector) {
const parts = selector.split('=', 2);
if (parts[1] && parts[1][0] === '"') {
parts[1] = parts[1].substring(1);
const len = parts[1].length;
if (parts[1] && parts[1][len - 1] === '"') parts[1] = parts[1].substring(0, len - 1);
}
if (parts[1] && parts[1][0] === "'") {
parts[1] = parts[1].substring(1);
const len = parts[1].length;
if (parts[1] && parts[1][len - 1] === "'") parts[1] = parts[1].substring(0, len - 1);
}
if (parts[1]) parts[1] = parts[1].trim();
parts[0] = parts[0].trim();
return {attr: parts[0], value: parts[1]};
}
/**
* Transforms a selector string with wildcards into a regular expression
* @param {string} str The selector string to transform into a regular expression
*/
function selectorStringToRegExp(str) {
if (str[0] === '*') {
// wildcard at the start, match the end of the string
return new RegExp(str.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
} else if (str[str.length - 1] === '*') {
// wildcard at the end, match the start of the string
return new RegExp('^' + str.replace(/\./g, '\\.').replace(/\*/g, '.*'));
} else {
// wildcard potentially in the middle, match whatever
return new RegExp(str.replace(/\./g, '\\.').replace(/\*/g, '.*'));
}
}
/**
* Adds a regular expression for selectors targeting the state ID
* @param {Selector} selector The selector to apply the transform to
* @returns {Selector}
*/
function addRegExpToIdAttrSelectors(selector) {
if (
(selector.attr === 'id' || selector.attr === 'state.id')
&& (!selector.idRegExp && selector.value)
) {
return {
attr: selector.attr,
value: selector.value,
idRegExp: selectorStringToRegExp(selector.value)
};
} else {
return selector;
}
}
/**
* Tests if a value loosely equals (==) the reference string.
* In contrast to the equality operator, this treats true == "true" aswell
* so we can test common and native attributes for boolean values
* @param {boolean | string | number | undefined} value The value to compare with the reference
* @param {string} reference The reference to compare the value to
*/
function looselyEqualsString(value, reference) {
// For booleans, compare the string representation
// For other types do a loose comparison
return (typeof value === 'boolean')
? (value && reference === 'true') || (!value && reference === 'false')
: value == reference
;
}
const sandbox = {
mods: mods,
_id: script._id,
name: name, // deprecated
scriptName: name,
instance: adapter.instance,
verbose: verbose,
request: mods.request,
exports: {}, // Polyfill for the exports object in TypeScript modules
require: function (md) {
console.log('REQUIRE: ' + md);
if (mods[md]) {
return mods[md];
} else {
try {
mods[md] = require(__dirname + '/../node_modules/' + md);
return mods[md];
} catch (e) {
try {
mods[md] = require(__dirname + '/../../' + md);
return mods[md];
} catch (e) {
context.logError(name, e, 6);
}
}
}
},
Buffer: Buffer,
__engine: {
__subscriptions: 0,
__schedules: 0
},
/**
* @param {string} selector
* @returns {iobJS.QueryResult}
*/
$: function (selector) {
// following is supported
// 'type[commonAttr=something]', 'id[commonAttr=something]', id(enumName="something")', id{nativeName="something"}
// Type can be state, channel or device
// Attr can be any of the common attributes and can have wildcards *
// E.g. "state[id='hm-rpc.0.*]" or "hm-rpc.0.*" returns all states of adapter instance hm-rpc.0
// channel(room="Living room") => all states in room "Living room"
// channel{TYPE=BLIND}[state.id=*.LEVEL]
// Switch all states with .STATE of channels with role "switch" in "Wohnzimmer" to false
// $('channel[role=switch][state.id=*.STATE](rooms=Wohnzimmer)').setState(false);
//
// Following functions are possible, setValue, getValue (only from first), on, each
// Todo CACHE!!!
const result = {};
let name = '';
/** @type {string[]} */
const commonStrings = [];
/** @type {string[]} */
const enumStrings = [];
/** @type {string[]} */
const nativeStrings = [];
let isInsideName = true;
let isInsideCommonString = false;
let isInsideEnumString = false;
let isInsideNativeString = false;
let currentCommonString = '';
let currentNativeString = '';
let currentEnumString = '';
// parse string
let selectorHasInvalidType = false;
if (typeof selector === 'string') {
for (let i = 0; i < selector.length; i++) {
if (selector[i] === '{') {
isInsideName = false;
if (isInsideCommonString || isInsideEnumString || isInsideNativeString) {
// Error
break;
}
isInsideNativeString = true;
} else if (selector[i] === '}') {
isInsideNativeString = false;
nativeStrings.push(currentNativeString);
currentNativeString = '';
} else if (selector[i] === '[') {
isInsideName = false;
if (isInsideCommonString || isInsideEnumString || isInsideNativeString) {
// Error
break;
}
isInsideCommonString = true;
} else if (selector[i] === ']') {
isInsideCommonString = false;
commonStrings.push(currentCommonString);
currentCommonString = '';
} else if (selector[i] === '(') {
isInsideName = false;
if (isInsideCommonString || isInsideEnumString || isInsideNativeString) {
// Error
break;
}
isInsideEnumString = true;
} else if (selector[i] === ')') {
isInsideEnumString = false;
enumStrings.push(currentEnumString);
currentEnumString = '';
} else if (isInsideName) {
name += selector[i];
} else if (isInsideCommonString) {
currentCommonString += selector[i];
} else if (isInsideEnumString) {
currentEnumString += selector[i];
} else if (isInsideNativeString) {
currentNativeString += selector[i];
} //else {
// some error
//}
}
} else {
selectorHasInvalidType = true;
}
// If some error in the selector
if (selectorHasInvalidType || isInsideEnumString || isInsideCommonString || isInsideNativeString) {
result.length = 0;
result.each = function () {
return this;
};
result.getState = function () {
return null;
};
result.setState = function () {
return this;
};
result.on = function () {
return this;
};
}
if (isInsideEnumString) {
adapter.log.warn('Invalid selector: enum close bracket ")" cannot be found in "' + selector + '"');
result.error = 'Invalid selector: enum close bracket ")" cannot be found';
return result;
} else if (isInsideCommonString) {
adapter.log.warn('Invalid selector: common close bracket "]" cannot be found in "' + selector + '"');
result.error = 'Invalid selector: common close bracket "]" cannot be found';
return result;
} else if (isInsideNativeString) {
adapter.log.warn('Invalid selector: native close bracket "}" cannot be found in "' + selector + '"');
result.error = 'Invalid selector: native close bracket "}" cannot be found';
return result;
} else if (selectorHasInvalidType) {
const message = `Invalid selector: selector must be a string but is of type ${typeof selector}`;
adapter.log.warn(message);
result.error = message;
return result;
}
/** @type {Selector[]} */
let commonSelectors = commonStrings.map(selector => splitSelectorString(selector));
let nativeSelectors = nativeStrings.map(selector => splitSelectorString(selector));
const enumSelectorObjects = enumStrings.map(_enum => splitSelectorString(_enum));
const allSelectors = commonSelectors.concat(nativeSelectors, enumSelectorObjects);
// These selectors match the state or object ID and don't belong in the common/native selectors
// Also use RegExp for the ID matching
const stateIdSelectors = allSelectors
.filter(selector => selector.attr === 'state.id')
.map(selector => addRegExpToIdAttrSelectors(selector))
;
const objectIdSelectors = allSelectors
.filter(selector => selector.attr === 'id')
.map(selector => addRegExpToIdAttrSelectors(selector))
;
commonSelectors = commonSelectors.filter(selector => selector.attr !== 'state.id' && selector.attr !== 'id');
nativeSelectors = nativeSelectors.filter(selector => selector.attr !== 'state.id' && selector.attr !== 'id');
const enumSelectors = enumSelectorObjects
.filter(selector => selector.attr !== 'state.id' && selector.attr !== 'id')
// enums are filtered by their enum id, so transform the selector into that
.map(selector => `enum.${selector.attr}.${selector.value}`)
;
name = name.trim();
if (name === 'channel' || name === 'device') {
// Fill the channels and devices objects with the IDs of all their states
// so we can loop over them afterwards
if (!context.channels || !context.devices) {
context.channels = {};
context.devices = {};
for (const _id in objects) {
if (objects.hasOwnProperty(_id) && objects[_id].type === 'state') {
const parts = _id.split('.');
parts.pop();
const chn = parts.join('.');
parts.pop();
const dev = parts.join('.');
context.devices[dev] = context.devices[dev] || [];
context.devices[dev].push(_id);
context.channels[chn] = context.channels[chn] || [];
context.channels[chn].push(_id);
}
}
}
}
/**
* applies all selectors targeting an object or state ID
* @param {string} objId
* @param {Selector[]} selectors
*/
function applyIDSelectors(objId, selectors) {
// Only keep the ID if it matches every ID selector
return selectors.every(selector => {
return selector.idRegExp == null || selector.idRegExp.test(objId);
});
}
/**
* Applies all selectors targeting the Object common properties
* @param {string} objId - The ID of the object in question
*/
function applyCommonSelectors(objId) {
const obj = objects[objId];
if (obj == null || obj.common == null) return false;
const objCommon = obj.common;
// make sure this object satisfies all selectors
return commonSelectors.every(selector => {
return (
// ensure a property exists
(selector.value === undefined && objCommon[selector.attr] !== undefined)
// or match exact values
|| looselyEqualsString(objCommon[selector.attr], selector.value)
);
});
}
/**
* Applies all selectors targeting the Object native properties
* @param {string} objId - The ID of the object in question
*/
function applyNativeSelectors(objId) {
const obj = objects[objId];
if (obj == null || obj.native == null) return false;
const objNative = obj.native;
// make sure this object satisfies all selectors
return nativeSelectors.every(selector => {
return (
// ensure a property exists
(selector.value === undefined && objNative[selector.attr] !== undefined)
// or match exact values
|| looselyEqualsString(objNative[selector.attr], selector.value)
);
});
}
/**
* Applies all selectors targeting the Objects enums
* @param {string} objId - The ID of the object in question
*/
function applyEnumSelectors(objId) {
const enumIds = [];
eventObj.getObjectEnumsSync(context, objId, enumIds);
// make sure this object satisfies all selectors
return enumSelectors.every(_enum => enumIds.indexOf(_enum) > -1);
}
/** @type {string[]} */
let res = [];
if (name === 'channel') {
// go through all channels
res = Object.keys(context.channels)
// filter out those that don't match every ID selector for the channel ID
.filter(channelId => applyIDSelectors(channelId, objectIdSelectors))
// filter out those that don't match every common selector
.filter(channelId => applyCommonSelectors(channelId))
// filter out those that don't match every native selector
.filter(channelId => applyNativeSelectors(channelId))
// filter out those that don't match every enum selector
.filter(channelId => applyEnumSelectors(channelId))
// retrieve the state ID collection for all remaining channels
.map(id => context.channels[id])
// and flatten the array to get only the state IDs
.reduce((acc, next) => acc.concat(next), [])
// now filter out those that don't match every ID selector for the state ID
.filter(stateId => applyIDSelectors(stateId, stateIdSelectors))
;
} else if (name === 'device') {
// go through all devices
res = Object.keys(context.devices)
// filter out those that don't match every ID selector for the channel ID
.filter(deviceId => applyIDSelectors(deviceId, objectIdSelectors))
// filter out those that don't match every common selector
.filter(deviceId => applyCommonSelectors(deviceId))
// filter out those that don't match every native selector
.filter(deviceId => applyNativeSelectors(deviceId))
// filter out those that don't match every enum selector
.filter(deviceId => applyEnumSelectors(deviceId))
// retrieve the state ID collection for all remaining devices
.map(id => context.devices[id])
// and flatten the array to get only the state IDs
.reduce((acc, next) => acc.concat(next), [])
// now filter out those that don't match every ID selector for the state ID
.filter(stateId => applyIDSelectors(stateId, stateIdSelectors))
;
} else {
// go through all states
res = context.stateIds;
// if the "name" is not state then we filter for the ID aswell
if (name && name !== 'state') {
const r = new RegExp('^' + name.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
res = res.filter(id => r.test(id));
}
res = res
// filter out those that don't match every ID selector for the object ID or the state ID
.filter(id => applyIDSelectors(id, objectIdSelectors))
.filter(id => applyIDSelectors(id, stateIdSelectors))
// filter out those that don't match every common selector
.filter(id => applyCommonSelectors(id))
// filter out those that don't match every native selector
.filter(id => applyNativeSelectors(id))
// filter out those that don't match every enum selector
.filter(id => applyEnumSelectors(id))
;
}
for (let i = 0; i < res.length; i++) {
result[i] = res[i];
}
result.length = res.length;
result.each = function (callback) {
if (typeof callback === 'function') {
let r;
for (let i = 0; i < this.length; i++) {
r = callback(result[i], i);
if (r === false) break;
}
}
return this;
};
result.getState = function (callback) {
if (adapter.config.subscribe) {
if (typeof callback !== 'function') {
sandbox.log('You cannot use this function synchronous', 'error');
} else {
adapter.getForeignState(this[0], callback);
}
} else {
if (this[0]) return states[this[0]];
return null;
}
};
result.setState = function (state, isAck, callback) {
if (typeof isAck === 'function') {
callback = isAck;
isAck = undefined;
}
if (isAck === true || isAck === false || isAck === 'true' || isAck === 'false') {
if (typeof state === 'object') {
state.ack = isAck;
} else {
state = {val: state, ack: isAck};
}
}
let cnt = 0;
for (let i = 0; i < this.length; i++) {
cnt++;
adapter.setForeignState(this[i], state, function () {
if (!--cnt && typeof callback === 'function') callback();
});
}
return this;
};
result.on = function (callbackOrId, value) {
for (let i = 0; i < this.length; i++) {
sandbox.subscribe(this[i], callbackOrId, value);
}
return this;
};
return result;
},
log: function (msg, sev) {
if (!sev) sev = 'info';
if (!adapter.log[sev]) {
msg = 'Unknown severity level "' + sev + '" by log of [' + msg + ']';
sev = 'warn';
}
adapter.log[sev](name + ': ' + msg);
},
exec: function (cmd, callback) {
if (!adapter.config.enableExec) {
const error = 'exec is not available. Please enable "Enable Exec" option in instance settings';
adapter.log.error(error);
sandbox.log(error);
if (typeof callback === 'function') {
setImmediate(callback, error);
}
} else {
if (sandbox.verbose) {
sandbox.log('exec: ' + cmd, 'info');
}
if (debug) {
sandbox.log(words._('Command %s was not executed, while debug mode is active', cmd), 'warn');
if (typeof callback === 'function') {
setImmediate(function () {
callback();
});
}
} else {
return mods.child_process.exec(cmd, callback);
}
}
},
email: function (msg) {
if (sandbox.verbose) sandbox.log('email(msg=' + JSON.stringify(msg) + ')', 'info');
adapter.sendTo('email', msg);
},
pushover: function (msg) {
if (sandbox.verbose) sandbox.log('pushover(msg=' + JSON.stringify(msg) + ')', 'info');
adapter.sendTo('pushover', msg);
},
subscribe: function (pattern, callbackOrId, value) {
if (pattern && Array.isArray(pattern)) {
const result = [];
for (let t = 0; t < pattern.length; t++) {
result.push(sandbox.subscribe(pattern[t], callbackOrId, value));
}
return result;
}
if (pattern && pattern.id && Array.isArray(pattern.id)) {
const result_ = [];
for (let tt = 0; tt < pattern.id.length; tt++) {
const pa = JSON.parse(JSON.stringify(pattern));
pa.id = pattern.id[tt];
result_.push(sandbox.subscribe(pa, callbackOrId, value));
}
return result_;
}
// try to detect astro or cron (by spaces)
if (typeof pattern === 'object' || (typeof pattern === 'string' && pattern.match(/[,/\d*]+\s[,/\d*]+\s[,/\d*]+/))) {
if (pattern.astro) {
return sandbox.schedule(pattern, callbackOrId);
} else if (pattern.time) {
return sandbox.schedule(pattern.time, callbackOrId);
}
}
let callback;
sandbox.__engine.__subscriptions += 1;
// source is set by regexp if defined as /regexp/
if (typeof pattern !== 'object' || pattern instanceof RegExp || pattern.source) {
pattern = {id: pattern, change: 'ne'};
}
if (pattern.id !== undefined && !pattern.id) {
adapter.log.error('Error by subscription: empty ID defined. All states matched.');
return;
}
// add adapter namespace if nothing given
if (pattern.id && typeof pattern.id === 'string' && pattern.id.indexOf('.') === -1) {
pattern.id = adapter.namespace + '.' + pattern.id;
}
if (typeof callbackOrId === 'function') {
callback = callbackOrId;
} else {
const that = this;
if (typeof value === 'undefined') {
callback = function (obj) {
that.setState(callbackOrId, obj.newState.val);
};
} else {
callback = function (/* obj */) {
that.setState(callbackOrId, value);
};
}
}
const subs = {
pattern: pattern,
callback: function (obj) {
if (typeof callback === 'function') {
try {
callback.call(sandbox, obj);
} catch (e) {
errorInCallback(e); // adapter.log.error('Error in callback: ' + e);
}
}
},
name: name
};
// try to extract adapter
if (pattern.id && typeof pattern.id === 'string') {
const parts = pattern.id.split('.');
const a = parts[0] + '.' + parts[1];
const _adapter = 'system.adapter.' + a;
if (objects[_adapter] && objects[_adapter].common && objects[_adapter].common.subscribable) {
const alive = 'system.adapter.' + a + '.alive';
context.adapterSubs[alive] = context.adapterSubs[alive] || [];
const subExists = context.adapterSubs[alive].filter(function (sub) {
return sub === pattern.id;
}).length > 0;
if (!subExists) {
context.adapterSubs[alive].push(pattern.id);
adapter.sendTo(a, 'subscribe', pattern.id);
}
}
}
if (sandbox.verbose) sandbox.log('subscribe: ' + JSON.stringify(subs), 'info');
subscribePattern(script, pattern.id);
subs.patternCompareFunctions = getPatternCompareFunctions(pattern);
context.subscriptions.push(subs);
if (pattern.enumName || pattern.enumId) context.isEnums = true;
return subs;
},
getSubscriptions: function () {
const result = {};
for (let s = 0; s < context.subscriptions.length; s++) {
result[context.subscriptions[s].pattern.id] = result[context.subscriptions[s].pattern.id] || [];
result[context.subscriptions[s].pattern.id].push({ name: context.subscriptions[s].name, pattern: context.subscriptions[s].pattern });
}
if (sandbox.verbose) sandbox.log('getSubscriptions() => ' + JSON.stringify(result), 'info');
return result;
},
adapterSubscribe: function (id) {
if (typeof id !== 'string') {
adapter.log.error('adapterSubscribe: invalid type of id' + typeof id);
return;
}
const parts = id.split('.');
const _adapter = 'system.adapter.' + parts[0] + '.' + parts[1];
if (objects[_adapter] && objects[_adapter].common && objects[_adapter].common.subscribable) {
const a = parts[0] + '.' + parts[1];
const alive = 'system.adapter.' + a + '.alive';
context.adapterSubs[alive] = context.adapterSubs[alive] || [];
context.adapterSubs[alive].push(id);
if (sandbox.verbose) sandbox.log('adapterSubscribe: ' + a + ' - ' + id, 'info');
adapter.sendTo(a, 'subscribe', id);
}
},
adapterUnsubscribe: function (id) {
return sandbox.unsubscribe(id);
},
unsubscribe: function (idOrObject) {
if (idOrObject && Array.isArray(idOrObject)) {
const result = [];
for (let t = 0; t < idOrObject.length; t++) {
result.push(sandbox.unsubscribe(idOrObject[t]));
}
return result;
}
if (sandbox.verbose) sandbox.log('adapterUnsubscribe(id=' + idOrObject + ')', 'info');
if (typeof idOrObject === 'object') {
for (let i = context.subscriptions.length - 1; i >= 0; i--) {
if (context.subscriptions[i] === idOrObject) {
unsubscribePattern(context.subscriptions[i].pattern.id);
context.subscriptions.splice(i, 1);
sandbox.__engine.__subscriptions--;
return true;
}
}
} else {
let deleted = 0;
for (let i = context.subscriptions.length - 1; i >= 0; i--) {
if (context.subscriptions[i].name === name && context.subscriptions[i].pattern.id === idOrObject) {
deleted++;
unsubscribePattern(context.subscriptions[i].pattern.id);
context.subscriptions.splice(i, 1);
sandbox.__engine.__subscriptions--;
}
}
return !!deleted;
}
},
on: function (pattern, callbackOrId, value) {
return sandbox.subscribe(pattern, callbackOrId, value);
},
schedule: function (pattern, callback) {
if (typeof callback !== 'function') {
adapter.log.error(name + ': schedule callback missing');
return;
}
sandbox.__engine.__schedules += 1;
if (pattern.astro) {
const nowdate = new Date();
if (adapter.config.latitude === undefined || adapter.config.longitude === undefined ||
adapter.config.latitude === '' || adapter.config.longitude === '' ||
adapter.config.latitude === null || adapter.config.longitude === null) {
adapter.log.error('Longitude or latitude does not set. Cannot use astro.');
return;
}
let ts = mods.suncalc.getTimes(nowdate, adapter.config.latitude, adapter.config.longitude)[pattern.astro];
if (ts.getTime().toString() === 'NaN') {
adapter.log.warn('Cannot calculate "' + pattern.astro + '" for ' + adapter.config.latitude + ', ' + adapter.config.longitude);
ts = new Date(nowdate.getTime());
if (pattern.astro === 'sunriseEnd' ||
pattern.astro === 'goldenHourEnd' ||
pattern.astro === 'sunset' ||
pattern.astro === 'nightEnd' ||
pattern.astro === 'nauticalDusk') {
ts.setMinutes(59);
ts.setHours(23);
ts.setSeconds(59);
} else {
ts.setMinutes(59);
ts.setHours(23);
ts.setSeconds(58);
}
}
if (ts && pattern.shift) {
ts = new Date(ts.getTime() + (pattern.shift * 60000));
}
if (!ts || ts < nowdate) {
const date = new Date(nowdate);
// Event doesn't occur today - try again tomorrow
// Calculate time till 24:00 and set timeout
date.setDate(date.getDate() + 1);
date.setMinutes(1); // Somtimes timer fires at 23:59:59
date.setHours(0);
date.setSeconds(0);
date.setMilliseconds(0);
date.setMinutes(-date.getTimezoneOffset());
// Calculate new schedule in the next day
sandbox.setTimeout(function () {
if (sandbox.__engine.__schedules > 0) sandbox.__engine.__schedules--;
sandbox.schedule(pattern, callback);
}, date.getTime() - nowdate.getTime());
return;
}
sandbox.setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
// Reschedule in 2 seconds
sandbox.setTimeout(function () {
if (sandbox.__engine.__schedules > 0) sandbox.__engine.__schedules--;
sandbox.schedule(pattern, callback);
}, 2000);
}, ts.getTime() - nowdate.getTime());
if (sandbox.verbose) sandbox.log('schedule(astro=' + pattern.astro + ', offset=' + pattern.shift + ')', 'info');
} else {
// fix problem with sunday and 7
if (typeof pattern === 'string') {
const parts = pattern.replace(/\s+/g, ' ').split(' ');
if (parts.length >= 5 && parts[5] >= 7) parts[5] = 0;
pattern = parts.join(' ');
}
const schedule = nodeSchedule.scheduleJob(pattern, function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
});
script.schedules.push(schedule);
if (sandbox.verbose) sandbox.log('schedule(cron=' + pattern + ')', 'info');
return schedule;
}
},
getAstroDate: function (pattern, date, offsetMinutes) {
if (date === undefined) date = new Date();
if (consts.astroList.indexOf(pattern) === -1) {
const pos = consts.astroListLow.indexOf(pattern.toLowerCase());
if (pos !== -1) pattern = consts.astroList[pos];
}
if ((!adapter.config.latitude && adapter.config.latitude !== 0) ||
(!adapter.config.longitude && adapter.config.longitude !== 0)) {
adapter.log.error('Longitude or latitude does not set. Cannot use astro.');
return;
}
let ts = mods.suncalc.getTimes(date, adapter.config.latitude, adapter.config.longitude)[pattern];
if (ts === undefined || ts.getTime().toString() === 'NaN') {
adapter.log.error('Cannot get astro date for "' + pattern + '"');
}
if (sandbox.verbose) sandbox.log('getAstroDate(pattern=' + pattern + ', date=' + date + ') => ' + ts, 'info');
if (offsetMinutes !== undefined) {
ts = new Date(ts.getTime() + (offsetMinutes * 60000));
}
return ts;
},
isAstroDay: function () {
const nowDate = new Date();
const dayBegin = sandbox.getAstroDate('sunrise');
const dayEnd = sandbox.getAstroDate('sunset');
if (dayBegin === undefined || dayEnd === undefined) return;
if (sandbox.verbose) sandbox.log('isAstroDay() => ' + (nowDate >= dayBegin && nowDate <= dayEnd), 'info');
return (nowDate >= dayBegin && nowDate <= dayEnd);
},
clearSchedule: function (schedule) {
for (let i = 0; i < script.schedules.length; i++) {
if (script.schedules[i] === schedule) {
if (!nodeSchedule.cancelJob(script.schedules[i])) {
adapter.log.error('Error by canceling scheduled job');
}
delete script.schedules[i];
script.schedules.splice(i, 1);
if (sandbox.verbose) sandbox.log('clearSchedule() => cleared', 'info');
return true;
}
}
if (sandbox.verbose) sandbox.log('clearSchedule() => invalid handler', 'warn');
return false;
},
setState: function (id, state, isAck, callback) {
if (typeof isAck === 'function') {
callback = isAck;
isAck = undefined;
}
if (state === null) {
state = {val: null};
}
if (isAck === true || isAck === false || isAck === 'true' || isAck === 'false') {
if (typeof state === 'object') {
state.ack = isAck;
} else {
state = {val: state, ack: isAck};
}
}
// Check type of state
if (!objects[id] && objects[adapter.namespace + '.' + id]) {
id = adapter.namespace + '.' + id;
}
const common = objects[id] ? objects[id].common : null;
if (common &&
common.type &&
common.type !== 'mixed' &&
common.type !== 'file' &&
common.type !== 'json') {
if (state && typeof state === 'object' && state.val !== undefined) {
if (common.type !== typeof state.val) {
context.logWithLineInfo.warn('Wrong type of ' + id + ': "' + typeof state.val + '". Please fix, while deprecated and will not work in next versions.');
//return;
}
} else {
if (common.type !== typeof state) {
context.logWithLineInfo.warn('Wrong type of ' + id + ': "' + typeof state + '". Please fix, while deprecated and will not work in next versions.');
//return;
}
}
}
// Check min and max of value
if (typeof state === 'object' && state) {
if (common && typeof state.val === 'number') {
if (common.min !== undefined && state.val < common.min) state.val = common.min;
if (common.max !== undefined && state.val > common.max) state.val = common.max;
}
} else if (common && typeof state === 'number') {
if (common.min !== undefined && state < common.min) state = common.min;
if (common.max !== undefined && state > common.max) state = common.max;
}
if (objects[id]) {
if (sandbox.verbose) sandbox.log('setForeignState(id=' + id + ', state=' + JSON.stringify(state) + ')', 'info');
if (debug) {
sandbox.log('setForeignState(id=' + id + ', state=' + JSON.stringify(state) + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
if (typeof callback === 'function') {
setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, 0);
}
} else {
adapter.setForeignState(id, state, function (err) {
if (err) sandbox.log('setForeignState: ' + err, 'error');
if (typeof callback === 'function') {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
});
}
} else if (objects[adapter.namespace + '.' + id]) {
if (sandbox.verbose) sandbox.log('setState(id=' + id + ', state=' + JSON.stringify(state) + ')', 'info');
if (debug) {
sandbox.log('setState(' + id + ', ' + JSON.stringify(state) + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
if (typeof callback === 'function') {
setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, 0);
}
} else {
adapter.setState(id, state, function (err) {
if (err) sandbox.log('setState: ' + err, 'error');
if (typeof callback === 'function') {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
});
}
} else {
if (objects[id]) {
if (objects[id].type === 'state') {
if (sandbox.verbose) sandbox.log('setForeignState(id=' + id + ', state=' + JSON.stringify(state) + ')', 'info');
if (debug) {
sandbox.log('setForeignState(id=' + id + ', state=' + JSON.stringify(state) + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
if (typeof callback === 'function') {
setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, 0);
}
} else {
adapter.setForeignState(id, state, function (err) {
if (err) sandbox.log('setForeignState: ' + err, 'error');
if (typeof callback === 'function') {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
});
}
} else {
adapter.log.warn('Cannot set value of non-state object "' + id + '"');
if (typeof callback === 'function') {
try {
callback.call(sandbox, 'Cannot set value of non-state object "' + id + '"');
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
}
} else if (objects[adapter.namespace + '.' + id]) {
if (objects[adapter.namespace + '.' + id].type === 'state') {
if (sandbox.verbose) sandbox.log('setState(id=' + id + ', state=' + JSON.stringify(state) + ')', 'info');
if (debug) {
sandbox.log('setState(id=' + id + ', state=' + JSON.stringify(state) + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
if (typeof callback === 'function') {
setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, 0);
}
} else {
adapter.setState(id, state, function (err) {
if (err) sandbox.log('setState: ' + err, 'error');
if (typeof callback === 'function') {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
});
}
} else {
adapter.log.warn('Cannot set value of non-state object "' + adapter.namespace + '.' + id + '"');
if (typeof callback === 'function') {
try {
callback.call(sandbox, 'Cannot set value of non-state object "' + adapter.namespace + '.' + id + '"');
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
}
} else {
context.logWithLineInfo.warn('State "' + id + '" not found');
if (typeof callback === 'function') {
try {
callback.call(sandbox, 'State "' + id + '" not found');
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
}
}
},
setStateDelayed: function (id, state, isAck, delay, clearRunning, callback) {
// find arguments
if (typeof isAck !== 'boolean') {
callback = clearRunning;
clearRunning = delay;
delay = isAck;
isAck = false;
}
if (typeof delay !== 'number') {
callback = clearRunning;
clearRunning = delay;
delay = 0;
}
if (typeof clearRunning !== 'boolean') {
callback = clearRunning;
clearRunning = true;
}
// Check type of state
if (!objects[id] && objects[adapter.namespace + '.' + id]) {
id = adapter.namespace + '.' + id;
}
if (clearRunning === undefined) clearRunning = true;
if (sandbox.verbose) sandbox.log('setStateDelayed(id=' + id + ', state=' + state + ', isAck=' + isAck + ', delay=' + delay + ', clearRunning=' + clearRunning + ')', 'info');
if (clearRunning) {
if (timers[id]) {
if (sandbox.verbose) sandbox.log('setStateDelayed: clear ' + timers[id].length + ' running timers', 'info');
for (let i = 0; i < timers[id].length; i++) {
clearTimeout(timers[id][i].t);
}
delete timers[id];
} else {
if (sandbox.verbose) sandbox.log('setStateDelayed: no running timers', 'info');
}
}
// If no delay => start immediately
if (!delay) {
sandbox.setState(id, state, isAck, callback);
return null;
} else {
// If delay
timers[id] = timers[id] || [];
// calculate timerId
context.timerId++;
if (context.timerId > 0xFFFFFFFE) context.timerId = 0;
// Start timeout
const timer = setTimeout(function (_timerId, _id, _state, _isAck) {
sandbox.setState(_id, _state, _isAck, callback);
// delete timer handler
if (timers[_id]) {
// optimisation
if (timers[_id].length === 1) {
delete timers[_id];
} else {
for (let t = 0; t < timers[_id].length; t++) {
if (timers[_id][t].id === _timerId) {
timers[_id].splice(t, 1);
break;
}
}
if (!timers[_id].length) delete timers[_id];
}
}
}, delay, context.timerId, id, state, isAck);
// add timer handler
timers[id].push({
t: timer,
id: context.timerId,
ts: Date.now(),
delay: delay,
val: typeof state === 'object' && state.val !== undefined ? state.val : state,
ack: typeof state === 'object' && state.val !== undefined && state.ack !== undefined ? state.ack : isAck
});
return context.timerId;
}
},
clearStateDelayed: function (id, timerId) {
// Check type of state
if (!objects[id] && objects[adapter.namespace + '.' + id]) {
id = adapter.namespace + '.' + id;
}
if (sandbox.verbose) sandbox.log('clearStateDelayed(id=' + id + ', timerId=' + timerId + ')', 'info');
if (timers[id]) {
for (let i = timers[id].length - 1; i >= 0; i--) {
if (timerId === undefined || timers[id][i].id === timerId) {
clearTimeout(timers[id][i].t);
if (timerId !== undefined) timers[id].splice(i, 1);
if (sandbox.verbose) sandbox.log('clearStateDelayed: clear timer ' + timers[id][i].id, 'info');
}
}
if (timerId === undefined) {
delete timers[id];
} else {
if (!timers[id].length) delete timers[id];
}
return true;
}
return false;
},
getStateDelayed: function (id) {
let result;
const now = Date.now();
if (id) {
// Check type of state
if (!objects[id] && objects[adapter.namespace + '.' + id]) {
id = adapter.namespace + '.' + id;
}
// If timerId given
if (typeof id === 'number') {
for (const _id_ in timers) {
if (timers.hasOwnProperty(_id_)) {
for (let ttt = 0; ttt < timers[_id_].length; ttt++) {
if (timers[_id_][ttt].id === id) {
return {
id: _id_,
left: timers[_id_][ttt].delay - (now - timers[id][ttt].ts),
delay: timers[_id_][ttt].delay,
val: timers[_id_][ttt].val,
ack: timers[_id_][ttt].ack
};
}
}
}
}
return null;
}
result = [];
if (timers.hasOwnProperty(id) && timers[id] && timers[id].length) {
for (let tt = 0; tt < timers[id].length; tt++) {
result.push({
timerId: timers[id][tt].id,
left: timers[id][tt].delay - (now - timers[id][tt].ts),
delay: timers[id][tt].delay,
val: timers[id][tt].val,
ack: timers[id][tt].ack
});
}
}
return result;
} else {
result = {};
for (const _id in timers) {
if (timers.hasOwnProperty(_id) && timers[_id] && timers[_id].length) {
result[_id] = [];
for (let t = 0; t < timers[_id].length; t++) {
result[_id].push({
timerId: timers[_id][t].id,
left: timers[_id][t].delay - (now - timers[_id][t].ts),
delay: timers[_id][t].delay,
val: timers[_id][t].val,
ack: timers[_id][t].ack
});
}
}
}
}
return result;
},
getState: function (id, callback) {
if (typeof callback === 'function') {
if (id.indexOf('.') === -1) {
adapter.getState(id, callback);
} else {
adapter.getForeignState(id, callback);
}
} else {
if (adapter.config.subscribe) {
sandbox.log('Cannot use sync getState, use callback instead getState("' + id + '", function (err, state){}); or disable the "Do not subscribe all states on start" option in instance configuration.', 'error');
} else {
if (states[id]) {
if (sandbox.verbose) sandbox.log('getState(id=' + id + ', timerId=' + timers[id] + ') => ' + JSON.stringify(states[id]), 'info');
return states[id];
}
if (states[adapter.namespace + '.' + id]) {
if (sandbox.verbose) sandbox.log('getState(id=' + id + ', timerId=' + timers[id] + ') => ' + states[adapter.namespace + '.' + id], 'info');
return states[adapter.namespace + '.' + id];
}
if (sandbox.verbose) sandbox.log('getState(id=' + id + ', timerId=' + timers[id] + ') => not found', 'info');
context.logWithLineInfo.warn('getState "' + id + '" not found (3)' + (states[id] !== undefined ? ' states[id]=' + states[id] : '')); ///xxx
return {val: null, notExist: true};
}
}
},
existsState: function (id) {
return states.get(id) !== undefined;
},
existsObject: function (id) {
return objects.get(id) !== undefined;
},
getIdByName: function (name, alwaysArray) {
if (sandbox.verbose) sandbox.log('getIdByName(name=' + name + ', alwaysArray=' + alwaysArray + ') => ' + context.names[name], 'info');
if (alwaysArray) {
if (typeof context.names[name] === 'string') {
return [context.names[name]];
}
return context.names[name];
} else {
return context.names[name];
}
},
getObject: function (id, enumName, cb) {
if (typeof enumName === 'function') {
cb = enumName;
enumName = null;
}
if (typeof cb === 'function') {
adapter.getForeignObject(id, (err, obj) => {
if (obj) {
objects[id] = obj;
} else if (objects[id]) {
delete objects[id];
}
let result;
try {
result = JSON.parse(JSON.stringify(objects[id]));
} catch (err) {
adapter.log.error('Object "' + id + '" can\'t be copied');
return null;
}
if (sandbox.verbose) sandbox.log('getObject(id=' + id + ', enumName=' + enumName + ') => ' + JSON.stringify(result), 'info');
cb(err, result);
});
} else {
if (!objects[id]) {
if (sandbox.verbose) sandbox.log('getObject(id=' + id + ', enumName=' + enumName + ') => does not exist', 'info');
adapter.log.warn('Object "' + id + '" does not exist');
return null;
} else if (enumName) {
const e = eventObj.getObjectEnumsSync(context, id);
const obj = JSON.parse(JSON.stringify(objects[id]));
obj.enumIds = JSON.parse(JSON.stringify(e.enumIds));
obj.enumNames = JSON.parse(JSON.stringify(e.enumNames));
if (typeof enumName === 'string') {
const r = new RegExp('^enum\\.' + enumName + '\\.');
for (let i = obj.enumIds.length - 1; i >= 0; i--) {
if (!r.test(obj.enumIds[i])) {
obj.enumIds.splice(i, 1);
obj.enumNames.splice(i, 1);
}
}
}
if (sandbox.verbose) sandbox.log('getObject(id=' + id + ', enumName=' + enumName + ') => ' + JSON.stringify(obj), 'info');
return obj;
} else {
let result;
try {
result = JSON.parse(JSON.stringify(objects[id]));
} catch (err) {
adapter.log.error('Object "' + id + '" can\'t be copied');
return null;
}
if (sandbox.verbose) sandbox.log('getObject(id=' + id + ', enumName=' + enumName + ') => ' + JSON.stringify(result), 'info');
return result;
}
}
},
setObject: function (id, obj, callback) {
adapter.log.error('Function "setObject" is not allowed. Use adapter settings to allow it.');
if (typeof callback === 'function') {
try {
callback.call(sandbox, 'Function "setObject" is not allowed. Use adapter settings to allow it.');
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
},
extendObject: function (id, obj, callback) {
adapter.log.error('Function "extendObject" is not allowed. Use adapter settings to allow it.');
if (typeof callback === 'function') {
try {
callback.call(sandbox, 'Function "extendObject" is not allowed. Use adapter settings to allow it.');
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
},
getEnums: function (enumName) {
const result = [];
const r = enumName ? new RegExp('^enum\\.' + enumName + '\\.') : false;
for (let i = 0; i < enums.length; i++) {
if (!r || r.test(enums[i])) {
result.push({
id: enums[i],
members: (objects[enums[i]].common) ? objects[enums[i]].common.members : [],
name: objects[enums[i]].common.name
});
}
}
if (sandbox.verbose) sandbox.log('getEnums(enumName=' + enumName + ') => ' + JSON.stringify(result), 'info');
return JSON.parse(JSON.stringify(result));
},
createState: function (name, initValue, forceCreation, common, native, callback) {
if (typeof native === 'function') {
callback = native;
native = {};
}
if (typeof common === 'function') {
callback = common;
common = undefined;
}
if (typeof initValue === 'function') {
callback = initValue;
initValue = undefined;
}
if (typeof forceCreation === 'function') {
callback = forceCreation;
forceCreation = undefined;
}
if (typeof initValue === 'object') {
common = initValue;
native = forceCreation;
forceCreation = undefined;
initValue = undefined;
}
if (typeof forceCreation === 'object') {
common = forceCreation;
native = common;
forceCreation = undefined;
}
common = common || {};
common.name = common.name || name;
common.role = common.role || 'javascript';
common.type = common.type || 'mixed';
if (initValue === undefined) initValue = common.def;
native = native || {};
// Check min, max and def values for number
if (common.type !== undefined && common.type === 'number') {
let min = 0;
let max = 0;
let def = 0;
let err;
if (common.min !== undefined) {
min = common.min;
if (typeof min !== 'number') {
min = parseFloat(min);
if (isNaN(min)) {
err = 'Wrong type of ' + name + '.common.min';
sandbox.log(err, 'error');
if (typeof callback === 'function') {
try {
callback.call(sandbox, err);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
return;
} else {
common.min = min;
}
}
}
if (common.max !== undefined) {
max = common.max;
if (typeof max !== 'number') {
max = parseFloat(max);
if (isNaN(max)) {
err = 'Wrong type of ' + name + '.common.max';
sandbox.log(err, 'error');
if (typeof callback === 'function') {
try {
callback.call(sandbox, err);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
return;
} else {
common.max = max;
}
}
}
if (common.def !== undefined) {
def = common.def;
if (typeof def !== 'number') {
def = parseFloat(def);
if (isNaN(def)) {
err = 'Wrong type of ' + name + '.common.def';
sandbox.log(err, 'error');
if (typeof callback === 'function') {
try {
callback.call(sandbox, err);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
return;
} else {
common.def = def;
}
}
}
if (common.min !== undefined && common.max !== undefined && min > max) {
common.max = min;
common.min = max;
}
if (common.def !== undefined && common.min !== undefined && def < min) common.def = min;
if (common.def !== undefined && common.max !== undefined && def > max) common.def = max;
}
if (sandbox.verbose) sandbox.log('createState(name=' + name + ', initValue=' + initValue + ', forceCreation=' + forceCreation + ', common=' + JSON.stringify(common) + ', native=' + JSON.stringify(native) + ')', 'debug');
if (forceCreation) {
// todo: store object in objects to have this object directly after callback
adapter.setObject(name, {
common: common,
native: native,
type: 'state'
}, function (err) {
if (err) adapter.log.warn('Cannot set object "' + name + '": ' + err);
if (initValue !== undefined) {
if (typeof initValue === 'object' && initValue.ack !== undefined) {
adapter.setState(name, initValue, callback);
} else {
adapter.setState(name, initValue, true, callback);
}
} else {
if (typeof callback === 'function') {
try {
callback.call(sandbox, name);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
}
});
} else {
adapter.getObject(name, function (err, obj) {
if (err || !obj) {
// todo: store object in objects to have this object directly after callback
// create new one
if (name.match(/^javascript\.\d+\./)) {
adapter.setForeignObject(name, {
common: common,
native: native,
type: 'state'
}, function (err) {
if (err) adapter.log.warn('Cannot set object "' + name + '": ' + err);
if (initValue !== undefined) {
adapter.setForeignState(name, initValue, callback);
if (typeof initValue === 'object' && initValue.ack !== undefined) {
adapter.setForeignState(name, initValue, callback);
} else {
adapter.setForeignState(name, initValue, true, callback);
}
} else {
adapter.setForeignState(name, null, true, callback);
}
});
} else {
adapter.setObject(name, {
common: common,
native: native,
type: 'state'
}, function (err) {
if (err) adapter.log.warn('Cannot set object "' + name + '": ' + err);
if (initValue !== undefined) {
if (typeof initValue === 'object' && initValue.ack !== undefined) {
adapter.setState(name, initValue, callback);
} else {
adapter.setState(name, initValue, true, callback);
}
} else {
adapter.setState(name, null, true, callback);
}
});
}
} else {
if (!adapter.config.subscribe && !states[name] && !states[adapter.namespace + '.' + name]) {
if (name.substring(0, adapter.namespace.length) !== adapter.namespace) {
states[adapter.namespace + '.' + name] = {val: null, ack: true};
} else {
states[name] = {val: null, ack: true};
}
}
// state yet exists
if (typeof callback === 'function') {
try {
callback.call(sandbox, name);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
}
});
}
},
deleteState: function (id, callback) {
// todo: check rights
let found = false;
if (objects[id]) {
found = true;
delete objects[id];
}
if (states[id]) delete states[id];
if (objects[adapter.namespace + '.' + id]) {
delete objects[adapter.namespace + '.' + id];
found = true;
}
if (states[adapter.namespace + '.' + id]) delete states[adapter.namespace + '.' + id];
if (sandbox.verbose) sandbox.log('deleteState(id=' + id + ')', 'debug');
adapter.delObject(id, function (err) {
if (err) adapter.log.warn('Object for state "' + id + '" does not exist: ' + err);
adapter.delState(id, function (err) {
if (err) adapter.log.error('Cannot delete state "' + id + '": ' + err);
if (typeof callback === 'function') {
if (typeof callback === 'function') {
try {
callback.call(sandbox, err, found);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
}
});
});
},
sendTo: function (_adapter, cmd, msg, callback) {
if (sandbox.verbose) sandbox.log('sendTo(adapter=' + _adapter + ', cmd=' + cmd + ', msg=' + JSON.stringify(msg) + ')', 'info');
adapter.sendTo(_adapter, cmd, msg, callback);
},
sendto: function (_adapter, cmd, msg, callback) {
return sandbox.sendTo(_adapter, cmd, msg, callback);
},
sendToHost: function (host, cmd, msg, callback) {
if (!adapter.config.enableSendToHost) {
const error = 'sendToHost is not available. Please enable "Enable SendToHost" option in instance settings';
adapter.log.error(error);
sandbox.log(error);
if (typeof callback === 'function') {
setImmediate(function () {
callback(error);
});
}
} else {
if (sandbox.verbose) sandbox.log('sendToHost(adapter=' + host + ', cmd=' + cmd + ', msg=' + JSON.stringify(msg) + ')', 'info');
adapter.sendToHost(host, cmd, msg, callback);
}
},
setInterval: function (callback, ms, arg1, arg2, arg3, arg4) {
if (typeof callback === 'function') {
const int = setInterval(function (_arg1, _arg2, _arg3, _arg4) {
try {
callback.call(sandbox, _arg1, _arg2, _arg3, _arg4);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, ms, arg1, arg2, arg3, arg4);
script.intervals.push(int);
if (sandbox.verbose) sandbox.log('setInterval(ms=' + ms + ')', 'info');
return int;
} else {
sandbox.log('Invalid callback for setInterval! - ' + typeof callback, 'error');
return null;
}
},
clearInterval: function (id) {
const pos = script.intervals.indexOf(id);
if (pos !== -1) {
if (sandbox.verbose) sandbox.log('clearInterval() => cleared', 'info');
clearInterval(id);
script.intervals.splice(pos, 1);
} else {
if (sandbox.verbose) sandbox.log('clearInterval() => not found', 'warn');
}
},
setTimeout: function (callback, ms, arg1, arg2, arg3, arg4) {
if (typeof callback === 'function') {
const to = setTimeout(function (_arg1, _arg2, _arg3, _arg4) {
// Remove timeout from the list
const pos = script.timeouts.indexOf(to);
if (pos !== -1) script.timeouts.splice(pos, 1);
try {
callback.call(sandbox, _arg1, _arg2, _arg3, _arg4);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, ms, arg1, arg2, arg3, arg4);
if (sandbox.verbose) sandbox.log('setTimeout(ms=' + ms + ')', 'info');
script.timeouts.push(to);
return to;
} else {
sandbox.log('Invalid callback for setTimeout! - ' + typeof callback, 'error');
return null;
}
},
clearTimeout: function (id) {
const pos = script.timeouts.indexOf(id);
if (pos !== -1) {
if (sandbox.verbose) sandbox.log('clearTimeout() => cleared', 'info');
clearTimeout(id);
script.timeouts.splice(pos, 1);
} else {
if (sandbox.verbose) sandbox.log('clearTimeout() => not found', 'warn');
}
},
setImmediate: function (callback, arg1, arg2, arg3, arg4, arg5) {
if (typeof callback === 'function') {
setImmediate(function (_arg1, _arg2, _arg3, _arg4, _arg5) {
try {
callback.call(sandbox, _arg1, _arg2, _arg3, _arg4, _arg5);
} catch (e) {
errorInCallback(e);
}
}, arg1, arg2, arg3, arg4, arg5);
if (sandbox.verbose) sandbox.log('setImmediate()', 'info');
} else {
sandbox.log('Invalid callback for setImmediate! - ' + typeof callback, 'error');
}
},
cb: function (callback) {
return function () {
if (context.scripts[name] && context.scripts[name]._id === sandbox._id) {
if (typeof callback === 'function') {
try {
callback.apply(this, arguments);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}
} else {
adapter.log.warn('Callback for old version of script: ' + name);
}
};
},
compareTime: function (startTime, endTime, operation, time) {
let pos;
if (startTime && typeof startTime === 'string') {
if ((pos = consts.astroListLow.indexOf(startTime.toLowerCase())) !== -1) {
startTime = sandbox.getAstroDate(consts.astroList[pos]);
startTime = startTime.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
}
} else if (startTime && typeof startTime === 'object' && startTime.astro) {
startTime = sandbox.getAstroDate(startTime.astro, startTime.date || new Date(), startTime.offset || 0);
startTime = startTime.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
}
if (endTime && typeof endTime === 'string') {
if ((pos = consts.astroListLow.indexOf(endTime.toLowerCase())) !== -1) {
endTime = sandbox.getAstroDate(consts.astroList[pos]);
endTime = endTime.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
}
} else if (endTime && typeof endTime === 'object' && endTime.astro) {
endTime = sandbox.getAstroDate(endTime.astro, endTime.date || new Date(), endTime.offset || 0);
endTime = endTime.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
}
if (time && typeof time === 'string') {
if ((pos = consts.astroListLow.indexOf(time.toLowerCase())) !== -1) {
time = sandbox.getAstroDate(consts.astroList[pos]);
}
} else if (time && typeof time === 'object' && time.astro) {
time = sandbox.getAstroDate(time.astro, time.date || new Date(), time.offset || 0);
}
let daily = true;
if (time) {
daily = false;
}
if (time && typeof time !== 'object') {
if (typeof time === 'string' && time.indexOf(' ') === -1 && time.indexOf('T') === -1) {
const parts = time.split(':');
time = new Date();
time.setHours(parseInt(parts[0], 10));
time.setMinutes(parseInt(parts[1], 10));
time.setMilliseconds(0);
if (parts.length === 3) {
time.setSeconds(parseInt(parts[2], 10));
} else {
time.setSeconds(0);
}
} else {
time = new Date(time);
}
} else if (!time) {
time = new Date();
time.setMilliseconds(0);
}
if (typeof startTime === 'string') {
if (startTime.indexOf(' ') === -1 && startTime.indexOf('T') === -1) {
const parts = startTime.split(':');
startTime = new Date();
startTime.setHours(parseInt(parts[0], 10));
startTime.setMinutes(parseInt(parts[1], 10));
startTime.setMilliseconds(0);
if (parts.length === 3) {
startTime.setSeconds(parseInt(parts[2], 10));
} else {
startTime.setSeconds(0);
}
} else {
daily = false;
startTime = new Date(startTime);
}
} else {
daily = false;
startTime = new Date(startTime);
}
startTime = startTime.getTime();
if (endTime && typeof endTime === 'string') {
if (endTime.indexOf(' ') === -1 && endTime.indexOf('T') === -1) {
const parts = endTime.split(':');
endTime = new Date();
endTime.setHours(parseInt(parts[0], 10));
endTime.setMinutes(parseInt(parts[1], 10));
endTime.setMilliseconds(0);
if (parts.length === 3) {
endTime.setSeconds(parseInt(parts[2], 10));
} else {
endTime.setSeconds(0);
}
} else {
daily = false;
endTime = new Date(endTime);
}
} else if (endTime) {
daily = false;
endTime = new Date(endTime);
} else {
endTime = null;
}
if (endTime) endTime = endTime.getTime();
if (operation === 'between') {
if (endTime) {
if (startTime > endTime && daily) return !(time >= endTime && time < startTime);
else return time >= startTime && time < endTime;
} else {
adapter.log.warn('missing or unrecognized endTime expression: ' + endTime);
return false;
}
} else if (operation === 'not between') {
if (endTime) {
if (startTime > endTime && daily) return time >= endTime && time < startTime;
else return !(time >= startTime && time < endTime);
} else {
adapter.log.warn('missing or unrecognized endTime expression: ' + endTime);
return false;
}
} else if (operation === '>') {
return time > startTime;
} else if (operation === '>=') {
return time >= startTime;
} else if (operation === '<') {
return time < startTime;
} else if (operation === '<=') {
return time <= startTime;
} else if (operation === '==') {
return time === startTime;
} else if (operation === '<>') {
return time !== startTime;
} else {
adapter.log.warn('Invalid operator: ' + operation);
return false;
}
},
onStop: function (cb, timeout) {
if (sandbox.verbose) sandbox.log('onStop(timeout=' + timeout + ')', 'info');
script.onStopCb = cb;
script.onStopTimeout = timeout || 1000;
},
formatValue: function (value, decimals, format) {
if (!format && objects['system.config']) {
format = objects['system.config'].common.isFloatComma ? '.,' : ',.';
}
return adapter.formatValue(value, decimals, format);
},
formatDate: function (date, format, language) {
if (!format) {
format = objects['system.config'] ? (objects['system.config'].common.dateFormat || 'DD.MM.YYYY') : 'DD.MM.YYYY';
}
if (format.match(/[WНOО]+/)) {
let text = adapter.formatDate(date, format);
if (!language || !consts.dayOfWeeksFull[language]) language = objects['system.config'].common.language;
const d = date.getDay();
text = text.replace('WW', consts.dayOfWeeksFull[language][d]);
text = text.replace('НН', consts.dayOfWeeksFull[language][d]);
text = text.replace('W', consts.dayOfWeeksShort[language][d]);
text = text.replace('Н', consts.dayOfWeeksShort[language][d]);
text = text.replace('W', consts.dayOfWeeksShort[language][d]);
text = text.replace('Н', consts.dayOfWeeksShort[language][d]);
const m = date.getMonth();
text = text.replace('OOO', consts.monthFullGen[language][m]);
text = text.replace('ООО', consts.monthFullGen[language][m]);
text = text.replace('OO', consts.monthFull[language][m]);
text = text.replace('ОО', consts.monthFull[language][m]);
text = text.replace('O', consts.monthShort[language][m]);
text = text.replace('О', consts.monthShort[language][m]);
text = text.replace('O', consts.monthShort[language][m]);
text = text.replace('О', consts.monthShort[language][m]);
return text;
} else {
return adapter.formatDate(date, format);
}
},
getDateObject: function (date) {
if (typeof date === 'object') return date;
if (typeof date !== 'string') return new Date(date);
if (date.match(/^\d?\d$/)) {
const _now = new Date();
date = _now.getFullYear() + '-' + (_now.getMonth() + 1) + '-' + _now.getDate() + ' ' + date + ':00';
} else {
// 20:00, 2:00, 20:00:00, 2:00:00
if (date.match(/^\d?\d:\d\d(:\d\d)?$/)) {
const now = new Date();
date = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + ' ' + date;
}
}
return new Date(date);
},
writeFile: function (_adapter, fileName, data, callback) {
if (typeof data === 'function' || !data) {
callback = data;
data = fileName;
fileName = _adapter;
_adapter = null;
}
if (debug) {
sandbox.log('readFile(adapter=' + _adapter + ', fileName=' + fileName + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
if (typeof callback === 'function') {
setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, 0);
}
} else {
if (sandbox.verbose) sandbox.log('readFile(adapter=' + _adapter + ', fileName=' + fileName + ')', 'info');
adapter.writeFile(_adapter, fileName, data, callback);
}
},
readFile: function (_adapter, fileName, callback) {
if (typeof fileName === 'function') {
callback = fileName;
fileName = _adapter;
_adapter = null;
}
if (sandbox.verbose) sandbox.log('readFile(adapter=' + _adapter + ', fileName=' + fileName + ')', 'info');
adapter.readFile(_adapter, fileName, callback);
},
unlink: function (_adapter, fileName, callback) {
if (typeof fileName === 'function') {
callback = fileName;
fileName = _adapter;
_adapter = null;
}
if (sandbox.verbose) sandbox.log('unlink(adapter=' + _adapter + ', fileName=' + fileName + ')', 'info');
if (debug) {
sandbox.log('unlink(adapter=' + _adapter + ', fileName=' + fileName + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
if (typeof callback === 'function') {
setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, 0);
}
} else {
if (sandbox.verbose) sandbox.log('unlink(adapter=' + _adapter + ', fileName=' + fileName + ')', 'info');
adapter.unlink(_adapter, fileName, callback);
}
},
delFile: function (_adapter, fileName, callback) {
return sandbox.unlink(_adapter, fileName, callback);
},
getHistory: function (instance, options, callback) {
if (typeof instance === 'object') {
callback = options;
options = instance;
instance = null;
}
if (typeof callback !== 'function') {
adapter.log.error('No callback found!');
return;
}
if (typeof options !== 'object') {
adapter.log.error('No options found!');
return;
}
if (!options.id) {
adapter.log.error('No ID found!');
return;
}
const timeoutMs = parseInt(options.timeout, 10) || 20000;
if (!instance) {
instance = objects['system.config'] ? objects['system.config'].common.defaultHistory : null;
}
if (sandbox.verbose) sandbox.log('getHistory(instance=' + instance + ', options=' + JSON.stringify(options) + ')', 'debug');
if (!instance) {
adapter.log.error('No default history instance found!');
try {
callback.call(sandbox, 'No default history instance found!');
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
return;
}
if (instance.match(/^system\.adapter\./)) instance = instance.substring('system.adapter.'.length);
if (!objects['system.adapter.' + instance]) {
adapter.log.error('Instance "' + instance + '" not found!');
try {
callback.call(sandbox, 'Instance "' + instance + '" not found!');
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
return;
}
let timeout = setTimeout(function () {
timeout = null;
if (sandbox.verbose) sandbox.log('getHistory => timeout', 'debug');
if (typeof callback === 'function') {
try {
callback.call(sandbox, 'Timeout', null, options, instance);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
callback = null;
}
}, timeoutMs);
adapter.sendTo(instance, 'getHistory', {id: options.id, options: options}, function (result) {
if (timeout) clearTimeout(timeout);
if (sandbox.verbose && result.error) sandbox.log('getHistory => ' + result.error, 'error');
if (sandbox.verbose && result.result) sandbox.log('getHistory => ' + result.result.length + ' items', 'debug');
if (typeof callback === 'function') {
try {
callback.call(sandbox, result.error, result.result, options, instance);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
callback = null;
}
});
},
runScript: function (scriptName, callback) {
scriptName = scriptName || name;
if (!scriptName.match(/^script\.js\./)) scriptName = 'script.js.' + scriptName;
// start other script
if (!objects[scriptName] || !objects[scriptName].common) {
sandbox.log('Cannot start "' + scriptName + '", because not found', 'error');
return false;
} else {
if (debug) {
sandbox.log('runScript(scriptName=' + scriptName + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
} else {
if (objects[scriptName].common.enabled) {
objects[scriptName].common.enabled = false;
adapter.extendForeignObject(scriptName, {common: {enabled: false}}, function (/* err, obj */) {
adapter.extendForeignObject(scriptName, {common: {enabled: true}}, function (err) {
if (callback === 'function') callback(err);
});
scriptName = null;
});
} else {
adapter.extendForeignObject(scriptName, {common: {enabled: true}}, function (err) {
if (callback === 'function') callback(err);
});
}
}
return true;
}
},
startScript: function (scriptName, ignoreIfStarted, callback) {
if (typeof ignoreIfStarted === 'function') {
callback = ignoreIfStarted;
ignoreIfStarted = false;
}
scriptName = scriptName || name;
if (!scriptName.match(/^script\.js\./)) scriptName = 'script.js.' + scriptName;
// start other script
if (!objects[scriptName] || !objects[scriptName].common) {
sandbox.log('Cannot start "' + scriptName + '", because not found', 'error');
return false;
} else {
console.log('STARTING!');
if (debug) {
sandbox.log('startScript(scriptName=' + scriptName + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
} else {
if (objects[scriptName].common.enabled) {
if (!ignoreIfStarted) {
objects[scriptName].common.enabled = false;
adapter.extendForeignObject(scriptName, {common: {enabled: false}}, function (err) {
adapter.extendForeignObject(scriptName, {common: {enabled: true}}, function (err) {
if (callback === 'function') callback(err, true);
});
scriptName = null;
});
} else if (callback === 'function') {
callback(null, false);
}
} else {
adapter.extendForeignObject(scriptName, {common: {enabled: true}}, function (err) {
if (callback === 'function') callback(err, true);
});
}
}
return true;
}
},
stopScript: function (scriptName, callback) {
scriptName = scriptName || name;
if (!scriptName.match(/^script\.js\./)) scriptName = 'script.js.' + scriptName;
// stop other script
if (!objects[scriptName] || !objects[scriptName].common) {
sandbox.log('Cannot stop "' + scriptName + '", because not found', 'error');
return false;
} else {
if (debug) {
sandbox.log('stopScript(scriptName=' + scriptName + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
} else {
if (objects[scriptName].common.enabled) {
objects[scriptName].common.enabled = false;
adapter.extendForeignObject(scriptName, {common: {enabled: false}}, function (err) {
if (callback === 'function') callback(err, true);
scriptName = null;
});
} else if (callback === 'function') {
callback(null, false);
}
}
return true;
}
},
isScriptActive: function (scriptName) {
if (!scriptName.match(/^script\.js\./)) scriptName = 'script.js.' + scriptName;
if (!objects[scriptName] || !objects[scriptName].common) {
sandbox.log('Script does not exist', 'error');
return false;
} else {
return objects[scriptName].common.enabled;
}
},
toInt: function (val) {
if (val === true || val === 'true') val = 1;
if (val === false || val === 'false') val = 0;
val = parseInt(val) || 0;
return val;
},
toFloat: function (val) {
if (val === true || val === 'true') val = 1;
if (val === false || val === 'false') val = 0;
val = parseFloat(val) || 0;
return val;
},
toBoolean: function (val) {
if (val === '1' || val === 'true') val = true;
if (val === '0' || val === 'false') val = false;
return !!val;
},
getAttr: function (obj, path) {
if (typeof path === 'string') {
path = path.split('.');
}
if (typeof obj === 'string') {
try {
obj = JSON.parse(obj);
} catch (e) {
sandbox.log('Cannot parse "' + obj.substring(0, 30) + '"' + e, 'error');
return null;
}
}
const attr = path.shift();
try {
obj = obj[attr];
} catch (e) {
sandbox.log(`Cannot get ${attr} of ${JSON.stringify(obj)}`, 'error');
return null;
}
if (!path.length) {
return obj;
} else {
const type = typeof obj;
if (obj === null || obj === undefined || type === 'boolean' || type === 'number') {
return null;
} else {
return sandbox.getAttr(obj, path);
}
}
},
console: {
log: function (msg) {
sandbox.log(msg, 'info');
},
error: function (msg) {
sandbox.log(msg, 'error');
},
warn: function (msg) {
sandbox.log(msg, 'warn');
},
debug: function (msg) {
sandbox.log(msg, 'debug');
}
}
};
if (adapter.config.enableSetObject) {
sandbox.setObject = function (id, obj, callback) {
if (debug) {
sandbox.log('setObject(id=' + id + ', obj=' + JSON.stringify(obj) + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
if (typeof callback === 'function') {
setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, 0);
}
} else {
if (sandbox.verbose) sandbox.log('setObject(id=' + id + ', obj=' + JSON.stringify(obj) + ')', 'info');
adapter.setForeignObject(id, obj, callback);
}
};
sandbox.extendObject = function (id, obj, callback) {
if (debug) {
sandbox.log('extendObject(id=' + id + ', obj=' + JSON.stringify(obj) + ') - ' + words._('was not executed, while debug mode is active'), 'warn');
if (typeof callback === 'function') {
setTimeout(function () {
try {
callback.call(sandbox);
} catch (e) {
errorInCallback(e); //adapter.log.error('Error in callback: ' + e)
}
}, 0);
}
} else {
if (sandbox.verbose) sandbox.log('extendObject(id=' + id + ', obj=' + JSON.stringify(obj) + ')', 'info');
adapter.extendForeignObject(id, obj, callback);
}
};
}
// Make all predefined properties and methods readonly so scripts cannot overwrite them
for (const prop in sandbox) {
if (sandbox.hasOwnProperty(prop)) {
Object.defineProperty(sandbox, prop, {
configurable: false,
writable: false
});
}
}
return sandbox;
}
module.exports = sandBox;