NEWS
Meine StateMachine
-
Hallo mitsammen!
Ich habe/hatte früher etliche node-red und javascript-sessions laufen um meine Automation mit ca ~80 Aktuatoren und Sensoren sowie 10 Adaptern zu steuern.
Da ich aus historischen Gründen verschiedene Systeme verwende, Homematic, FS20 und etliche andere Funksensoren und Schalter einsetzte kann ich die Logik nur zentral steuern und dafür ist ja ioBroker ideal geeignet. Es kann bei mir vorkommen dass ein FS20-Schalter, ein HomeMatic Bewegungsmeler mit Helligkeitssensor eine Funksteckdose eines 3. Anbieters steuern soll.
Das haben bisher die node-red und javascripte erledigt, Blocky fiel aus da es noch langwieriger war. In node-Red hatte ich mir so eine Art State-Maschine gebaut die Events auf Eingangsseite auf Aktionen auf der Ausgangsseite verknüpfen konnte. Dies war aber auch nicht einfach zu pflegen und es klamen schon zu viele Seiten zusammen.
Nun wollte ich meine scripte zusammenführen und node-red von der Adapterliste streichen und habe mir deshalb eine 'StateMachine' für den Javascript-Adapter gebaut.
Der code besteht nun aus einem Javascript-script das das einzige 'Common'-Element auf meiner ioBroker-Installation darstellt. Es enthält alle Funktionen die notwendig sind eine Statemachine (und andere Dinge) zu implementieren.
! ```
// globale StateMachine FJ v1.3 2017/03/26 const util = require("util"), http = require("http"), https = require('https'), suncalc = require('suncalc'), jsadapter = getObject("system.adapter.javascript.0"); // https.get var debuglog = 'debug'; // default level von _D(), kann im script dann z.B. auf 'info' geschalten werden um _D() auch im Info-Level anzuzeigen function _T(i) {var t=typeof i; if(t==='object'){ if(Array.isArray(i)) t = 'array'; else if(i instanceof RegExp) t = 'regexp'; else if(i===null) t = 'null'; } else if(t==='number' && isNaN(i)) t='NaN'; return t;} function _O(obj,level) { return util.inspect(obj, false, level ? level-1 : 2, false).replace(/\n/g,' ');} // print object as string like node would do on console but remove new lines function _J(str) { try { return JSON.parse(str); } catch (e) { return {'error':'JSON Parse Error of:'+str}}} // convert a JSON string securely to an object function _D(str,val) { log(
debug: ${str},debuglog); return val !== undefined ? val : str; } // Write debug message in log, optionally return 2nd argument function _W(str,val) { log(
warn: ${str},'warn'); return val !== undefined ? val : str; } // Write warning message in log, optionally return 2nd argument function _I(str,ret) { log(str); return ret !== undefined ? ret : str; } // Write normal information to log, optionally return 2nd argument function _N(fun) { return setTimeout.apply(null,[fun,0].concat(Array.prototype.slice.call(arguments, 1))); } // execute fun after pending IO in next schedule keeping arguments function _E(test, str) { if (typeof test !== 'string' && !test) return; throw new Error(typeof test !== 'string' ? str : test); } function _PR(v) { return Promise.resolve(v); } function _PE(v) { return Promise.reject(v); } function idn(id) { // return object name name if it exist, otherwise return id. const obj = getObject(id); return ((obj && obj.common && obj.common.name) ? obj.common.name : id) + " "; } function pSeries(obj,promfn,delay) { // makes an call for each item 'of' obj to promfun(item) which need to return a promise. All Promises are executed therefore in sequence one after the other var p = Promise.resolve(); const nv = [], f = (k) => p = p.then(() => promfn(k).then(res => wait(delay || 0,nv.push(res)))); for(var item of obj) f(item); return p.then(() => nv); } function pSeriesIn(obj,promfn,delay) { // makes an call for each item 'in' obj to promfun(key,obj) which need to return a promise. All Promises are executed therefore in sequence one after the other var p = Promise.resolve(); const nv = [], f = k => p = p.then(() => promfn(k,obj).then(res => wait(delay || 0, nv.push(res)))); for(var item in obj) f(item); return p.then(() => nv); } function c2pP(f) {// turns a callback(err,data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((err, result) => (err && rej(err)) || res(result)); f.apply(this, args); }); }; } function c1pP(f) { // turns a callback(data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((result) => res(result)); f.apply(this, args); }); }; } function pRetry(nretry, fn, arg) { return fn(arg).catch(err => { if (nretry <= 0) throw err; return pRetry(nretry - 1, fn,arg); }); } // these functions retry a promise max nretry times or repeat a promise nrepeat times with calling fn(arg) as the function which needs to return a promise function pRepeat(nrepeat, fn, arg, r) { r = r || []; return fn(arg).then(x => (nrepeat <= 0 ? Promise.resolve(r) : pRepeat(nrepeat - 1, fn,arg, r, r.push(x)))); } function wait(time,arg) { return new Promise(res => parseInt(time)>=0 ? setTimeout(res,parseInt(time), arg) : res(arg))} // wait time (in ms) and then resolve promise with arg, returns a promise thich means the '.then()' will be executed after the delay function pGet(url,retry) { // get a web page either with http or https and return a promise for the data, could be done also with request but request is now an external package and http/https are part of nodejs. const fun = url.startsWith('https') ? https.get : http.get; return (new Promise((resolve,reject)=> { fun(url, (res) => { const statusCode = res.statusCode; const contentType = res.headers['content-type']; if (statusCode !== 200) { const error = new Error(
Request Failed. Status Code: ${statusCode}); res.resume(); // consume response data to free up memory return reject(error); } res.setEncoding('utf8'); var rawData = ''; res.on('data', (chunk) => rawData += chunk); res.on('end', () => resolve(rawData)); }).on('error', (e) => reject(e)); })).catch(err => { if (!retry) throw err; return wait(100,retry -1).then(a => pGet(url,a)); }); } const pExec = c2pP(exec), pCst = c1pP(createState), pSst = c2pP(setState), pGst = c2pP(getState); function pSubs(scriptname) { // Print subscriptions const subscriptions = getSubscriptions(); var subscriptionCount = 0; if (scriptname === undefined) scriptname = 'alle'; for (var dp in subscriptions) for (var s of subscriptions[dp]) if ((scriptname == 'alle') || (scriptname == s.name) || dp.match(scriptname) || s.name.match(scriptname)) _D(
on(${s.pattern.id}), im Script: ${s.name}, change: ${s.pattern.change}, logic: ${s.pattern.logic},subscriptionCount++); return _D(subscriptionCount < 1 ? "keine Subscrition enthalten. Filter: (" + scriptname +")" : 'Anzahl Subscriptions: ' + subscriptionCount + ' in javascript.' + instance + ', Filter: ("' + scriptname + '")',subscriptionCount); } function telegram(text) { _D(
Send '${text}' to Telegram!,pSst("telegram.0.communicate.response"/*Send text through telegram*/,text,false)); } // ================ StateMachine start---------------- function Setter(fun, min) { // fun = function returng a promise, min = minimal time in ms between next function call if (!(this instanceof Setter)) return new Setter(fun, min); if (typeof fun !=='function') throw Error('Invalid Argument - no function for Setter'); ! this._efun = fun; this._list = []; this._current = null; this._min = min || 20; this._enabled = false; return this; } ! Setter.stripEx = function(s) { return typeof s === 'string' ? (s.trim().startsWith('!') ? s.slice(1).trim() : s.trim()) : s; }; Setter.prototype.toString = function() { return
Setter(${this._min},${this._enabled},${this.length})=${this.length>0 ? this._list[0] : 'empty'}; }; Setter.prototype.clearall = function() { this._list = []; return this; }; Setter.prototype.add = function(id) { ! function execute (that) { if (that.length>0 && !that._current) { that._current = that._list.shift(); that._efun.apply(null,that._current) .then(() => wait(that._min),e => e) .then(() => execute(that,that._current = null),() => execute(that,that._current = null)); } } ! const args = Array.prototype.slice.call(arguments); if (this._enabled && (typeof id !== 'string' || !id.startsWith('!'))) { this._list.push(args); if (this._list.length === 1) execute(this); return this; } if (typeof id === 'string' && id.startsWith('!')) args[0]=id.slice(1); return this._efun.apply(null,args); }; ! Object.defineProperty(Setter.prototype, "min", { get: function() { return this._min; }, set: function(y) { this._min = y>5 ? y : 5;} }); Object.defineProperty(Setter.prototype, "length", { get: function() { return this._list.length; }, }); Object.defineProperty(Setter.prototype, "enabled", { get: function() { return this._enabled; }, set: function(y) { this._enabled = !! y;} }); ! function MState(options,afrom) { if (!(this instanceof MState)) return new Mstate(options,afrom); if(options===undefined || _T(options)!=='object') options = {val:options}; if (options.val !== undefined) this.val = options.val; if (options.ack !== undefined) this.ack = options.ack; if (options.ts !== undefined) this.ts = options.ts; else this.ts = Date.now(); if (options.lc !== undefined) this.lc = options.lc; if (options.from !== undefined) this.from = options.from; if (options.num !== undefined) this.num = options.num; if (options._obj !== undefined) this._obj = options._obj; if(typeof afrom === 'string') this.addFrom(afrom); return this; } ! MState.prototype.addFrom = function(from) { return (this.from = from + (this.from ? ' | ' + this.from : '')) ; }; MState.prototype.toString = function() { return
MS(${_T(this.val)=== 'symbol' ? this.val.toString() : this.val}, ${this.stime}, '${this.sfrom}', ${this.num !== undefined ? this.num : ''}); }; Object.defineProperty(MState.prototype, "sfrom", { get: function() { return this.from.length>52 ? this.from.slice(0,25)+' ... '+ this.from.slice(-25) : this.from; } }); Object.defineProperty(MState.prototype, "stime", { get: function() { var t = new Date(this.ts); const ts = t.toTimeString().slice(0,8); t = (''+t.getMilliseconds()); return ts +'.'+'0'.repeat(4-t.length)+t; } }); Object.defineProperty(MState.prototype, "value", { get: function(d) { return (this.val = (this.val !== undefined ? this.val : d)); }, set: function(y) { this.val = y;} }); Object.defineProperty(MState.prototype, "time", { get: function() { return this.ts; }, set: function(y) { this.val = y;} }); ! function SmBase(name,machines,cn) { const sm = cn === 'StateMachine'; this[SmBase.Name] = cn ? cn : 'SmBase'; _E(typeof name !== 'string',name + ' err: invalid name:'+_O(name,1)); this._name = name.trim(); _E(!(machines instanceof StateMachine),
${this[SmBase.Name]} err: invalid type of Machines: ${_O(machines,1)}); this[SmBase.Machine] = machines; if(!sm) { _E(machines._items.get(name),this[SmBase.Name] + ' err: Machine has already item "'+name+'"'); machines._items.set(name,this); } this._val = null; this._list = []; this._listeners = []; return this; } ! SmBase.instanz = "javascript." + instance + "."; SmBase.stSetter = new Setter(pSst,50); SmBase.setter = function(a,b,c) {return _PR(SmBase.stSetter.add(a,b,c).length);}; SmBase.printArgs = function (val) {return _D(
printArgs(${Array.prototype.slice.call(arguments)}));}; SmBase.toggleState = function(id) {return SmBase.mGst(id).then(st => st.notExist ? _PE() : SmBase.setter(id,!st.val));}; SmBase.readState = function(id) { return SmBase.mGst(id).then(st => st.notExist ? undefined : st.val);}; SmBase.mGst = function(id) { // Get an id or if nothing returned try to get 'SmBase.instanz'+id id = Setter.stripEx(id); return pGst(id) .then(x => x ? x : pGst(SmBase.instanz+id), x => pGst(SmBase.instanz+id)) .then(x => x ? x : _W(
Could not get state for ${_O(id)},null),x => _W(
Could not get state for ${_O(id)},null)); }; ! SmBase.Machine = Symbol('machine'); SmBase.Name = Symbol('name'); SmBase.ExitRun = Symbol('exitRun'); SmBase.Toggle = Symbol('toggle'); SmBase.Empty = Symbol('empty'); ! Object.defineProperty(SmBase.prototype, "machines", { get: function() { return this[SmBase.Machine]; } }); Object.defineProperty(SmBase.prototype, "type", { get: function() { return this[SmBase.Name]; } }); Object.defineProperty(SmBase.prototype, "name", { get: function() { return this._name; }, set: function(n) { this._name = n; } }); Object.defineProperty(SmBase.prototype, "val", { get: function() { return this._val; }, set: function(y) { this._val = y;} }); Object.defineProperty(SmBase.prototype, "sname", { get: function() { return this.machines.name + '.' + this.name; }, }); Object.defineProperty(SmBase.prototype, "items", { get: function() { return this.machines._items; }, }); Object.defineProperty(SmBase.prototype, "list", { get: function() { return this._list; }, }); Object.defineProperty(SmBase.prototype, "lname", { get: function() { return SmBase.instanz+this.sname; }, }); ! SmBase.prototype.run = function(st) { return _PR(_W('.run of SmBase should never happen!')); }; SmBase.prototype.toString = function() { return this.type+'('+this.name+')'; }; SmBase.prototype.getMachine = function(m) { return this.machines.getMachine(m); }; SmBase.prototype.getDebug = function(l) { return this.machines.getDebug(l); }; SmBase.prototype.getVariable = function(v) { return this.machines.getVariable(v); }; ! SmBase.prototype.execute = function(st) { const nst = new MState(st,this.name); // _D(
${this}.execute ${nst}); return this.run(nst) .then(r => r === SmBase.ExitRun ? _PR(null) : (nst.val = this.val, pSeries(this._listeners.filter((ni) => !(ni.item instanceof SmState) || ni.item.isActive), (l) => (nst.num=l.num,l.item.execute(nst)),-1))) .catch(e => _W(
${this}.execute catch: ${_O(e)})); }; ! SmBase.prototype.setItem = function(item,val,num) { item = this.items.get(item.trim()); if (!item) return; const nst = new MState({val:val,num:num,_noFun:true},'execute:'+item.name); _D(
${item}.executeItem ${nst}); return item.run(nst) .catch(e => _W(
${item}.execute catch: ${_O(e)})); }; ! SmBase.prototype.runAction = function(action,st) { var sa; switch(_T(action)) { case 'function': if(this.getDebug(2)) _D(
SmAction(${action}), from=${st}); return _PR(action(st.val,this,st)) .catch(e => _W(
${action}.runAction catch1: ${_O(e)})); case 'array': return pSeries(action,x => this.runAction(x,st),-1) .catch(e => _W(
${action}.runAction catch2: ${_O(e)})); case 'string': action = action.trim(); break; default: return _PR(_W(
runAction invalid action type: ${_T(action)}=${action}; st=${st})); } if(this.getDebug(2)) _D(
SmAction(${action}) st=${st}); var onoff = action.slice(-1), id = action.slice(0,-1).trim(), val = SmBase.Empty; // st.val === undefined ? SmBase.Empty : st.val; if((sa=action.match(/^\s*wait\:\s*(\d+)\s*$/))) return wait(parseInt(sa[1])); switch (onoff) { case '-': val = false; break; case '+': val = true; break; case '~': val = SmBase.Toggle; break; case '!': val = !st.val; break; case '?': val = st.val; break; default: sa = action.split('='); if (sa.length==2) { id = sa[0].trim(); val = sa[1].trim(); // _D(
runAction ${action} = ${val}, ${arg}, ${from},val = arg); } else id = action; break; } const nst = new MState(st); if (val !== SmBase.Empty) nst.num = val; val = nst.val = val === SmBase.Empty ? nst.val : val; sa = this.items.get(id); while (sa && sa instanceof SmAction && typeof sa._action === 'string') { id = sa._action.trim(); sa = this.items.get(id); } if (sa) { if (sa instanceof SmAction) { return sa.runAction(sa._action,nst) } if (sa instanceof SmState) { val = sa.name.split('.')[1]; sa = sa._machine; } if (sa instanceof SmMachine) { var stt = sa._states.get(val); nst.num = val; return stt ? sa.setState(nst) : (typeof val === 'boolean' ? sa.enable(val) : _PR(_W(
runAction Wrong action: '${action} State ${val} not found in ${sa}'))); } if (sa instanceof SmVariable || sa instanceof SmEvent) { if(val !== SmBase.Empty) nst.val = sa.val = val; nst.num = -1; return sa.execute(nst); } if (typeof sa._action === 'function') return _PR(sa._action(val === SmBase.Empty ? null : val, sa, st)) .catch(e => _W(
${action}.runAction catch3: ${_O(e)})); } if(this.getDebug(2)) _D(
Set ${_O(id,1)} to ${val.toString()}); if(id === '_debug') return _PR(this.machines._debug = val); return (val === SmBase.Toggle ? SmBase.toggleState(id) : SmBase.setter(id,val)) .catch(e => _W(
${action}.runAction catch4: ${_O(e)})); }; ! SmBase.prototype.addListener = function(what,num) { var options = {}, test = true, idd = what, t; // if (this.getDebug(2)) _D(
${this}.addListener for ${_O(what)} with ${num}); switch(_T(what)) { case 'array': for(t of what) this.addListener(t,num); return true; case 'string': idd = what.trim(); if (this.items.has(idd)) { var there = this.items.get(idd); _E( !there,
addListener Error: wanted to add wrong listeners ${this} to ${idd}); for (var l of there._listeners) if (l.item === this && l.num === num) return; return there._listeners.push({item:this, "num":num}); } if((t=idd.match(/^astro\:\s*([a-zA-Z]{4,})\s*(([\+\-])\s*(\d+))?$/))) { options = {astro:t[1]}; if(t[2]) options.shift = parseInt(t[3]+t[4]); } else if((t=idd.match(/^\s*every\:\s*(\d+)\s*(h|m?s?)$/))) { options = parseInt(t[1]) * (t[2]=='s' ? 1000 : (t[2]=='m' ? 60000 : (t[2]=='h' ? 3600000 : 1))); } else if(idd.match(/^((\d\d?\s*)(,\s*\d\d?\s*)*)|\*(\s*\:((\s*\d\d?\s*)(,\s*\d\d?\s*)*)|(\s*\*)){1,2}$/)) { t = idd.split(':'); options = {time: {}}; t.forEach((str,index,arr) => { str = str.trim(); var item = ['hour','minute','second'][index]; if (str == '*') return; if (str.indexOf(',')<0) return (options.time[item]=parseInt(str)); options.time[item]=str.split(',').map(x => parseInt(x.trim())); }); } else { while(idd.length>0 && test) { l = idd.slice(-1); t = idd; idd = t.slice(0,-1).trim(); switch(l) { case '-': options.val = false; break; case '+': options.val = true; break; case '!': options.change = 'ne'; break; case '~': options.change = 'all'; break; case '/r>: options.ack = 'false'; break; case '&': options.ack = 'true'; break; default: idd = t; test = false; break; } } if (idd.startsWith('/') && idd.endsWith('/')) idd = new RegExp(idd.slice(1,-1)); options.id = idd; } idd = options; break; case 'object': _E(!(what.id || what.name || what.time || what.astro),
addListener has wrong object to process: ${_O(what)}); idd = what; break; case 'regexp': idd = what; break; default: _E(true,
wrong SmVariable source-id ${_O(what)}); } var lname = _O(idd); const that = this; t =this.machines._ioevents.get(lname); if (!t) { t = []; this.machines._ioevents.set(lname,t); // _D(
CReateOnListenerIoBroker ${lname}=${_O(idd)}(${num})); if (this.getDebug(2)) _D(
${this}.addListener for ${_T(idd)+' '+_O(idd)} with ${num}); if (typeof idd === 'number') setInterval((obj) => pSeries(t, fu => fu('I'+idd),-1), idd); else on(idd,(obj) => pSeries(t, fu => fu(obj),-1)); } if (typeof idd === 'number') lname = what; t.push(function onIoBrokerEvent(obj) { const nst = new MState( obj && obj.state ? obj.state : obj,lname); if (nst.val === undefined) nst.val = nst.ts; if (obj && obj.oldState && obj.oldState.lc) nst.lc = obj.oldState.lc; nst._obj = obj; // _D(
event ${that} with ${_O(obj)}:${nst}); if (num!==undefined) nst.num = num; return that.execute(nst).catch(e => _W(
${this}.execute on event catch: ${_O(e)})); }); }; ! function StateMachine(options) { if (!(this instanceof StateMachine)) return new StateMachine(options); SmBase.call(this, options && typeof options.name === 'string' ? options.name.trim() : 'StateMachine',this,'StateMachine'); this._options = options || null; this._items = new Map(); this._debug = 0; this._ioevents = new Map(); ! if (options) this.init(options); return this; } util.inherits(StateMachine, SmBase); ! StateMachine.prototype._preInit = function(options,Fun) { _E(!options || !Fun,
StateMachine.preInit has no valid definition (${options}) or ${Fun}); for(var i in options) { var name = i.trim(); if (this._items.has(name)) { _W(
StateMachine item '${name}' already defined. 2nd definition ignored!); continue; } var option = options[i]; var item = new Fun(name,this); item._option = option; } }; ! StateMachine.prototype.init = function(options) { _E(!options,
Empty options in SateMachine.init(${_O(options)})); ! if (options.actions) this._preInit(options.actions,SmAction); if (options.events) this._preInit(options.events,SmEvent); if (options.variables) this._preInit(options.variables,SmVariable); if (options.machines) this._preInit(options.machines,SmMachine); ! if(options.name) this.name = options.name.trim(); if (options.debug !== undefined) this._debug = options.debug || 0; if (options.setdelay) { var s = parseInt(options.setdelay); s = !s || isNaN(s) ? 0 : s; SmBase.stSetter.min = s; SmBase.stSetter.enabled = s>0; } else SmBase.stSetter.enabled = false; _E(!this._items.size,'State machine includes no definitions, cannot start!'); if (this.getDebug(1)) _D(
StateMachione initialize with name '${this.name}', debug=${this._debug} and setter:${SmBase.stSetter}`);
var t = [];
for(var i of this._items.values())
t.push(i);return pSeries(t.reverse(),x => _PR(x.init(x._option)),1) .then(x => { const init = this._items.get('_init'); if (init && init instanceof SmAction) { return init.execute(new MState({val:'_init'},'_init')); } return x; }) .then(x => on({id: new RegExp('^'+SmBase.instanz+this.name+'\.'), change:'ne', ack:false, fromNe:'system.adapter.'+SmBase.instanz.slice(0,-1) },(obj) => { // _W('Command for StateMachine ' +obj.id+ ' auf '+_O(obj.state)); if (!obj || !obj.state) return; var id = obj.id, val = obj.state.val, from = obj.state.from, ie = id.endsWith('._enabled'), nst = new MState(obj.state); id = id.split('.').slice(-1)[0]; var m = this._items.get(id); if (m && m instanceof SmMachine && m.list[val]) { if(m.getDebug(2)) _I(`Command for StateMachine ${id} auf ${_O(obj.state)}`); nst.num = m.list[val]; return ie ? (m._enabled = val) : m.setState(nst); } else if(m && !ie && m instanceof SmVariable) { nst.val = m.val = val; return m.execute(nst); } return null; })).catch(e => _W(`${this}.init catch: ${_O(e)}`));
};
! StateMachine.prototype.getDebug = function(l) { return typeof l === 'number' ? this._debug>=(l>0 ? l : 1) : this._debug; };
StateMachine.prototype.getMachine = function(machine) {
const m = this._items.get(machine.trim());
return m && m instanceof SmMachine ? m : undefined;
};
StateMachine.prototype.getActiveState = function(machine) {
const m = this.getMachine(machine);
return m && m._activeState ? m._activeState : undefined;
};
StateMachine.prototype.getVariable = function(variable) {
const v = this._items.get(variable);
return v && v.val !== undefined ? v.val : undefined;
};
! function SmMachine(name, machines) {
if (!(this instanceof SmMachine)) return new SmMachine(name, machines);
SmBase.call(this,name,machines,'SmMachine');
! this._fun = null;
this._activeState = null;
this._enabled = true;
this._states = new Map();
return this;
}
util.inherits(SmMachine, SmBase);
! SmMachine.prototype.init = function(option) {
switch(_T(option)) {
case 'function': this._fun = option; return true;
case 'array':
_E(option.length<=1 ,${this} array len too short ${_O(option)} !
);
const mf = this.items.get(option[0]);
_E(!mf || typeof mf._option !== 'function',${this} array len or no function ${_O(mf)} !
);
this._option = option = mf._option.apply(null,option.slice(1));
break;
case 'object': break;
default:
_W(SmMachine ${this.name} has wrong config ${_O(option)}
);
return false;
}
var def = null;
this._all = option._all;
for(var s in option) {
var sname = s.trim();
if (sname === '_all')
continue;
var soption = optionfunction SmState(name, machines, ini) {
if (!(this instanceof SmState)) return new SmState(name, machines, ini);
SmBase.call(this,name,machines,'SmState');! this._all = null;
this._machine = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmState, SmBase);
! Object.defineProperty(SmState.prototype, "isActive", {
get: function() { return this._machine._activeState === this; }
});
SmState.prototype.addEvent = function(to, event) {
const m = this._machine;
to = to.trim();
const t = m._states.get(to);
// _D(${this}.addEvent '${event}' to state ${t} ${to} not existing in ${m.name}
,false);
if (!t) return _W(${this}.addEvent '${event}' to state ${t ? t : to} not existing in ${m.name}
,false);
return this.addListener(event,to);
};
SmState.prototype.onExit = function(st) {
this._machine._activeState = null;
if (this._timeout)
clearTimeout(this._timeout);
this._timeout = null;
const nst = new MState(st,exit(${this.name})
);
return (this._onExit ? this.runAction(this._onExit,nst) : _PR())
.catch(e => _W(${this}.onExit catch: ${_O(e)}
));
};
SmState.prototype.onChange = function(st) {
var nst = new MState(st,_onState(${this.name})
);
nst.val = this.isActive;
return (this._onState ? this.runAction(this._onState,nst) : _PR())
.then(() => {
nst = new MState(st,_onNotState(${this.name})
);
nst.val = !this.isActive;}).then(() => this._onNotState ? this.runAction(this._onNotState,nst) : false) .catch(e => _W(`${this}.onStates catch: ${_O(e)}`));
};
SmState.prototype.onEnter = function(st) {
const nst = new MState(st,enter(${this.name})
);
nst.val = this.name;
if (this._timer) {
if (this._timeout)
clearTimeout(this._timeout);
const tst = new MState(st);
tst.num = this._timerState; tst.from = '_timeout:'+this._timer;
this._timeout = setTimeout(function(that) {
that._timeout = null;
that._machine.setState(tst);
}, this._timer, this);
}
return (this._onEnter ? this.runAction(this._onEnter,nst) : _PR())
.then(x => this._machine._activeState = this)
.then(x => pSeries(this._listeners.filter((ni) => !(ni.item instanceof SmState) || ni.item.isActive), (l) => (nst.num=l.num,l.item.execute(nst)),-1))
.catch(e => _W(${this}.onEnter catch: ${_O(e)}
));
};
SmState.prototype.execute = function(st) {
if (!this.isActive || !this._machine._enabled) // _W(${this}.execute '${_O(st)}'
);
return _PR(false);
return this._machine.setState(st)
.catch(e => _W(${this}.execute catch: ${_O(e)}
));
};! SmState.prototype.init = function(option) {
var e;
if (_T(option)==='string') {
this._machine.addListener(option.trim(),this.name);
option = {};
}
if (this._machine._all) {
for(e in this._machine._all) {
var aa = this._machine._all[e];
switch(e) {
case '_onState':
case '_onNotState':
case '_onEnter':
case '_onExit':
option[e]=aa;
break;
default:
this.addEvent(e,aa);
}
}
}
const st = this._machine.name;
const k = this._shortname;
for(e in option) {
var ea = option[e];
switch(e) {
case '_timeout':
var t = ea.split(':');
_E(t.length!=2 || (parseInt(t[1])>0)===false,Timer in ${st}.${k} has incorrect definition '${ea}'
);
this._timer = parseInt(t[1]);
this._timeout = null;
this._timerState = t[0];
break;
case '_onState':
case '_onNotState':
case '_onEnter':
case '_onExit':
this[e] = ea;
break;
case '_default':
this._machine._activeState = this;
this._default = ea;
break;
default:
this.addEvent(e,ea);
break;
}
}
};
! function SmEvent(name, machines, ini) {
if (!(this instanceof SmEvent)) return new SmEvent(name, machines, ini);
SmBase.call(this,name,machines,'SmEvent');
this._fun = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmEvent, SmBase);
! SmEvent.prototype.init = function(option) {
if(_T(option)==='array') {
const f = option.slice(-1)[0];
var l = option;
if (typeof f === 'function') {
this._fun = f;
l = l.slice(0,-1);
}
if (l.length< 1) return _W(Event Definition Error: SmEvent has no funcion or no values to process: ${this}=${_O(l)}, ${_O(f)}
);
l.forEach((v,i) => (this.list.push(null),this.addListener(v,i)));
} else this.addListener(option);
};
SmEvent.prototype.run = function(st) {
if (this._fun && st.num>=0 && st.num< this.list.length) {
this.list[st.num] = st.val;
if (this.getDebug(3)) _D(${this} ${st.num}=${st.val} ? ${st.sfrom}
);
}
return _PR(this._fun ? this._fun(this.val,this,st) : st.val).then(x => this.val = x)
.then(x => (!x && this._fun) ? SmBase.ExitRun : true )
.catch(e => _W(${this}.run catch: ${_O(e)}
));
};
function SmAction(name, machines, ini) {
if (!(this instanceof SmAction)) return new SmAction(name, machines, ini);
SmBase.call(this,name,machines,'SmAction');
this._action = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmAction, SmBase);
! SmAction.prototype.init = function variableInit(option) {
switch(_T(option)) {
case 'function':
case 'array': this._action = option; break;
case 'string': this._action = option.trim(); break;
default:
return _W(Action definition Err: Action needs a string, function, array or {id/val} object: ${_O(option)}
);
}
const ma = this.name.match(/^\se:\s(.+)$/);
if (ma)
this.addListener(ma[1].trim());
};SmAction.prototype.run = function(st) {
_E(!this._action,Action.execute: No action to execute!
);
return this.runAction(this._action,st)
.catch(e => _W(${this}.run catch: ${_O(e)}
));
}; // Line 900!!! // ================= StateMachine end ===================`! Dieses script ist (ohne Kommentare ) 900 Zeilen lang und im obigen Spoiler versteckt. Es wurde auch urch eine neue Version ersetzt welche weniger bugs aufweist.
!
! Die Definition und Initialisierung der StateMachine erfolgt (bei mir) in einm normalen Javascript-script.
! Die kürzeste Form schaut etwa so aus:
~~[code]~~// State Machene test and demos ! const statemachine = new StateMachine(); // Die StateMachine wird leer kreiert debuglog = 'info'; // debuglog ist der Loglevel von debug messages, Wenn Adapter-loglevel größer oder gleich dann werden debug logs angezeigt ! wait(100) // Verzögerte Initialization und Subscriptions anzeigen wenn debug>=1 ist .then(() => statemachine.init(testMachine)) // Die StateMachine wird mit der Konfiguration initialisiert. Damit werden Objekte angelegt und die Event-Subscriptions gestartet .then(() => _I(
${statemachine.name} intitialized with ${SmBase.stSetter.enabled ? SmBase.stSetter.min +'ms' : 'no'} setterDelay on debuglevel ${statemachine._debug}), e => _W(
Err init: ${e})) .then(() => statemachine.getDebug(1) ? pSubs(name) : false) // Wenn der Debuglevel der statemachine >=1 ist dann werden die Subscriptions angezeigt .then(() => _I('subscriptions running'), err => _W(
Err finish ${_O(err)}`)) // test functions
;const testMachine = { // Die Definition der Maschinenlogik
actions: {
"e:rpi2.0.gpio.19.state": "rpi2.0.gpio.26.state"
},
};
[/code]`! Dieses Beispiel macht nur eines, es definiert eine Aktion die beim Auftreten eines Events auf 'rpi2.0.gpio.19.state' (ist bei mir ein PIR-Sensor) dessen Zustand auf den Ausgang 'rpi2.0.gpio.26.state' (ist bei mir eine Led) schreibt. Also immer wenn der Sensor Bewegung anzeigt geht die Led an!
! Gut, alle Javascript-Adapter-Kenner werden jetzt wohl sagen dass das auch der Befehl
~~[code]~~on('rpi2.0.gpio.19.state','rpi2.0.gpio.26.state');[/code]
erwirkt hätte, und damit haben sie Recht. Nun, aber meine 'actions' können noch etwas mehr!
! z.B. würde
~~[code]~~ "e:rpi2.0.gpio.19.state": "rpi2.0.gpio.26.state!", // hier würde die Led leuchten wenn der Pir kein Signal ausgibt, also das Pir-Signal invertieren "e:rpi2.0.gpio.19.state": "rpi2.0.gpio.26.state~" , // hier würde die Led an- und aus-geschaltet (toggle) wenn immer ein Pir-Signal kommt "e:rpi2.0.gpio.19.state": "rpi2.0.gpio.26.state+" , // das würde die Led einschalten wenn ein Pir-Signal kommt "e:rpi2.0.gpio.18.state": "rpi2.0.gpio.26.state-" , // und ausschalten wenn ein Signal am Eingag 18 kommt! [/code]
! Aber das ist ja noch immer kompliziert, oder? Vielleicht müssen wir wissen dass ich Aktionen definieren kann die mir das Leben etwas vereinfachen. Es gibt auf Events die ich definieren kann und die mir helfen das Leben zu erleichtern.
! Also könnte obiges auch folgendermaßen geschrieben werden:
!~~[code]~~const testMachine = { // Die Definition der Maschinenlogik events: { pir: 'rpi2.0.gpio.19.state', pir2: 'rpi2.0.gpio.18.state', }, actions: { led: 'rpi2.0.gpio.26.state', "e:pir": "led", 'e:pir': 'led!', 'e:pir': 'led~', 'e:pir': 'led+', 'e:pir2': 'led-', }, [/code]
Obwohl das alles gleichzeitig natürlich KEINEN Sinn macht und auch nicht funktionieren würde da 3x 'e:pir' mit unterschiedlichen Aktionen definiert werden, aber ich wollte nur veranschaulichen was eine Aktion sonst noch kann.
! Übrigens, das ist erst der Anfang, andere Möglichkeiten von Aktionen sind: Arrays, also mehrere Aktionen und auch Funktionen!
! Ein kleines Beispiel:
!~~[code]~~ events: { pir: 'rpi2.0.gpio.19.state', pir2: 'rpi2.0.gpio.18.state', }, actions: { printme: (val,that,st) => _D(
${that}: ${val} from ${st}), led: 'rpi2.0.gpio.26.state', led2:'rpi2.0.gpio.25.state', "e:pir": ["led+",'led2-','printme=pir'], "e:pir2": ['led-','led2+','printme=pir2'], }, [/code]
! Das würde eine Aktion printme definieren die den Wert, die Aktion und die Herkunft des Events das die Aktion auslöst auf dem debug-log (über die _D()-Funktion) ausgibt.
! Es definiert auch Aktionen die beim Erkennen eines Signals auf Pir die eine Led einschaltet und die andere ausschaltet, und bei pir2 umgekehrt. Natürlich werden dann immer logs ausgegeben.
! Events können auch noch viel mehr, so würde z.B. ein
~~[code]~~ 'e:every 15s': 'rpi2.0.gpio.26.state~' [/code]
! Da ist aber noch bei weitem nicht alles erklärt. Events können auch viel mehr und ich brauche wahrscheinlich noch 2 posts um das alles zu erläutern.
! Neben actions und events gibt es auch noch 'machines' und 'variables'.
! Ich werde beides erst im nästen Artikel beschreiben aber als kleiner Vorgeschmack wie so eine StateMaschine ausschaut oder was Events sonst noch können:
!~~[code]~~ machines: { // Definition der StaeMachines TestSM: { ein: { aus: "*:*:20,40,0", _onState:'led' }, aus: { ein: "*:*:10,30,50", _default:true } }, }, events: { pir: 'rpi2.0.gpio.19.state', every15: 'every 15s', amMorgen: "06:00", Sonnenaufgang20: 'astro:dusk +20', everyminute2ndsecond: "*:*:2", }, actions: { printme: (val,that,st) => _D(
${that}: ${val} from ${st}), led: 'rpi2.0.gpio.26.state', led2:'rpi2.0.gpio.25.state', "e:pir": ["led+",'led2-','printme=pir'], "e:pir2": ['led-','led2+','printme=pir2'], }, [/code]
! Das würde eine StateMachine erzeugen die die Led jede Minute bei der Sekunde 10,30, und 50 einschaltet und bei der Sekunde 0,20 und 40 ausschaltet.
! mit
~~[code]~~ machines: { // Definition der StaeMachines TestSM: { ein: { aus: 'toggle', _onState:'led' }, aus: { ein: 'toggle', _default:true } }, }, events: { toggle: 'every 10s', ... [/code]
kann die Led auch alle 10s ein und ausgeschaltet werden... aber wie schon gesagt, das ist noch immer nur ein kleiner Teil der Funktionalität.
! Die StateMachines erzeugen auch Objekte in IoBroker die den State angeben. Sie können auch über ioBroker auf andere States umgesetzt werden under disabled/enabled werden. Damit kann man z.B. steuern wann bestimmte StateMachines aktiv sein sollen (nur wenn Alarm ein ist oder nur wenn es dunkel ist).
! Variable werden auch in ioBroker-Objekte abgebildet und ermöglichen die Einbeziehung von Sensoren oder Werten in die Entscheidung für States oder Aktionen.
! Ach ja, eine Einschränkung: Das script funktioniert nur mit nodejs >=4.x, also nicht auf 0.1x!
! Mehr beim nächsten mal!
! Gruß[/i][/i][/i] -
Hallo mitsammen!
Dieser Eintrag ist reserviert für den nächsten Teil der Anleitung um 'Actions' und 'Events' abzuschließen und auch Variable vorzustellen.
Ich habe schon fast alle Möglichkeiten für die Actions gezeigt. Ich möchte auch darauf hinweisen dass die Namen der Actions, Events, Variable und StatesMaschinen nur einmal verwendet werden können, also es darf keine Action mit dem gleichen Namen kreiert werden wie es einen Event oder eine Variable… gibt!
Nun zurück zu den Actions, Es gibt folgende Variationen wie sie definiert werden können:
- Ein String mit : name( ? | + | - | ~ | ! | =wert ) ist Das Fragezeichen kann normalerweise auch weggelassen werden, es gibt nur an dass der Wert vom auslösenden Event übernommen wird. Name kann ein id sein oder ein name einer anderen Aktionen oder Variablen. Auch eine StateMachine kann ein name sein, damit kann man sie mit 'Machine=ein' z.B. auf den state schalten. Es gibt einen besonderen Namen: 'wait', 'wait=100'
würde 100ms warten. * ? = Wert vom auslösendem Element übernehmen * + = auf true/1/ein schalten * - = auf false/0/aus schalten * ~ = umschalten (toogle), d.h. wenn der id ein war ausschalten und umgekehrt. Achtung, funktioniert nur wenn name ein id ist oder eine Aktion die auf einen id ohne zusätzliche Aktionen zeigt! * ! = invertiere Eingangswert * =wert = damit kann man beliebige Werte zuweisen. Bitte nicht 'false' oder 'true' verwenden sondern eher '0' und '1' bei states da 'false' als string interpretiert wird und auch als 'true' in ein boolean umgewandelt wird :(
-
ein array von actions, also z.B. "['action1','action2', 'wait:1000' , 'action3']". Da würde nach der 2. Aktion eine Pause von einer Sekunde eingelegt werden.
-
eine Funktion(val,that,st) die 3 Parameter hat: Eine Value=val, einen Zeiger auf die Aktion (that), und eine State=st aus dem herausgelesen werden kann wie/wann der Event erzeugt wurde und wo er herkommt.
Nun zu den Events.
- Ein String mit : name( + | - | ~ | ! | $ | & ) wobei der Name ein id oder ein event-name ist * + = Event schaltet nur wenn state auf true ist
* - = Event schaltet nur wenn state auf false ist * ~ = Event schaltet bei 'all'en Schaltvorgängen * ! = Event schaltet nur wenn wen Wert ungleich ist ('ne') * $ = Event schaltet nur wenn ack false ist * & = Event schaltet nur wenn ack true ist
-
eine string das 'Stunde(,n)|:Minute(,n)| (:Sekunde(,n)' enthalten kann wobei die Sekunden weggelassen werden können samt dem 2.':'. Somit z.B. um '14:13:52' zu einer fixen Zeit oder mit '*:2,22,42' jede Stunde um 2 Minuten, 22 Minuten und 42 Minuten der Event erzeugt werden.
-
eine string das 'every:xxx(s|m|h|ms) und einen event alle xxx Sekunden/Minuten/Stunden/Millisekunden (auch wenn kein ms angegeben wird) erzeugt. Also 'every 10m' erzeugt einen Event
-
eine string das 'astro:name (+|- xxx) der einen astro-event beim 'namen' erzeugt und diesen dann mit +-xxx Minuten nach vorne (-) oder hinten (nichts oder +) schiebt
-
eine reguläre Expression, also /.*.STATE/
-
ein Objekt wie unter https://github.com/ioBroker/ioBroker.javascript beschrieben. also z.B. '{id: "javascript.0.myState1", change: "ne"}' oder '{time: "*/2 * * * *"}'. Damit können kompliziertere Abfragen gebaut werden.
-
ein array von events, also z.B. "['event1','event2','event3' (, funcktion()]". Wird bei der Eventdefinition eine Funktion am Ende des Arrays definiert so wird diese immer aufgerufen wenn irgend ein Event vorher auftritt, sie kann dann 'true' zurückgeben um den Event auszulösen oder 'false' um den Event nicht auszulösen. Damit kann man events mit kalkulierten Bedingungen bauen
Und wenn wir schon dabei sind dann schen wir uns noch die Variablen an.
variables: { helligkeit: ["hm-rpc.0.NEQ0119725.1.BRIGHTNESS", "hm-rpc.0.NEQ0119727.1.BRIGHTNESS",(val,that,st) => (val*3 + that.list[0] + that.list[1])/ 5], counter: ['every:17s', (last) => 1+last>1000 ? 0 : 1+last], },
würde eine Variable 'helligkeit' definieren, diese würde auch als Objekt in der StateMachine kreiert werden. Wenn immer die zwei Helligkeitssensoren einen neuen Wert liefern würde die Funktion am Ende der Liste aufgerufen werden und in diesem Fall den alten Wert mit 3 multipliziert und die beiden aktuellen werte dazugezählt und das ganze durch 5 dividiert werden was einen 'gefilterten und gemittelten' Hellighkeitswert bedeuten würde. Dieser Wert wird dann in der Variablen als neuer Wert abgespeichert und ein Event mit dem neuen Wert erzeugt.
Der counter würde jede 17 Sekunden eine Zahl erhöhen und wenn sie über 1000 kommt auf 0 zurückstellen.
Also sind Variablen sowas wie programmierbare Werte die events auslösen und berechnen können.
So, damit ist der 2. Teil abgeschlossen. Viel Spaß!
-
So, dieser teil ist den StateMachines gewidmet, was die halt sonst noch können
machines: { // Definition der StateMachines TestSM: { _default:'aus', _all: { aus:'tasteraus', ein:'tasterein' }, ein: { aus: 'tastertoggle', _onChange:'Lampe?' }, aus: { ein: 'tastertoggle', _default:true } }, },
Oben ist ein erweitertes Beispiel. Eins im Voraus: es sollte ein _default-State angegeben werden, das kann auf 2 Arten geschehen, entweder anstatt einer State-Difinition als '_default:"state"' ofder im state als '_default:true'. Beides ist nicht sinnvoll und die Definition im State würde siegen. Wenn allerdings kein default state vergeben wird meckert das Programm und nimmt den 1. State als solchen.
Nun, der state '_all' ist speziell dahin gesehen dass alle states dieser Maschine diese Attribute hinzubekommen. Also in unserem Fall würde der 'tasterein' und 'tasteraus' für beide States (ein und aus) definiert werden. Da siech diese nicht verändern ist es damit möglich eine wiederkehrende Auflistung vereinfachen. Übrigens, tritt ein Event in einem State auf der ihn selbst ans Ziel angibt wird er ignoriert, also macht es nix wenn der ein-State einen verweis auf sich selbst enthält.
Nun, jeder State besteht aus einem Objekt welches aus folgenden Teilen bestehen kann zusammengesetzt:
-
'_default:true' setzt den state als default-state aktiv bei Programmstart falls die Statemaschine noch keinen Objekteintrag hat, sonst wird der letzt State genonmmen.
-
'_onEnter: Aktion' ruft Aktion auf wenn zu diesem State gewechselt wird.
-
'_onExit: Aktion' ruft Aktion auf wenn auf einen anderen State gewechselt wird.
-
'_onChange: Aktion' ruft Aktion auf wenn sich dieser State ändert, also bei _onEnter und _onExit und der Wert der generiert wird ist true bei _onEnter und false bei _onexit. Damit wird die Lampe im Beispiel ein und ausgeschaltet.
-
'_timeout: "xxxx|state' wechselt auf State 'state' nach xxxx ms. als Beispile würde '_timeout: "60000:aus"' bewirken dass der state nach einer Minute zum State 'aus' verlassen wird.
-
'State: Event' wechstelt zu 'State' wenn Event auftritt. Die Maschione meckert falls es State nicht gibt.
Wenn wir das Beispiel nun erweitern:
TestSM: { _all: { aus:'tasteraus', ein:'tasterein', timer:'pir' }, ein: { aus: 'tastertoggle', _onEnter:'Lampe+' }, aus: { ein: 'tastertoggle', _onEnter: 'Lampe-', _default:true }, timer: { aus: 'tastertoggle', _timeout:'aus:100000', _onEnter:'Lampe+'}, },
So was setzte ich z.B. im Vorzimmer ein wo ich einen Bewegungsmelder hab aber auch einen Ein/Ausschalter beim Eingang sowie einen Taster der nur umschalten kann beim Kellerabgang und in der Küche.
Nun, ich habe mehrere solche Kombinationen und wollte die nicht immer wiederholen, deshalb hab ich noch eine 'Makro-Funktion' eingebaut:
machines: { // Definition der StateMachines ToggleTimer(taus,tein,ttoggle,ttimer,ttimerlen,lampe) { return { _all: { aus:taus, ein:tein, timer:ttimer }, ein: { aus: ttoggle, _onEnter:lampe+'+' }, aus: { ein: ttoggle, _onEnter: lampe+'-', _default:true }, timer: { aus: ttoggle, _timeout:'aus:'+ttimerlen, _onEnter:lampe+'+'}, }; }, TestSM1: ['ToggleTimer','tasteraus','tasterein','tastertoggle', 'pir', 3000, 'Lampe'], TestSM2: ['ToggleTimer','tasteraus',[],['tastertoggle','tastertoggle2'], 'pir2', 60000, 'Lampe2'], ...
Oben hab ich eine Funktion definiert die ein neues Maschionenobjekt berechnet. Aufgerufen wird mittels dem Array, der Erste Eintrag ist der Name der Funktion und die weiteren sind die Parameter. Damit kann ich eine Standard-Statemaschine definieren (z.B. ein/aus/Toggle oder wie zuletzt auch mit Timer) und dann diese auf verschiedene Ein/Ausgänge anwenden.
So werden zwei Machinen namens TestSM1 und TestSM2 im obigen Beispiel definiert.
Übrigens, Events die keine Eingänge haben können durch '[]' ersetzt werden (Siehe TestSM2 oben) , Namen für Aktionen müssen aber immer angegeben werden!
So, nun ein anderes Beispiel:
Astro: { _default:'Dawn', _all: { _onEnter: 'printme?' }, Nadir:'astro:nadir', NightEnd:'astro:nightEnd', NauticalDawn:'astro:nauticalDawn', Dawn:'astro:dawn', Sunrise:'astro:sunrise', SunriseEnd:'astro:sunriseEnd', GoldenHourEnd:'astro:goldenHourEnd', SolarNoon:'astro:solarNoon', GoldenHour:'astro:goldenHour', SunsetStart:'astro:sunsetStart', Sunset:'astro:sunset', Dusk:'astro:dusk', NauticalDusk:'astro:nauticalDusk', Night:'astro:night', },
Die oben gezeigte StateMachine zeigt dass man einen State auch nur durch einen Event definieren kann. Das _all definiert dazu noch dass nach allen States der momentane Zustand ausgegeben werden soll.
Nun, wenn ich in Vis den aktuellen Astro-State anzeigen will brauch ich nur den das Astro-Objekt verwenden.
Es schaut so aus:
Man sieht hier auch die ._enabled state mit welcher man die StateMachine ausschalten kann.
Übrigens, mein printme in 'actions' schaut so aus:
printme: val => pExec(`python /home/pi/write.py ${val}`), telegram: text => pSst("telegram.0.communicate.response"/*Send text through telegram*/,text,false),
Ich habe am Testsystem einen SenseHAT der eine 16x16 led-Matrix hat und darauf eine Laufschrift erzeugen kann mit the Phython-Programm write.py
Genau dieses ruft mein printme auf wenn immer sich der Status der Astro-StateMachine ändert
Ach ja, ich habe mir auch eine telegram-Action definiert, anstatt 'printme?' könnte ich mir mit 'telegram?' den Asto-State auf's handy schicken lassen.
Das mache ich aber nur mit meinen Alarm-States und den zugehörigen Kamerabildern ….
-
-
Wie schon vorher versprochen möcht' ich hier noch ein paar allgemeine Details erläutern.
Auf Maschinenebene können drei verschiedene Optionen definiert werden:
const testMachine = { // Die Definition der Maschinenlogik name : 'StateMachine_test', // Der Name der Machine, dieser wird auch für die Objekte verwendet debug: 1, // Debug-Level (0-3) der StateMachine, 0 = keine Info, 1 = STatechanges und einige wenige Meldungen, 2 & 3 mehr meldungen auch dann bei jedem event setdelay:25, // Verzögerung in ms der ausgaben auf Objekte die nicht mit'!' am Anfang gekennzeichnet sind. Bei 0 oder Abwesenheit keine Verzögerung machines: { // Definition der StaeMachines ...
Man kann den Namen der StateMachine mit 'name:'MeinName' definieren, dieser Name erscheint auch im Objektbrowser als Teil für alle Machines und Variablen.
Dann kann man eine Debug-Funktion aktivieren mir 3 Levels: 1 = niedrigster Level, im Log erscheint welche Events subscribed sind und jeder Wechsel von States.
Das schaut dann im log so aus:
00:04:32.860 [info] javascript.0 script.js.Scripte.StateMachineTest: TestSM1.aus was timer: _timeout:3000 00:05:16.480 [info] javascript.0 script.js.Scripte.StateMachineTest: TestSM1.ein was aus: tastertoggle | every:10s
und bedeutet dass eine StateMaschine auf einen neuen State geschaltet wurde und in welchem State sie vorher war. Es zeigt auch eine Ereignisliste weshalb der State umgeschaltet wurde.
Im obigen Beispoiel z.B. wurde ein Event 'every:10s' getriggert der im event tastertoggle definiert war und dann in der Statemaschine TestSM1.aus die StateMaschine auf 'ein' gestellt hat.
Die am weitesten rechts liegene Eventbezeichnung ist die Ursache des Events und durch '|' getrennt nach links die Hirarchie bis zu der Ausführenden Stelle.
Bei Debuglevel 2 werden die Events selbst auch angezeigt und bei Debuglevel 3 auch Variablenzuweisungen.
Das kann dann so aussehen:````
00:12:46.585 [info] javascript.0 script.js.Scripte.StateMachineTest: TestSM1.aus was timer: tastertoggle | every:10s
00:12:46.587 [info] javascript.0 script.js.Scripte.StateMachineTest: debug: SmAction(Lampe-) st=MS(TestSM1.aus, 23:12:46.0536, 'enter(TestSM1.aus) | tastertoggle | every:10s', aus)
00:12:46.587 [info] javascript.0 script.js.Scripte.StateMachineTest: debug: Set 'rpi2.0.gpio.26.state' to false
00:12:47.347 [info] javascript.0 script.js.Scripte.StateMachineTest: debug: SmVariable(counter) val = 234
00:12:47.348 [info] javascript.0 script.js.Scripte.StateMachineTest: TestSM1.ein was aus: tasterein | every:17s
00:12:47.348 [info] javascript.0 script.js.Scripte.StateMachineTest: debug: SmAction(Lampe+) st=MS(TestSM1.ein, 23:12:47.0336, 'enter(TestSM1.ein) | tasterein | every:17s', ein)
00:12:47.349 [info] javascript.0 script.js.Scripte.StateMachineTest: debug: Set 'rpi2.0.gpio.26.state' to trueIch verwende, wie im 1\. Post erwähnt, verschiedene Technologien, einige davon basieren auch auf unterschiedliche Funktechnik, sogar auf gleichen Frequenzen. Ich hatte gemerkt wenn ich mehrere Signale sende dann stören sich diese oft. Beide senden gleichzeitig drauf los und was am Empfänger ankommt ist zu oft nicht das Richtige. Das hat sich so ausgewirkt dass manche Schaltvorgänge nicht mehr richtig ausgelöst haben oder bei HomeMatic sogar Fehler verursacht haben. Deshalb hab ich in die StateMaschine eine automatische 'Serialisierung' von Aktionen mit id's implementiert. Dies wird mit 'setdelay:xxx' konfiguriert. ein 'setdelay:25' bedeutet z.B. dass alle SetStates hintereinander angereiht werden und nach 25ms stattfinden. Das bedeutet wenn bei einem State z.B. Licht1 eingeschaltet und Licht2 ausgeschaltet wird dann erfolgt beides mit jeweils 25ms verzögerung nacheinander. Da die Statemaschinen von extern in beliebigen Zeiträumen gesteuert werden kann es natürlich vorkommen dass ein 'every:xx' event zufällig nur wenige ms nach einem Taster event oder nach Zeitevents wie '*:*:30,50' gefeuert wird. Die Umschaltung wäre dann gleichzeitig aber würde bei setdelay trotzem immer hinten angestellt werden. Also wenn sogar 3 events innerhalb von 10ms feuern würden alle alle SetState-Aktionen um 25ms versetzt nacheinander ausgeführt. FRunktionen würden sofort ausgeführt werden. Nun, da auch das nicht immer sinnvoll ist weil ich zwar die meisten Schalter funkgesteuert habe aber z.,B. die Led am GPIO oder die Temperatur der Heizung (über KM200) nicht zeitversetzt betreiben muss hab ich für die Aktionen noch eine zusätzliche Syntax eingeführt:
led: '!rpi2.0.gpio.26.state', led2:'rpi2.0.gpio.25.state',
Ein Rufzeichen am Anfang würde bedeuten dass dass SetState sofort ausgeführt wird (bei led), bei led2 würde es in die setdelay-Schlange eingereiht werden und strikt hintereinander mit dem entsprechenden delay ausgeführt werden. Übrigens wenn man setdely auf 0 setzt oder weglässt dann werden Aktionen immer sofort ausgeführt, aber trotzdem 'nacheinander'. Als Beispiel action:
allesAus: ['licht1-','licht2-','wait:1000','licht3-'],
Würde ohne setdelay zuerst Licht1 dann licht2 (wenige ms Differenz zwischen den Befehlen wie lange Adapter und ioBroker eben so brauchen) ausschalten, dann 1s warten und Licht3 ausschalten. Wenn setdelay auf 50ms gesetzt ist dann würde Licht1 nach ~50ms, Licht2 nach ~100ms, und Licht3 nach ~1s (gerechnet ab dem Zeitpunkt wo die Aktion gestartet wird und nicht nach led2) ausgeschaltet werden. Um nochmal zusammenzufassen, eine komplette Definition mit 3 StateMachines könnte so ausschauen:
const testMachine = { // Die Definition der Maschinenlogik
name : 'StateMachine_test', // Der Name der Machine, dieser wird auch für die Objekte verwendet
debug: 1, // Debug-Level (0-3) der StateMachine, 0 = keine Info, 1 = STatechanges und einige wenige Meldungen, 2 & 3 mehr meldungen auch dann bei jedem event
setdelay:25, // Verzögerung in ms der ausgaben auf Objekte die nicht mit'!' am Anfang gekennzeichnet sind. Bei 0 oder Abwesenheit keine Verzögerung
machines: { // Definition der StaeMachines
ToggleTimer(taus,tein,ttoggle,ttimer,ttimerlen,lampe) {
return {
_all: { aus:taus, ein:tein, timer:ttimer },
ein: { aus: ttoggle, _onEnter:lampe+'+' },
aus: { ein: ttoggle, _onEnter: lampe+'-', _default:true },
timer: { aus: ttoggle, _timeout:'aus:'+ttimerlen, _onEnter:lampe+'+'},
};
},
TestSM1: ['ToggleTimer','tasteraus','tasterein','tastertoggle', 'every:13s', 1601000, 'Lampe'],
TestSM2: ['ToggleTimer','tasteraus',[],['tastertoggle','tastertoggle2'], 'pir', 2601000, 'Lampe2'],
Astro: {
_default:'Dawn', _all: { _onEnter: 'printme?' },
Nadir:'astro:nadir', NightEnd:'astro:nightEnd', NauticalDawn:'astro:nauticalDawn', Dawn:'astro:dawn',
Sunrise:'astro:sunrise', SunriseEnd:'astro:sunriseEnd', GoldenHourEnd:'astro:goldenHourEnd', SolarNoon:'astro:solarNoon',
GoldenHour:'astro:goldenHour', SunsetStart:'astro:sunsetStart', Sunset:'astro:sunset', Dusk:'astro:dusk',
NauticalDusk:'astro:nauticalDusk', Night:'astro:night',
},
},
variables: {
helligkeit: ["hm-rpc.0.NEQ0119725.1.BRIGHTNESS", "hm-rpc.0.NEQ0119727.1.BRIGHTNESS",(val,that,st) => (that.list[0] + that.list[1])/ 5],
counter: ['every:17s', (last) => 1+last>1000 ? 0 : 1+last],
},
events: {
toggle: 'every:10s',
pir: 'rpi2.0.gpio.19.state',
tasteraus: "pir",
tasterein: 'every:17s',
tastertoggle: 'every:10s',
every15: 'every:15s',
amMorgen: "06:00",
Sonnenaufgang20: 'astro:dusk +20',
everyminute2ndsecond: "::2",
},
actions: {
printme: (val,that,st) => _D(printme: ${val} from ${st}
),
led: '!rpi2.0.gpio.26.state',
Lampe:"led",
led2:'rpi2.0.gpio.25.state',
"e:pir": ["led+",'led2-','wait:2000','printme=pir'],
"e:pir2": ['led-','led2+','printme=pir2'],
"e:6,12,19:": 'pool.pumpe+', // Pool Pumpe soll jeden Tag 2x2h und ein mal 3h laufen
"e:8,14,22:": 'pool.pumpe-', // um 6h, 12h und 19h
},
};Hoffe ihr könnt was damit anfangen! Gruß
-
Hallo Mitsammen!
Habe den Code im 1. Post verändert um nicht unterschiedliche Syntax zu verwenden.
Es war früher bei Events z.B.: 'every:17s'
und bei Aktionen: 'wait=1000'
und bei _timeout: '1000|aus'
Nun ist es:
und bei Aktionen: 'wait:1000'
und bei _timeout: 'aus:1000'
Der Grund ist einfach. Ich wollte nur ein Zeichen verwenden damit man nicht nachdenken muss was man wo verwendet. Alles verwendet den ':' und damit ist ein wait=1000 als wait-Action oder Variable auf 1000 zu setzen von 'wait:1000' als Action 1000ms zu warten eindeutig unterscheidbar.
Übrigens, 'wait: 1000' würde auch funktionieren, genauso wie 'every: 17s', aber 'wait :1000' und 'every :17s' nicht!
Damit ist auch eindeutig dass der ':' nicht in Namen von Aktionen, Variablen oder States verwendet werden sollte.
-
So, alle Erläuterungen angeführt aber immer noch nicht fertig
Ich wollte eigentlich diese Funktion in einen Adapter verfrachten, scheiterte aber daran dass man ja einen Javascript-Editor braucht um das Konfigurations-Objekt zu generieren.
Habe dabei 4 mögliche lösungen:
-
Das bleibt nur ein script
-
Das wird als getrenntes Verfahren (wie Javascript und Blocky) in den JS-Adapter integriert
-
Es wir ein eigener Adapter mit eigenem JS-Editor
-
Es wird ein eigener Adapter woe die Konfiguration in einem vis-Element oder in der Adapter-Konfiguration erfolgt
Für alle Lösungen außer der 1. hab ich ohne eure Hilde keine Ahnung wie das umgesetzt werden kann da mir das Wissen fehlt wie ich einen js-Editor implementieren kann u.s.w….
Egal, sollte eine Anregung sein und wenn ihr Interesse zeigt dann kann man ja mehr draus machen.
p.s.: Könnte helfen den Einstieg in ioBroker-Progammierung einfacher zu gestalten.
-
-
Hallo mitsammen!
Will euch in diesem und zukünftigen Posts mal zeigen was ich so mit der StateMaschine mache und wie es meine vorhergehenden scripts (teilweise aus diesem Forum) ersetzt.
Ich hatte z.B. vorher ein script das alle ppar Stunden bestimmte Dinge tat.
Eins davon war den Sonnenstandwinkel zu berechnen und in ein Objekt zu schreiben und ein anderes war alle LOWBAT-Zustände zu suchen und in ein String die id's zu stellen welche auf LOWBAT sind.
Mit der StateMachine schauen beide so aus:
variables: { sunpos: ['every:20m', () => (suncalc.getPosition(new Date(), jsadapter.native.latitude, jsadapter.native.longitude).altitude * 180 / Math.PI).toFixed(1)], lowbat: ['every:6h', () => pSeriesIn($('channel[state.id=*.LOWBAT]'),(a,x) => pGst(x[a]).then(o => o && o.val ? x[a] : null).catch(() => _PR())).then(x => x.filter(a => a).join(', '))], }, ````für die sunpos braucht man natürlich die Latitude/longitude im JS-Adapter und auch am Anfang:
const suncalc = require('suncalc'),
jsadapter = getObject("system.adapter.javascript.0"); // Instanz angeben auf dem die StateMachine läftin dem script. Das ermittelt alle 20 Minuten den Sonnenstand in ° vom Horizont und alle 6 Stunden welche 'LOWBAT' states aktiv sind! Diese paar Zeilen ersetzten ein script von fast 200 Zeilen. Zum lowbat eine kurze Erklärung: pSeries geht allen LOWBAT nach, wenn diese true sind wird der Name des id's zurückgegeben, sonst null, bei Fehler undefined. pSeries speichert alle zurückgegebenen Werte in einem Array das dann mittels .filter gefiltert werden wo dann nur die übrig bleiben die nicht null oder undefined (also strings) sind, diese werden dann mit ', ' zusammengehängt. Viel Erfolg mit euren Anwendungen! Werde noch andere meiner 'real-life' Beispiele bringen.
-
Hallo mitsammen!
Habe heute das script auf eine neuere Version upgedated:
! ```
// globale Funktionen FJ 12.3.2017 const util = require("util"), http = require("http"), https = require('https'); // https.get var debuglog = 'debug'; // default level von _D(), kann im script dann z.B. auf 'info' geschalten werden um _D() auch im Info-Level anzuzeigen function _T(i) {var t=typeof i; if(t==='object'){ if(Array.isArray(i)) t = 'array'; else if(i instanceof RegExp) t = 'regexp'; else if(i===null) t = 'null'; } else if(t==='number' && isNaN(i)) t='NaN'; return t;} function _O(obj,level) { return util.inspect(obj, false, level ? level-1 : 2, false).replace(/\n/g,' ');} // print object as string like node would do on console but remove new lines function _J(str) { try { return JSON.parse(str); } catch (e) { return {'error':'JSON Parse Error of:'+str}}} // convert a JSON string securely to an object function _D(str,val) { log(
debug: ${str},debuglog); return val !== undefined ? val : str; } // Write debug message in log, optionally return 2nd argument function _W(str,val) { log(
warn: ${str},'warn'); return val !== undefined ? val : str; } // Write warning message in log, optionally return 2nd argument function _I(str,ret) { log(str); return ret !== undefined ? ret : str; } // Write normal information to log, optionally return 2nd argument function _N(fun) { return setTimeout.apply(null,[fun,0].concat(Array.prototype.slice.call(arguments, 1))); } // execute fun after pending IO in next schedule keeping arguments function _E(test, str) { if (typeof test !== 'string' && !test) return; throw new Error(typeof test !== 'string' ? str : test); } function _PR(v) { return Promise.resolve(v); } function _PE(v) { return Promise.reject(v); } function idn(id) { // return object name name if it exist, otherwise return id. const obj = getObject(id); return ((obj && obj.common && obj.common.name) ? obj.common.name : id) + " "; } function pSeries(obj,promfn,delay) { // makes an call for each item 'of' obj to promfun(item) which need to return a promise. All Promises are executed therefore in sequence one after the other var p = Promise.resolve(); const nv = [], f = (k) => p = p.then(() => promfn(k).then(res => wait(delay || 0,nv.push(res)))); for(var item of obj) f(item); return p.then(() => nv); } function pSeriesIn(obj,promfn,delay) { // makes an call for each item 'in' obj to promfun(key,obj) which need to return a promise. All Promises are executed therefore in sequence one after the other var p = Promise.resolve(); const nv = [], f = k => p = p.then(() => promfn(k,obj).then(res => wait(delay || 0, nv.push(res)))); for(var item in obj) f(item); return p.then(() => nv); } function c2pP(f) {// turns a callback(err,data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((err, result) => (err && rej(err)) || res(result)); f.apply(this, args); }); }; } function c1pP(f) { // turns a callback(data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((result) => res(result)); f.apply(this, args); }); }; } function pRetry(nretry, fn, arg) { return fn(arg).catch(err => { if (nretry <= 0) throw err; return pRetry(nretry - 1, fn,arg); }); } // these functions retry a promise max nretry times or repeat a promise nrepeat times with calling fn(arg) as the function which needs to return a promise function pRepeat(nrepeat, fn, arg, r) { r = r || []; return fn(arg).then(x => (nrepeat <= 0 ? Promise.resolve(r) : pRepeat(nrepeat - 1, fn,arg, r, r.push(x)))); } function wait(time,arg) { return new Promise(res => parseInt(time)>=0 ? setTimeout(res,parseInt(time), arg) : res(arg))} // wait time (in ms) and then resolve promise with arg, returns a promise thich means the '.then()' will be executed after the delay function pGet(url,retry) { // get a web page either with http or https and return a promise for the data, could be done also with request but request is now an external package and http/https are part of nodejs. const fun = url.startsWith('https') ? https.get : http.get; return (new Promise((resolve,reject)=> { fun(url, (res) => { const statusCode = res.statusCode; const contentType = res.headers['content-type']; if (statusCode !== 200) { const error = new Error(
Request Failed. Status Code: ${statusCode}); res.resume(); // consume response data to free up memory return reject(error); } res.setEncoding('utf8'); var rawData = ''; res.on('data', (chunk) => rawData += chunk); res.on('end', () => resolve(rawData)); }).on('error', (e) => reject(e)); })).catch(err => { if (!retry) throw err; return wait(100,retry -1).then(a => pGet(url,a)); }); } const pExec = c2pP(exec), pCst = c1pP(createState), pSst = c2pP(setState), pGst = c2pP(getState); function pSubs(scriptname) { // Print subscriptions const subscriptions = getSubscriptions(); var subscriptionCount = 0; if (scriptname === undefined) scriptname = 'alle'; for (var dp in subscriptions) for (var s of subscriptions[dp]) if ((scriptname == 'alle') || (scriptname == s.name) || dp.match(scriptname) || s.name.match(scriptname)) _D(
on(${s.pattern.id}), im Script: ${s.name}, change: ${s.pattern.change}, logic: ${s.pattern.logic},subscriptionCount++); return _D(subscriptionCount < 1 ? "keine Subscrition enthalten. Filter: (" + scriptname +")" : 'Anzahl Subscriptions: ' + subscriptionCount + ' in javascript.' + instance + ', Filter: ("' + scriptname + '")',subscriptionCount); } function telegram(text) { _D(
Send '${text}' to Telegram!,pSst("telegram.0.communicate.response"/*Send text through telegram*/,text,false)); } // ================ StateMachine start---------------- function Setter(fun, min) { // fun = function returng a promise, min = minimal time in ms between next function call if (!(this instanceof Setter)) return new Setter(fun, min); if (typeof fun !=='function') throw Error('Invalid Argument - no function for Setter'); ! this._efun = fun; this._list = []; this._min = min || 20; this._enabled = false; return this; } ! Setter.stripEx = function(s) { return typeof s === 'string' ? (s.trim().startsWith('!') ? s.slice(1).trim() : s.trim()) : s; }; Setter.prototype.toString = function() { return
Setter(${this._min},${this._enabled},${this.length})=${this.length>0 ? this._list[0] : 'empty'}; }; Setter.prototype.clearall = function() { this._list = []; return this; }; Setter.prototype.add = function(id) { ! function execute (that) { if (that.length>0) wait(that._min) .then(() => that._list.length>0 ? that._efun.apply(null,that._list.shift()) : _PR()) .then(() => execute(that),() => execute(that)); } ! const args = Array.prototype.slice.call(arguments); if (this._enabled && (typeof id !== 'string' || !id.startsWith('!'))) { this._list.push(args); if (this._list.length === 1) execute(this); return this; } if (typeof id === 'string' && id.startsWith('!')) args[0]=id.slice(1); return this._efun.apply(null,args); }; ! Object.defineProperty(Setter.prototype, "min", { get: function() { return this._min; }, set: function(y) { this._min = y>5 ? y : 5;} }); Object.defineProperty(Setter.prototype, "length", { get: function() { return this._list.length; }, }); Object.defineProperty(Setter.prototype, "enabled", { get: function() { return this._enabled; }, set: function(y) { this._enabled = !! y;} }); ! function MState(options,afrom) { if (!(this instanceof MState)) return new Mstate(options,afrom); if(options===undefined || _T(options)!=='object') options = {val:options}; if (options.val !== undefined) this.val = options.val; if (options.ack !== undefined) this.ack = options.ack; if (options.ts !== undefined) this.ts = options.ts; else this.ts = Date.now(); if (options.lc !== undefined) this.lc = options.lc; if (options.from !== undefined) this.from = options.from; if (options.num !== undefined) this.num = options.num; if (options._obj !== undefined) this._obj = options._obj; if(typeof afrom === 'string') this.addFrom(afrom); return this; } ! MState.prototype.addFrom = function(from) { return (this.from = from + (this.from ? ' | ' + this.from : '')) ; }; MState.prototype.toString = function() { return
MS(${_T(this.val)=== 'symbol' ? this.val.toString() : this.val}, ${this.stime}, '${this.sfrom}', ${this.num !== undefined ? this.num : ''}); }; Object.defineProperty(MState.prototype, "sfrom", { get: function() { return this.from.length>52 ? this.from.slice(0,25)+' ... '+ this.from.slice(-25) : this.from; } }); Object.defineProperty(MState.prototype, "stime", { get: function() { var t = new Date(this.ts); const ts = t.toTimeString().slice(0,8); t = (''+t.getMilliseconds()); return ts +'.'+'0'.repeat(4-t.length)+t; } }); Object.defineProperty(MState.prototype, "value", { get: function(d) { return (this.val = (this.val !== undefined ? this.val : d)); }, set: function(y) { this.val = y;} }); Object.defineProperty(MState.prototype, "time", { get: function() { return this.ts; }, set: function(y) { this.val = y;} }); ! function SmBase(name,machines,cn) { const sm = cn === 'StateMachine'; this[SmBase.Name] = cn ? cn : 'SmBase'; _E(typeof name !== 'string',name + ' err: invalid name:'+_O(name,1)); this._name = name.trim(); _E(!(machines instanceof StateMachine),
${this[SmBase.Name]} err: invalid type of Machines: ${_O(machines,1)}); this[SmBase.Machine] = machines; if(!sm) { _E(machines._items.get(name),this[SmBase.Name] + ' err: Machine has already item "'+name+'"'); machines._items.set(name,this); } this._val = null; this._list = []; this._listeners = []; return this; } ! SmBase.instanz = "javascript." + instance + "."; SmBase.stSetter = new Setter(pSst,50); SmBase.setter = function(a,b,c) {return _PR(SmBase.stSetter.add(a,b,c).length);}; SmBase.printArgs = function (val) {return _D(
printArgs(${Array.prototype.slice.call(arguments)}));}; SmBase.toggleState = function(id) {return SmBase.mGst(id).then(st => st.notExist ? _PE() : SmBase.setter(id,!st.val));}; SmBase.readState = function(id) { return SmBase.mGst(id).then(st => st.notExist ? undefined : st.val);}; SmBase.mGst = function(id) { // Get an id or if nothing returned try to get 'SmBase.instanz'+id id = Setter.stripEx(id); return pGst(id) .then(x => x ? x : pGst(SmBase.instanz+id), x => pGst(SmBase.instanz+id)) .then(x => x ? x : _W(
Could not get state for ${_O(id)},null),x => _W(
Could not get state for ${_O(id)},null)); }; ! SmBase.Machine = Symbol('machine'); SmBase.Name = Symbol('name'); SmBase.ExitRun = Symbol('exitRun'); SmBase.Toggle = Symbol('toggle'); SmBase.Empty = Symbol('empty'); ! Object.defineProperty(SmBase.prototype, "machines", { get: function() { return this[SmBase.Machine]; } }); Object.defineProperty(SmBase.prototype, "type", { get: function() { return this[SmBase.Name]; } }); Object.defineProperty(SmBase.prototype, "name", { get: function() { return this._name; }, set: function(n) { this._name = n; } }); Object.defineProperty(SmBase.prototype, "val", { get: function() { return this._val; }, set: function(y) { this._val = y;} }); Object.defineProperty(SmBase.prototype, "sname", { get: function() { return this.machines.name + '.' + this.name; }, }); Object.defineProperty(SmBase.prototype, "items", { get: function() { return this.machines._items; }, }); Object.defineProperty(SmBase.prototype, "list", { get: function() { return this._list; }, }); Object.defineProperty(SmBase.prototype, "lname", { get: function() { return SmBase.instanz+this.sname; }, }); ! SmBase.prototype.run = function(st) { return _PR(_W('.run of SmBase should never happen!')); }; SmBase.prototype.toString = function() { return this.type+'('+this.name+')'; }; SmBase.prototype.getMachine = function(m) { return this.machines.getMachine(m); }; SmBase.prototype.getDebug = function(l) { return this.machines.getDebug(l); }; SmBase.prototype.getVariable = function(v) { return this.machines.getVariable(v); }; ! SmBase.prototype.execute = function(st) { const nst = new MState(st,this.name); // _D(
${this}.execute ${nst}); return this.run(nst) .then(r => r === SmBase.ExitRun ? _PR(null) : (nst.val = this.val, pSeries(this._listeners.filter((ni) => !(ni.item instanceof SmState) || ni.item.isActive), (l) => (nst.num=l.num,l.item.execute(nst)),-1))) .catch(e => _W(
${this}.execute catch: ${_O(e)})); }; ! SmBase.prototype.setItem = function(item,val,num) { item = this.items.get(item.trim()); if (!item) return; const nst = new MState({val:val,num:num,_noFun:true},'execute:'+item.name); _D(
${item}.executeItem ${nst}); return item.run(nst) .catch(e => _W(
${item}.execute catch: ${_O(e)})); }; ! SmBase.prototype.runAction = function(action,st) { var sa; switch(_T(action)) { case 'function': if(this.getDebug(2)) _D(
SmAction(${action}), from=${st}); return _PR(action(st.val,this,st)) .catch(e => _W(
${action}.runAction catch1: ${_O(e)})); case 'array': return pSeries(action,x => this.runAction(x,st),-1) .catch(e => _W(
${action}.runAction catch2: ${_O(e)})); case 'string': action = action.trim(); break; default: return _PR(_W(
runAction invalid action type: ${_T(action)}=${action}; st=${st})); } if(this.getDebug(2)) _D(
SmAction(${action}) st=${st}); var onoff = action.slice(-1), id = action.slice(0,-1).trim(), val = SmBase.Empty; // st.val === undefined ? SmBase.Empty : st.val; if((sa=action.match(/^\s*wait\:\s*(\d+)\s*$/))) return wait(parseInt(sa[1])); switch (onoff) { case '-': val = false; break; case '+': val = true; break; case '~': val = SmBase.Toggle; break; case '!': val = !st.val; break; case '?': val = st.val; break; default: sa = action.split('='); if (sa.length==2) { id = sa[0].trim(); val = sa[1].trim(); // _D(
runAction ${action} = ${val}, ${arg}, ${from},val = arg); } else id = action; break; } const nst = new MState(st); if (val !== SmBase.Empty) nst.num = val; val = nst.val = val === SmBase.Empty ? nst.val : val; sa = this.items.get(id); while (sa && sa instanceof SmAction && typeof sa._action === 'string') { id = sa._action.trim(); sa = this.items.get(id); } if (sa) { if (sa instanceof SmAction) { return sa.runAction(sa._action,nst) } if (sa instanceof SmState) { val = sa.name.split('.')[1]; sa = sa._machine; } if (sa instanceof SmMachine) { var stt = sa._states.get(val); nst.num = val; return stt ? sa.setState(nst) : (typeof val === 'boolean' ? sa.enable(val) : _PR(_W(
runAction Wrong action: '${action} State ${val} not found in ${sa}'))); } if (sa instanceof SmVariable || sa instanceof SmEvent) { if(val !== SmBase.Empty) nst.val = sa.val = val; nst.num = -1; return sa.execute(nst); } if (typeof sa._action === 'function') return _PR(sa._action(val === SmBase.Empty ? null : val, sa, st)) .catch(e => _W(
${action}.runAction catch3: ${_O(e)})); } if(this.getDebug(2)) _D(
Set ${_O(id,1)} to ${val.toString()}); if(id === '_debug') return _PR(this.machines._debug = val); return (val === SmBase.Toggle ? SmBase.toggleState(id) : SmBase.setter(id,val)) .catch(e => _W(
${action}.runAction catch4: ${_O(e)})); }; ! SmBase.prototype.addListener = function(what,num) { var options = {}, test = true, idd = what, t; // if (this.getDebug(2)) _D(
${this}.addListener for ${_O(what)} with ${num}); switch(_T(what)) { case 'array': for(t of what) this.addListener(t,num); return true; case 'string': idd = what.trim(); if (this.items.has(idd)) { var there = this.items.get(idd); _E( !there,
addListener Error: wanted to add wrong listeners ${this} to ${idd}); for (var l of there._listeners) if (l.item === this && l.num === num) return; return there._listeners.push({item:this, "num":num}); } if((t=idd.match(/^astro\:\s*([a-zA-Z]{4,})\s*(([\+\-])\s*(\d+))?$/))) { options = {astro:t[1]}; if(t[2]) options.shift = parseInt(t[3]+t[4]); } else if((t=idd.match(/^\s*every\:\s*(\d+)\s*(h|m?s?)$/))) { options = parseInt(t[1]) * (t[2]=='s' ? 1000 : (t[2]=='m' ? 60000 : (t[2]=='h' ? 3600000 : 1))); } else if(idd.match(/^((\d\d?\s*)(,\s*\d\d?\s*)*)|\*(\s*\:((\s*\d\d?\s*)(,\s*\d\d?\s*)*)|(\s*\*)){1,2}$/)) { t = idd.split(':'); options = {time: {}}; t.forEach((str,index,arr) => { str = str.trim(); var item = ['hour','minute','second'][index]; if (str == '*') return; if (str.indexOf(',')<0) return (options.time[item]=parseInt(str)); options.time[item]=str.split(',').map(x => parseInt(x.trim())); }); } else { while(idd.length>0 && test) { l = idd.slice(-1); t = idd; idd = t.slice(0,-1).trim(); switch(l) { case '-': options.val = false; break; case '+': options.val = true; break; case '!': options.change = 'ne'; break; case '~': options.change = 'all'; break; case '/r>: options.ack = 'false'; break; case '&': options.ack = 'true'; break; default: idd = t; test = false; break; } } if (idd.startsWith('/') && idd.endsWith('/')) idd = new RegExp(idd.slice(1,-1)); options.id = idd; } idd = options; break; case 'object': _E(!(what.id || what.name || what.time || what.astro),
addListener has wrong object to process: ${_O(what)}); idd = what; break; case 'regexp': idd = what; break; default: _E(true,
wrong SmVariable source-id ${_O(what)}); } var lname = _O(idd); const that = this; t =this.machines._ioevents.get(lname); if (!t) { t = []; this.machines._ioevents.set(lname,t); // _D(
CReateOnListenerIoBroker ${lname}=${_O(idd)}(${num})); if (this.getDebug(2)) _D(
${this}.addListener for ${_T(idd)+' '+_O(idd)} with ${num}); if (typeof idd === 'number') setInterval((obj) => pSeries(t, fu => fu('I'+idd),-1), idd); else on(idd,(obj) => pSeries(t, fu => fu(obj),-1)); } if (typeof idd === 'number') lname = what; t.push(function onIoBrokerEvent(obj) { const nst = new MState( obj && obj.state ? obj.state : obj,lname); if (nst.val === undefined) nst.val = nst.ts; if (obj && obj.oldState && obj.oldState.lc) nst.lc = obj.oldState.lc; nst._obj = obj; // _D(
event ${that} with ${_O(obj)}:${nst}); if (num!==undefined) nst.num = num; return that.execute(nst).catch(e => _W(
${this}.execute on event catch: ${_O(e)})); }); }; ! function StateMachine(options) { if (!(this instanceof StateMachine)) return new StateMachine(options); SmBase.call(this, options && typeof options.name === 'string' ? options.name.trim() : 'StateMachine',this,'StateMachine'); this._options = options || null; this._items = new Map(); this._debug = 0; this._ioevents = new Map(); ! if (options) this.init(options); return this; } util.inherits(StateMachine, SmBase); ! StateMachine.prototype._preInit = function(options,Fun) { _E(!options || !Fun,
StateMachine.preInit has no valid definition (${options}) or ${Fun}); for(var i in options) { var name = i.trim(); if (this._items.has(name)) { _W(
StateMachine item '${name}' already defined. 2nd definition ignored!); continue; } var option = options[i]; var item = new Fun(name,this); item._option = option; } }; ! StateMachine.prototype.init = function(options) { _E(!options,
Empty options in SateMachine.init(${_O(options)})); ! if (options.actions) this._preInit(options.actions,SmAction); if (options.events) this._preInit(options.events,SmEvent); if (options.variables) this._preInit(options.variables,SmVariable); if (options.machines) this._preInit(options.machines,SmMachine); ! if(options.name) this.name = options.name.trim(); if (options.debug !== undefined) this._debug = options.debug || 0; if (options.setdelay) { var s = parseInt(options.setdelay); s = !s || isNaN(s) ? 0 : s; SmBase.stSetter.min = s; SmBase.stSetter.enabled = s>0; } else SmBase.stSetter.enabled = false; _E(!this._items.size,'State machine includes no definitions, cannot start!'); if (this.getDebug(1)) _D(
StateMachione initialize with name '${this.name}', debug=${this._debug} and setter:${SmBase.stSetter}`);
var t = [];
for(var i of this._items.values())
t.push(i);return pSeries(t.reverse(),x => _PR(x.init(x._option)),1) .then(x => { const init = this._items.get('_init'); if (init && init instanceof SmAction) { return init.execute(new MState({val:'_init'},'_init')); } return x; }) .then(x => on({id: new RegExp('^'+SmBase.instanz+this.name+'\.'), change:'ne', ack:false, fromNe:'system.adapter.'+SmBase.instanz.slice(0,-1) },(obj) => { // _W('Command for StateMachine ' +obj.id+ ' auf '+_O(obj.state)); if (!obj || !obj.state) return; var id = obj.id, val = obj.state.val, from = obj.state.from, ie = id.endsWith('._enabled'), nst = new MState(obj.state); id = id.split('.').slice(-1)[0]; var m = this._items.get(id); if (m && m instanceof SmMachine && m.list[val]) { if(m.getDebug(2)) _I(`Command for StateMachine ${id} auf ${_O(obj.state)}`); nst.num = m.list[val]; return ie ? (m._enabled = val) : m.setState(nst); } else if(m && !ie && m instanceof SmVariable) { nst.val = m.val = val; return m.execute(nst); } return null; })).catch(e => _W(`${this}.init catch: ${_O(e)}`));
};
! StateMachine.prototype.getDebug = function(l) { return typeof l === 'number' ? this._debug>=(l>0 ? l : 1) : this._debug; };
! StateMachine.prototype.getMachine = function(machine) {
const m = this._items.get(machine.trim());
return m && m instanceof SmMachine ? m : undefined;
};
StateMachine.prototype.getActiveState = function(machine) {
const m = this.getMachine(machine);
return m && m._activeState ? m._activeState : undefined;
};
StateMachine.prototype.getVariable = function(variable) {
const v = this._items.get(variable);
return v && v.val !== undefined ? v.val : undefined;
};
! function SmMachine(name, machines) {
if (!(this instanceof SmMachine)) return new SmMachine(name, machines);
SmBase.call(this,name,machines,'SmMachine');
! this._fun = null;
this._activeState = null;
this._enabled = true;
this._states = new Map();
return this;
}
util.inherits(SmMachine, SmBase);
! SmMachine.prototype.init = function(option) {
switch(_T(option)) {
case 'function': this._fun = option; return true;
case 'array':
_E(option.length<=1 ,${this} array len too short ${_O(option)} !
);
const mf = this.items.get(option[0]);
_E(!mf || typeof mf._option !== 'function',${this} array len or no function ${_O(mf)} !
);
this._option = option = mf._option.apply(null,option.slice(1));
break;
case 'object': break;
default:
_W(SmMachine ${this.name} has wrong config ${_O(option)}
);
return false;
}
var def = null;
this._all = option._all;
for(var s in option) {
var sname = s.trim();
if (sname === '_all')
continue;
var soption = optionfunction SmState(name, machines, ini) {
if (!(this instanceof SmState)) return new SmState(name, machines, ini);
SmBase.call(this,name,machines,'SmState');! this._all = null;
this._machine = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmState, SmBase);
! Object.defineProperty(SmState.prototype, "isActive", {
get: function() { return this._machine._activeState === this; }
});
! SmState.prototype.addEvent = function(to, event) {
const m = this._machine;
to = to.trim();
const t = m._states.get(to);
// _D(${this}.addEvent '${event}' to state ${t} ${to} not existing in ${m.name}
,false);
if (!t) return _W(${this}.addEvent '${event}' to state ${t ? t : to} not existing in ${m.name}
,false);
return this.addListener(event,to);
};
! SmState.prototype.onExit = function(st) {
this._machine._activeState = null;
if (this._timeout)
clearTimeout(this._timeout);
this._timeout = null;
const nst = new MState(st,exit(${this.name})
);
return (this._onExit ? this.runAction(this._onExit,nst) : _PR())
.catch(e => _W(${this}.onExit catch: ${_O(e)}
));
};
! SmState.prototype.onChange = function(st) {
const nst = new MState(st,exit(${this.name})
);
nst.val = this.isActive;
return (this._onChange ? this.runAction(this._onChange,nst) : _PR())
.catch(e => _W(${this}.onExit catch: ${_O(e)}
));
};
! SmState.prototype.onEnter = function(st) {
const nst = new MState(st,enter(${this.name})
);
nst.val = this.name;
if (this._timer) {
if (this._timeout)
clearTimeout(this._timeout);
const tst = new MState(st);
tst.num = this._timerState; tst.from = '_timeout:'+this._timer;
this._timeout = setTimeout(function(that) {
that._timeout = null;
that._machine.setState(tst);
}, this._timer, this);
}
return (this._onEnter ? this.runAction(this._onEnter,nst) : _PR())
.then(x => this._machine._activeState = this)
.catch(e => _W(${this}.onEnter catch: ${_O(e)}
));
};
! SmState.prototype.execute = function(st) {
if (!this.isActive || !this._machine._enabled) // _W(${this}.execute '${_O(st)}'
);
return _PR(false);
return this._machine.setState(st)
.catch(e => _W(${this}.execute catch: ${_O(e)}
));
};
! SmState.prototype.init = function(option) {
var e;
if (_T(option)==='string') {
this._machine.addListener(option.trim(),this.name);
option = {};
}
if (this._machine._all) {
for(e in this._machine._all) {
var aa = this._machine._all[e];
switch(e) {
case '_onChange':
case '_onEnter':
case '_onExit':
option[e]=aa;
break;
default:
this.addEvent(e,aa);
}
}
}
const st = this._machine.name;
const k = this._shortname;
for(e in option) {
var ea = option[e];
switch(e) {
case '_timeout':
var t = ea.split(':');
_E(t.length!=2 || (parseInt(t[1])>0)===false,Timer in ${st}.${k} has incorrect definition '${ea}'
);
this._timer = parseInt(t[1]);
this._timeout = null;
this._timerState = t[0];
break;
case '_onChange':
case '_onEnter':
case '_onExit':
this[e] = ea;
break;
case '_default':
this._machine._activeState = this;
this._default = ea;
break;
default:
this.addEvent(e,ea);
break;
}
}
};
! function SmEvent(name, machines, ini) {
if (!(this instanceof SmEvent)) return new SmEvent(name, machines, ini);
SmBase.call(this,name,machines,'SmEvent');
this._fun = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmEvent, SmBase);
! SmEvent.prototype.init = function(option) {
if(_T(option)==='array') {
const f = option.slice(-1)[0];
var l = option;
if (typeof f === 'function') {
this._fun = f;
l = l.slice(0,-1);
}
if (l.length< 1) return _W(Event Definition Error: SmEvent has no funcion or no values to process: ${this}=${_O(l)}, ${_O(f)}
);
l.forEach((v,i) => (this.list.push(null),this.addListener(v,i)));
} else this.addListener(option);
};
! SmEvent.prototype.run = function(st) {
if (this._fun && st.num>=0 && st.num< this.list.length) {
this.list[st.num] = st.val;
if (this.getDebug(3)) _D(${this} ${st.num}=${st.val} ? ${st.sfrom}
);
}
return _PR(this._fun ? this._fun(this.val,this,st) : st.val).then(x => this.val = x)
.then(x => (!x && this._fun) ? SmBase.ExitRun : true )
.catch(e => _W(${this}.run catch: ${_O(e)}
));
};function SmAction(name, machines, ini) {
if (!(this instanceof SmAction)) return new SmAction(name, machines, ini);
SmBase.call(this,name,machines,'SmAction');
this._action = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmAction, SmBase);! SmAction.prototype.init = function variableInit(option) {
switch(_T(option)) {
case 'function':
case 'array': this._action = option; break;
case 'string': this._action = option.trim(); break;
default:
return _W(Action definition Err: Action needs a string, function, array or {id/val} object: ${_O(option)}
);
}
const ma = this.name.match(/^\se:\s(.+)$/);
if (ma)
this.addListener(ma[1].trim());
};SmAction.prototype.run = function(st) {
_E(!this._action,Action.execute: No action to execute!
);
return this.runAction(this._action,st)
.catch(e => _W(${this}.run catch: ${_O(e)}
));
}; // Line 900!!! // ================= StateMachine end ===================`
Habe auch das script im 1. Post upgedated. Die Änderungen sind einige Fehler wie z.B. Timerstates wurden nicht neu gestartet wenn sie noch nicht abgelaufen wurden und nochmal aktiviert wurden. Auch sind nicht alle Variablen/Aktionen im debug=3 angezeigt worden und eine Initialisierung wurde auch eingebaut.! Damit komm ich gleich zur Beschreibung der noch nicht erwähnten Features und Neuigkeiten anhand einiger meiner eigenen Anwendung (ca. die hälfte meiner Maschinen, Aktionen, ... mit etwas veränderten Namen).
!~~[code]~~// State Machene test and demos ! const suncalc = require('suncalc'), jsadapter = getObject("system.adapter.javascript.0"); ! const statemachine = new StateMachine(); // Die StateMachine wird leer kreiert debuglog = 'info'; // debuglog ist der Loglevel von debug messages, Wenn Adapter-loglevel größer oder gleich dann werden debug logs angezeigt ! wait(100) // Verzögerte Initialization und Subscriptions anzeigen wenn debug>=1 ist .then(() => statemachine.init(testMachine)) // Die StateMachine wird mit der Konfiguration initialisiert. Damit werden Objekte angelegt und die Event-Subscriptions gestartet .then(() => _I(
${statemachine.name} intitialized with ${SmBase.stSetter.enabled ? SmBase.stSetter.min +'ms' : 'no'} setterDelay on debuglevel ${statemachine._debug}), e => _W(
Err init: ${e})) .then(() => statemachine.getDebug(1) ? pSubs(name) : false) // Wenn der Debuglevel der statemachine >=1 ist dann werden die Subscriptions angezeigt .then(() => _I('subscriptions running'), err => _W(
Err finish ${_O(err)}`)) // test functions
;const testMachine = { // Die Definition der Maschinenlogik
debug: 3, // Debug-Level (0-3) der StateMachine, 0 = keine Info, 1 = STatechanges und einige wenige Meldungen, 2 & 3 mehr meldungen auch dann bei jedem event
setdelay:25, // Verzögerung in ms der ausgaben auf Objekte die nicht mit'!' am Anfang gekennzeichnet sind. Bei 0 oder Abwesenheit keine Verzögerung
machines: { // Definition der StaeMachines
ToggleTimer2(lampe, taus, tein, ttoggle, ttimer, ttimerlen, t2timer, t2timerlen) {
const m = { _all: { aus:taus, ein:tein, timer:ttimer},
ein: { aus: ttoggle, _onEnter:lampe+'+' },
aus: { ein: ttoggle, _onEnter: lampe+'-', _default:true },
timer: { aus: ttoggle, _timeout:'aus:'+(ttimerlen1000), _onEnter:lampe+'+'},
};
if (t2timer && t2timerlen) {
m.timer2 = { aus: ttoggle, _timeout:'aus:'+(t2timerlen1000), _onEnter:lampe+'+'};
m._all.timer2 = t2timer;
}
return m;
},
Azflicht: ['ToggleTimer2', 'tstazfl','tstazfu','tstazfo',[], 'bewegungAzDunkel', 300],
Kellerlicht: ['ToggleTimer2', 'idkl','idku','idko','schkl','bewko',15, 'bewku', 300],
Eingangslicht: ['ToggleTimer2', 'idegl',[],[],[], 'bewegungEgDunkel', 60],
},
events: {
hellEingang: "hm-rpc.0.Eingang.1.BRIGHTNESS~",
hellTerasse: "hm-rpc.0.Terasse.1.BRIGHTNESS~",
bewku : "hm-rpc.0.Kellerunten.3.MOTION~+",
bewko : "xs1.0.Actuators.KellerBewegungO~+",
schkl : "xs1.0.Actuators.KellerAbgangL~",
tstku : "/hm-rpc.0.KellerTaster.1.PRESS./~+",
tstko : "/hm-rpc.0.KellerTaster.2.PRESS./~+",
bewazf: "hm-rpc.0.AZBewegung.3.MOTION~+",
bewazf2: "xs1.0.Actuators.BewAZF2~+",
tstazfo: "/hm-rpc.0.AZTaster.1.PRESS./~+",
tstazfu: "/hm-rpc.0.AZTaster.2.PRESS./~+",
pirazf : "rpi2.0.gpio.19.state~+",
pirazfx : "rpi2.0.gpio.19.state!",
bewegungAzDunkel: ['bewazf','bewazf2','pirazf', (old, that) => that.getVariable('helligkeit')<59],
bewegungEgDunkel: ["hm-rpc.0.EgBewegung.1.MOTION~+", (old, that) => that.getVariable('helligkeit')<59],
allAus: "xs1.0.Actuators.AllesLichtAus+",
},
actions: {
allesLichtAus: ['kulicht+', 'Azflicht=aus', 'wzlicht-', 'Kellerlicht=aus', 'Eingangslicht=aus', 'Asrlicht=aus','Vzlicht=aus', 'wblicht-',
"couchtischLicht-", "terassenLicht-", "esstischLicht-", 'wait:60000', 'idkul-'],
tstazfl: "xs1.0.Actuators.LichtAZF",
idkl: "hm-rpc.0.Kellerlicht.1.STATE",
idegl: "xs1.0.Actuators.EingangsLi",
"e:allAus": 'allesLichtAus',
'e:pirazfx' : "!rpi2.0.gpio.26.state",
_init(arg1,that) { return pGst("hm-rpc.0.Terasse.1.BRIGHTNESS")
.then(x => that.setItem('helligkeit',x.val,1))
.then(() => pGst("hm-rpc.0.Eingang.1.BRIGHTNESS"))
.then(x => that.setItem('helligkeit',x.val,0))
.catch(e => _W(_init err ${e}
));
}
},
variables: {
helligkeit: ['hellEingang', 'hellTerasse', (val,that,st) => Math.round((that.list[0] + that.list[1])/ 5)],
sunpos: ['every:20m', () => (suncalc.getPosition(new Date(), jsadapter.native.latitude, jsadapter.native.longitude).altitude * 180 / Math.PI).toFixed(1)],
lowbat: ['every:6h', () => pSeriesIn($('channel[state.id=*.LOWBAT]'),(a,x) => pGst(x[a]).then(o => o && o.val ? x[a].slice(0,-7) : null).catch(() => _PR())).then(x => x.filter(a => a).join(', '))],
},
};
[/code]`! Die machines beinhalten eine Funktion (Makrodefinition) ToogleTimer2 welcher eine Statemachine erzeugt die entweder einen oder 2 timer beinhaltet. Neben den Timern kann man das jeweilige Licht, einen Ausschalter, Einschalter und Umschalter definieren.
! Wenn eines der beiden t2timer nicht angegeben werden wird der 2. timer nicht erzeugt. Die Zeit wird auch von ms auf Sekunden umgerechnet.
! Damit definiere ich mal 3 StateMachines, Azflicht, Kellerlicht und Eingangslicht. Das Azflicht hat keinen Umschalter und nur einen Timer der eine Minute läuft, auf den Event 'bewegungAzDunkel' komm ich dann noch zurück.
! Das Kellerlicht hat alles, der obere Bewegungsmelder schaltet nur 15s, das reicht um die Stiegen hinunterzugehen und den 2. Bewegungsmelder zu erreichen. Dieser schaltet 5min. Wenn man nun wieder heraufgeht kommt man natürlich wieder beim 1. Bewegungsmelder vorbei der dann wieder nach 15s das Licht ausschaltet. Im Kellervorraum+Aufgang wo das Licht angebracht ist ist es immer finster, also schaltet es immer.
! Im Azflicht hab ich mehere Bewegungsmelder (einer ist ein PIR am Raspi, einer ein FS20 und einer ein HomeMatic) und die schalten das Licht in Abhängigkeit der Helligkeit.
! Dazu dienen die Variable helligkeit und die Aktion bewegungAzDunkel und zwei 'BRIGHTNESS' messwerte von der Terasse und vom Eingang.
! Die Variable helligkeit berechnen die mittlere Helligkeit (jeder der Werte kann zwischen 0 und 255 liegen) und gibt nur ganze %-Zahlen aus. Wenn Variablen als Ereignisse verwendet werden lösen sie nur aus wenn sich ihr Wert ändert.
! Nun wird noch der Event bewegungAzDunkel definiert. Dieser hat die Bewegungsmelder für das Az als Eingang und eine Funktion am Ende. Die Funktion liest mit "(old, that) => that.getVariable('helligkeit')<59" den momentanen Wert der Variablen helligkeit aus und löst dann den Event nur aus wenn dieser Wert kleiner als 59(%) ist.
! Somit wird das Az-Licht, die Terasse, das Vorzimmer, .... nur automatisch eingeschaltet wenn es dunkel wird/ist.
! Übrigens, das script definiert auch einen allesLichtAus-Mechanismus für das Erdgeschoss. Ich habe im Vorraum und in der Küche Taster die alle Lichter ausschalten, aber das Küchenlicht einschaltet und es nach einer Minute ausschaltet. Da das Küchenlicht die Stiegen ins Obergeschoß, diese in den Keller und in den Vorraum mitbeleuchtet hilft es wenn ich am Aben nach oben zum Schlafzimmer gehen will aber oben kein Licht aufdrehen will um oben niemand zu stören und es reicht bis ich im Bad bin...
! Nun zu der Aktion '_init(arg1,that) { return pGst("hm-rpc.0.Terasse.1.BRIGHTNESS").....'
! Legt man eine Aktion mit dem Namen _init' an wird die ausgeführt bevor die anderen Events die von irgendwo im System kommen können, eingeschaltet werden.
! Ich habe oben erläutert wie ich die Helligkeit berechne und um bei der ersten Berechnung wenn eine Änderung auftritt beide Werte der Helligkeit zur Verfügung stehen lese ich die beiden Werte aus den ioBroker-Objekten und speichere sie in die Variable mittels der Funktion "that.setItem(variable/event,wert,index)". Damit kann ich sicher sein dass die Helligkeitsvariable initialisiert ist und es nicht Minuten dauert bis beide Sensoren neue Werte geliefert haben und bis dahin falsche Werte erzeugt werden. setItem führt dabei keine weiterreichenden Aktionen aus sondern speichert nur die Werte und das Ergebnis.
! p.s.: Das die lowbat-Variable hat sich auch leicht verändert um die '.LOWBAT'-Endungen wegzunehmen und das String dadurch kürzer werden zu lassen.
! Ich verwende alle gezeigten Definitionen mit ein paar Zusätzen. So werden noch Alarm- und Anwesenheits-States definiert, alle Bewegungsmelder arbeiten im Alarm- und Nachtmodus auch als Alarmmelder (seit wir keine Katze mehr haben), etliche Zeitschalter für das Pool und Gartenbewässerung mit zusätzlichen Sensoren u.s.w....
! Ich habe jetzt nur noch ein script laufen welches die Raspi-cam mit 'motion' steuert und bei Alarm oder Bewegung einen Film erzeugt und ein Bild per Telegram ans Handy schickt, sonst hab ich alles mittels der StateMachine abgedeckt und auch den node-Red-Adapter deinstalliert.
! Da die StateMachine jeden Event nur einmal definiert, auch wenn er intern auch 5x verwendet wird (also wenn z.B. ein Taster an 5 verscheidenen Stellen einen Event mit '~+' erzeugen kann wird dafür nur eine 'on(...)' verwendet), so sind die Ressourcen geschont.
! Übrigens, bin von MySQL auf Postgre umgestiegen da dies auch wesentlich ressourcen schonender ist, ich speichere ca 35 Sensorpunkte und komme auf über 2000 Werte/Tag neu.
! Nach dieser Kur hab ich weder Swap-File-Nutzung am Pi und auch immer 150-200MB (>=20%) freien Speicher.
! Viel Spaß mit euren Anwendeungen! Und danke für eventuelle Bug-Reports oder Feedback.[/i][/i][/i] -
Hallo mitsammen!
Habe eine neue Version der StateMachine im ersten Eintrag und hier drunter gepostet:
! ```
// StateMachine v1.02 globale Funktionen FJ const util = require("util"), http = require("http"), https = require('https'); // https.get var debuglog = 'debug'; // default level von _D(), kann im script dann z.B. auf 'info' geschalten werden um _D() auch im Info-Level anzuzeigen function _T(i) {var t=typeof i; if(t==='object'){ if(Array.isArray(i)) t = 'array'; else if(i instanceof RegExp) t = 'regexp'; else if(i===null) t = 'null'; } else if(t==='number' && isNaN(i)) t='NaN'; return t;} function _O(obj,level) { return util.inspect(obj, false, level ? level-1 : 2, false).replace(/\n/g,' ');} // print object as string like node would do on console but remove new lines function _J(str) { try { return JSON.parse(str); } catch (e) { return {'error':'JSON Parse Error of:'+str}}} // convert a JSON string securely to an object function _D(str,val) { log(
debug: ${str},debuglog); return val !== undefined ? val : str; } // Write debug message in log, optionally return 2nd argument function _W(str,val) { log(
warn: ${str},'warn'); return val !== undefined ? val : str; } // Write warning message in log, optionally return 2nd argument function _I(str,ret) { log(str); return ret !== undefined ? ret : str; } // Write normal information to log, optionally return 2nd argument function _N(fun) { return setTimeout.apply(null,[fun,0].concat(Array.prototype.slice.call(arguments, 1))); } // execute fun after pending IO in next schedule keeping arguments function _E(test, str) { if (typeof test !== 'string' && !test) return; throw new Error(typeof test !== 'string' ? str : test); } function _PR(v) { return Promise.resolve(v); } function _PE(v) { return Promise.reject(v); } function idn(id) { // return object name name if it exist, otherwise return id. const obj = getObject(id); return ((obj && obj.common && obj.common.name) ? obj.common.name : id) + " "; } function pSeries(obj,promfn,delay) { // makes an call for each item 'of' obj to promfun(item) which need to return a promise. All Promises are executed therefore in sequence one after the other var p = Promise.resolve(); const nv = [], f = (k) => p = p.then(() => promfn(k).then(res => wait(delay || 0,nv.push(res)))); for(var item of obj) f(item); return p.then(() => nv); } function pSeriesIn(obj,promfn,delay) { // makes an call for each item 'in' obj to promfun(key,obj) which need to return a promise. All Promises are executed therefore in sequence one after the other var p = Promise.resolve(); const nv = [], f = k => p = p.then(() => promfn(k,obj).then(res => wait(delay || 0, nv.push(res)))); for(var item in obj) f(item); return p.then(() => nv); } function c2pP(f) {// turns a callback(err,data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((err, result) => (err && rej(err)) || res(result)); f.apply(this, args); }); }; } function c1pP(f) { // turns a callback(data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((result) => res(result)); f.apply(this, args); }); }; } function pRetry(nretry, fn, arg) { return fn(arg).catch(err => { if (nretry <= 0) throw err; return pRetry(nretry - 1, fn,arg); }); } // these functions retry a promise max nretry times or repeat a promise nrepeat times with calling fn(arg) as the function which needs to return a promise function pRepeat(nrepeat, fn, arg, r) { r = r || []; return fn(arg).then(x => (nrepeat <= 0 ? Promise.resolve(r) : pRepeat(nrepeat - 1, fn,arg, r, r.push(x)))); } function wait(time,arg) { return new Promise(res => parseInt(time)>=0 ? setTimeout(res,parseInt(time), arg) : res(arg))} // wait time (in ms) and then resolve promise with arg, returns a promise thich means the '.then()' will be executed after the delay function pGet(url,retry) { // get a web page either with http or https and return a promise for the data, could be done also with request but request is now an external package and http/https are part of nodejs. const fun = url.startsWith('https') ? https.get : http.get; return (new Promise((resolve,reject)=> { fun(url, (res) => { const statusCode = res.statusCode; const contentType = res.headers['content-type']; if (statusCode !== 200) { const error = new Error(
Request Failed. Status Code: ${statusCode}); res.resume(); // consume response data to free up memory return reject(error); } res.setEncoding('utf8'); var rawData = ''; res.on('data', (chunk) => rawData += chunk); res.on('end', () => resolve(rawData)); }).on('error', (e) => reject(e)); })).catch(err => { if (!retry) throw err; return wait(100,retry -1).then(a => pGet(url,a)); }); } const pExec = c2pP(exec), pCst = c1pP(createState), pSst = c2pP(setState), pGst = c2pP(getState); function pSubs(scriptname) { // Print subscriptions const subscriptions = getSubscriptions(); var subscriptionCount = 0; if (scriptname === undefined) scriptname = 'alle'; for (var dp in subscriptions) for (var s of subscriptions[dp]) if ((scriptname == 'alle') || (scriptname == s.name) || dp.match(scriptname) || s.name.match(scriptname)) _D(
on(${s.pattern.id}), im Script: ${s.name}, change: ${s.pattern.change}, logic: ${s.pattern.logic},subscriptionCount++); return _D(subscriptionCount < 1 ? "keine Subscrition enthalten. Filter: (" + scriptname +")" : 'Anzahl Subscriptions: ' + subscriptionCount + ' in javascript.' + instance + ', Filter: ("' + scriptname + '")',subscriptionCount); } function telegram(text) { _D(
Send '${text}' to Telegram!,pSst("telegram.0.communicate.response"/*Send text through telegram*/,text,false)); } // ================ StateMachine start---------------- function Setter(fun, min) { // fun = function returng a promise, min = minimal time in ms between next function call if (!(this instanceof Setter)) return new Setter(fun, min); if (typeof fun !=='function') throw Error('Invalid Argument - no function for Setter'); ! this._efun = fun; this._list = []; this._current = null; this._min = min || 20; this._enabled = false; return this; } ! Setter.stripEx = function(s) { return typeof s === 'string' ? (s.trim().startsWith('!') ? s.slice(1).trim() : s.trim()) : s; }; Setter.prototype.toString = function() { return
Setter(${this._min},${this._enabled},${this.length})=${this.length>0 ? this._list[0] : 'empty'}; }; Setter.prototype.clearall = function() { this._list = []; return this; }; Setter.prototype.add = function(id) { ! function execute (that) { if (that.length>0 && !that._current) { that._current = that._list.shift(); that._efun.apply(null,that._current) .then(() => wait(that._min),e => e) .then(() => execute(that,that._current = null),() => execute(that,that._current = null)); } } ! const args = Array.prototype.slice.call(arguments); if (this._enabled && (typeof id !== 'string' || !id.startsWith('!'))) { this._list.push(args); if (this._list.length === 1) execute(this); return this; } if (typeof id === 'string' && id.startsWith('!')) args[0]=id.slice(1); return this._efun.apply(null,args); }; ! Object.defineProperty(Setter.prototype, "min", { get: function() { return this._min; }, set: function(y) { this._min = y>5 ? y : 5;} }); Object.defineProperty(Setter.prototype, "length", { get: function() { return this._list.length; }, }); Object.defineProperty(Setter.prototype, "enabled", { get: function() { return this._enabled; }, set: function(y) { this._enabled = !! y;} }); ! function MState(options,afrom) { if (!(this instanceof MState)) return new Mstate(options,afrom); if(options===undefined || _T(options)!=='object') options = {val:options}; if (options.val !== undefined) this.val = options.val; if (options.ack !== undefined) this.ack = options.ack; if (options.ts !== undefined) this.ts = options.ts; else this.ts = Date.now(); if (options.lc !== undefined) this.lc = options.lc; if (options.from !== undefined) this.from = options.from; if (options.num !== undefined) this.num = options.num; if (options._obj !== undefined) this._obj = options._obj; if(typeof afrom === 'string') this.addFrom(afrom); return this; } ! MState.prototype.addFrom = function(from) { return (this.from = from + (this.from ? ' | ' + this.from : '')) ; }; MState.prototype.toString = function() { return
MS(${_T(this.val)=== 'symbol' ? this.val.toString() : this.val}, ${this.stime}, '${this.sfrom}', ${this.num !== undefined ? this.num : ''}); }; Object.defineProperty(MState.prototype, "sfrom", { get: function() { return this.from.length>52 ? this.from.slice(0,25)+' ... '+ this.from.slice(-25) : this.from; } }); Object.defineProperty(MState.prototype, "stime", { get: function() { var t = new Date(this.ts); const ts = t.toTimeString().slice(0,8); t = (''+t.getMilliseconds()); return ts +'.'+'0'.repeat(4-t.length)+t; } }); Object.defineProperty(MState.prototype, "value", { get: function(d) { return (this.val = (this.val !== undefined ? this.val : d)); }, set: function(y) { this.val = y;} }); Object.defineProperty(MState.prototype, "time", { get: function() { return this.ts; }, set: function(y) { this.val = y;} }); ! function SmBase(name,machines,cn) { const sm = cn === 'StateMachine'; this[SmBase.Name] = cn ? cn : 'SmBase'; _E(typeof name !== 'string',name + ' err: invalid name:'+_O(name,1)); this._name = name.trim(); _E(!(machines instanceof StateMachine),
${this[SmBase.Name]} err: invalid type of Machines: ${_O(machines,1)}); this[SmBase.Machine] = machines; if(!sm) { _E(machines._items.get(name),this[SmBase.Name] + ' err: Machine has already item "'+name+'"'); machines._items.set(name,this); } this._val = null; this._list = []; this._listeners = []; return this; } ! SmBase.instanz = "javascript." + instance + "."; SmBase.stSetter = new Setter(pSst,50); SmBase.setter = function(a,b,c) {return _PR(SmBase.stSetter.add(a,b,c).length);}; SmBase.printArgs = function (val) {return _D(
printArgs(${Array.prototype.slice.call(arguments)}));}; SmBase.toggleState = function(id) {return SmBase.mGst(id).then(st => st.notExist ? _PE() : SmBase.setter(id,!st.val));}; SmBase.readState = function(id) { return SmBase.mGst(id).then(st => st.notExist ? undefined : st.val);}; SmBase.mGst = function(id) { // Get an id or if nothing returned try to get 'SmBase.instanz'+id id = Setter.stripEx(id); return pGst(id) .then(x => x ? x : pGst(SmBase.instanz+id), x => pGst(SmBase.instanz+id)) .then(x => x ? x : _W(
Could not get state for ${_O(id)},null),x => _W(
Could not get state for ${_O(id)},null)); }; ! SmBase.Machine = Symbol('machine'); SmBase.Name = Symbol('name'); SmBase.ExitRun = Symbol('exitRun'); SmBase.Toggle = Symbol('toggle'); SmBase.Empty = Symbol('empty'); ! Object.defineProperty(SmBase.prototype, "machines", { get: function() { return this[SmBase.Machine]; } }); Object.defineProperty(SmBase.prototype, "type", { get: function() { return this[SmBase.Name]; } }); Object.defineProperty(SmBase.prototype, "name", { get: function() { return this._name; }, set: function(n) { this._name = n; } }); Object.defineProperty(SmBase.prototype, "val", { get: function() { return this._val; }, set: function(y) { this._val = y;} }); Object.defineProperty(SmBase.prototype, "sname", { get: function() { return this.machines.name + '.' + this.name; }, }); Object.defineProperty(SmBase.prototype, "items", { get: function() { return this.machines._items; }, }); Object.defineProperty(SmBase.prototype, "list", { get: function() { return this._list; }, }); Object.defineProperty(SmBase.prototype, "lname", { get: function() { return SmBase.instanz+this.sname; }, }); ! SmBase.prototype.run = function(st) { return _PR(_W('.run of SmBase should never happen!')); }; SmBase.prototype.toString = function() { return this.type+'('+this.name+')'; }; SmBase.prototype.getMachine = function(m) { return this.machines.getMachine(m); }; SmBase.prototype.getDebug = function(l) { return this.machines.getDebug(l); }; SmBase.prototype.getVariable = function(v) { return this.machines.getVariable(v); }; ! SmBase.prototype.execute = function(st) { const nst = new MState(st,this.name); // _D(
${this}.execute ${nst}); return this.run(nst) .then(r => r === SmBase.ExitRun ? _PR(null) : (nst.val = this.val, pSeries(this._listeners.filter((ni) => !(ni.item instanceof SmState) || ni.item.isActive), (l) => (nst.num=l.num,l.item.execute(nst)),-1))) .catch(e => _W(
${this}.execute catch: ${_O(e)})); }; ! SmBase.prototype.setItem = function(item,val,num) { item = this.items.get(item.trim()); if (!item) return; const nst = new MState({val:val,num:num,_noFun:true},'execute:'+item.name); _D(
${item}.executeItem ${nst}); return item.run(nst) .catch(e => _W(
${item}.execute catch: ${_O(e)})); }; ! SmBase.prototype.runAction = function(action,st) { var sa; switch(_T(action)) { case 'function': if(this.getDebug(2)) _D(
SmAction(${action}), from=${st}); return _PR(action(st.val,this,st)) .catch(e => _W(
${action}.runAction catch1: ${_O(e)})); case 'array': return pSeries(action,x => this.runAction(x,st),-1) .catch(e => _W(
${action}.runAction catch2: ${_O(e)})); case 'string': action = action.trim(); break; default: return _PR(_W(
runAction invalid action type: ${_T(action)}=${action}; st=${st})); } if(this.getDebug(2)) _D(
SmAction(${action}) st=${st}); var onoff = action.slice(-1), id = action.slice(0,-1).trim(), val = SmBase.Empty; // st.val === undefined ? SmBase.Empty : st.val; if((sa=action.match(/^\s*wait\:\s*(\d+)\s*$/))) return wait(parseInt(sa[1])); switch (onoff) { case '-': val = false; break; case '+': val = true; break; case '~': val = SmBase.Toggle; break; case '!': val = !st.val; break; case '?': val = st.val; break; default: sa = action.split('='); if (sa.length==2) { id = sa[0].trim(); val = sa[1].trim(); // _D(
runAction ${action} = ${val}, ${arg}, ${from},val = arg); } else id = action; break; } const nst = new MState(st); if (val !== SmBase.Empty) nst.num = val; val = nst.val = val === SmBase.Empty ? nst.val : val; sa = this.items.get(id); while (sa && sa instanceof SmAction && typeof sa._action === 'string') { id = sa._action.trim(); sa = this.items.get(id); } if (sa) { if (sa instanceof SmAction) { return sa.runAction(sa._action,nst) } if (sa instanceof SmState) { val = sa.name.split('.')[1]; sa = sa._machine; } if (sa instanceof SmMachine) { var stt = sa._states.get(val); nst.num = val; return stt ? sa.setState(nst) : (typeof val === 'boolean' ? sa.enable(val) : _PR(_W(
runAction Wrong action: '${action} State ${val} not found in ${sa}'))); } if (sa instanceof SmVariable || sa instanceof SmEvent) { if(val !== SmBase.Empty) nst.val = sa.val = val; nst.num = -1; return sa.execute(nst); } if (typeof sa._action === 'function') return _PR(sa._action(val === SmBase.Empty ? null : val, sa, st)) .catch(e => _W(
${action}.runAction catch3: ${_O(e)})); } if(this.getDebug(2)) _D(
Set ${_O(id,1)} to ${val.toString()}); if(id === '_debug') return _PR(this.machines._debug = val); return (val === SmBase.Toggle ? SmBase.toggleState(id) : SmBase.setter(id,val)) .catch(e => _W(
${action}.runAction catch4: ${_O(e)})); }; ! SmBase.prototype.addListener = function(what,num) { var options = {}, test = true, idd = what, t; // if (this.getDebug(2)) _D(
${this}.addListener for ${_O(what)} with ${num}); switch(_T(what)) { case 'array': for(t of what) this.addListener(t,num); return true; case 'string': idd = what.trim(); if (this.items.has(idd)) { var there = this.items.get(idd); _E( !there,
addListener Error: wanted to add wrong listeners ${this} to ${idd}); for (var l of there._listeners) if (l.item === this && l.num === num) return; return there._listeners.push({item:this, "num":num}); } if((t=idd.match(/^astro\:\s*([a-zA-Z]{4,})\s*(([\+\-])\s*(\d+))?$/))) { options = {astro:t[1]}; if(t[2]) options.shift = parseInt(t[3]+t[4]); } else if((t=idd.match(/^\s*every\:\s*(\d+)\s*(h|m?s?)$/))) { options = parseInt(t[1]) * (t[2]=='s' ? 1000 : (t[2]=='m' ? 60000 : (t[2]=='h' ? 3600000 : 1))); } else if(idd.match(/^((\d\d?\s*)(,\s*\d\d?\s*)*)|\*(\s*\:((\s*\d\d?\s*)(,\s*\d\d?\s*)*)|(\s*\*)){1,2}$/)) { t = idd.split(':'); options = {time: {}}; t.forEach((str,index,arr) => { str = str.trim(); var item = ['hour','minute','second'][index]; if (str == '*') return; if (str.indexOf(',')<0) return (options.time[item]=parseInt(str)); options.time[item]=str.split(',').map(x => parseInt(x.trim())); }); } else { while(idd.length>0 && test) { l = idd.slice(-1); t = idd; idd = t.slice(0,-1).trim(); switch(l) { case '-': options.val = false; break; case '+': options.val = true; break; case '!': options.change = 'ne'; break; case '~': options.change = 'all'; break; case '/r>: options.ack = 'false'; break; case '&': options.ack = 'true'; break; default: idd = t; test = false; break; } } if (idd.startsWith('/') && idd.endsWith('/')) idd = new RegExp(idd.slice(1,-1)); options.id = idd; } idd = options; break; case 'object': _E(!(what.id || what.name || what.time || what.astro),
addListener has wrong object to process: ${_O(what)}); idd = what; break; case 'regexp': idd = what; break; default: _E(true,
wrong SmVariable source-id ${_O(what)}); } var lname = _O(idd); const that = this; t =this.machines._ioevents.get(lname); if (!t) { t = []; this.machines._ioevents.set(lname,t); // _D(
CReateOnListenerIoBroker ${lname}=${_O(idd)}(${num})); if (this.getDebug(2)) _D(
${this}.addListener for ${_T(idd)+' '+_O(idd)} with ${num}); if (typeof idd === 'number') setInterval((obj) => pSeries(t, fu => fu('I'+idd),-1), idd); else on(idd,(obj) => pSeries(t, fu => fu(obj),-1)); } if (typeof idd === 'number') lname = what; t.push(function onIoBrokerEvent(obj) { const nst = new MState( obj && obj.state ? obj.state : obj,lname); if (nst.val === undefined) nst.val = nst.ts; if (obj && obj.oldState && obj.oldState.lc) nst.lc = obj.oldState.lc; nst._obj = obj; // _D(
event ${that} with ${_O(obj)}:${nst}); if (num!==undefined) nst.num = num; return that.execute(nst).catch(e => _W(
${this}.execute on event catch: ${_O(e)})); }); }; ! function StateMachine(options) { if (!(this instanceof StateMachine)) return new StateMachine(options); SmBase.call(this, options && typeof options.name === 'string' ? options.name.trim() : 'StateMachine',this,'StateMachine'); this._options = options || null; this._items = new Map(); this._debug = 0; this._ioevents = new Map(); ! if (options) this.init(options); return this; } util.inherits(StateMachine, SmBase); ! StateMachine.prototype._preInit = function(options,Fun) { _E(!options || !Fun,
StateMachine.preInit has no valid definition (${options}) or ${Fun}); for(var i in options) { var name = i.trim(); if (this._items.has(name)) { _W(
StateMachine item '${name}' already defined. 2nd definition ignored!); continue; } var option = options[i]; var item = new Fun(name,this); item._option = option; } }; ! StateMachine.prototype.init = function(options) { _E(!options,
Empty options in SateMachine.init(${_O(options)})); ! if (options.actions) this._preInit(options.actions,SmAction); if (options.events) this._preInit(options.events,SmEvent); if (options.variables) this._preInit(options.variables,SmVariable); if (options.machines) this._preInit(options.machines,SmMachine); ! if(options.name) this.name = options.name.trim(); if (options.debug !== undefined) this._debug = options.debug || 0; if (options.setdelay) { var s = parseInt(options.setdelay); s = !s || isNaN(s) ? 0 : s; SmBase.stSetter.min = s; SmBase.stSetter.enabled = s>0; } else SmBase.stSetter.enabled = false; _E(!this._items.size,'State machine includes no definitions, cannot start!'); if (this.getDebug(1)) _D(
StateMachione initialize with name '${this.name}', debug=${this._debug} and setter:${SmBase.stSetter}`);
var t = [];
for(var i of this._items.values())
t.push(i);return pSeries(t.reverse(),x => _PR(x.init(x._option)),1) .then(x => { const init = this._items.get('_init'); if (init && init instanceof SmAction) { return init.execute(new MState({val:'_init'},'_init')); } return x; }) .then(x => on({id: new RegExp('^'+SmBase.instanz+this.name+'\.'), change:'ne', ack:false, fromNe:'system.adapter.'+SmBase.instanz.slice(0,-1) },(obj) => { // _W('Command for StateMachine ' +obj.id+ ' auf '+_O(obj.state)); if (!obj || !obj.state) return; var id = obj.id, val = obj.state.val, from = obj.state.from, ie = id.endsWith('._enabled'), nst = new MState(obj.state); id = id.split('.').slice(-1)[0]; var m = this._items.get(id); if (m && m instanceof SmMachine && m.list[val]) { if(m.getDebug(2)) _I(`Command for StateMachine ${id} auf ${_O(obj.state)}`); nst.num = m.list[val]; return ie ? (m._enabled = val) : m.setState(nst); } else if(m && !ie && m instanceof SmVariable) { nst.val = m.val = val; return m.execute(nst); } return null; })).catch(e => _W(`${this}.init catch: ${_O(e)}`));
};
! StateMachine.prototype.getDebug = function(l) { return typeof l === 'number' ? this._debug>=(l>0 ? l : 1) : this._debug; };
StateMachine.prototype.getMachine = function(machine) {
const m = this._items.get(machine.trim());
return m && m instanceof SmMachine ? m : undefined;
};
StateMachine.prototype.getActiveState = function(machine) {
const m = this.getMachine(machine);
return m && m._activeState ? m._activeState : undefined;
};
StateMachine.prototype.getVariable = function(variable) {
const v = this._items.get(variable);
return v && v.val !== undefined ? v.val : undefined;
};
! function SmMachine(name, machines) {
if (!(this instanceof SmMachine)) return new SmMachine(name, machines);
SmBase.call(this,name,machines,'SmMachine');
! this._fun = null;
this._activeState = null;
this._enabled = true;
this._states = new Map();
return this;
}
util.inherits(SmMachine, SmBase);
! SmMachine.prototype.init = function(option) {
switch(_T(option)) {
case 'function': this._fun = option; return true;
case 'array':
_E(option.length<=1 ,${this} array len too short ${_O(option)} !
);
const mf = this.items.get(option[0]);
_E(!mf || typeof mf._option !== 'function',${this} array len or no function ${_O(mf)} !
);
this._option = option = mf._option.apply(null,option.slice(1));
break;
case 'object': break;
default:
_W(SmMachine ${this.name} has wrong config ${_O(option)}
);
return false;
}
var def = null;
this._all = option._all;
for(var s in option) {
var sname = s.trim();
if (sname === '_all')
continue;
var soption = optionfunction SmState(name, machines, ini) {
if (!(this instanceof SmState)) return new SmState(name, machines, ini);
SmBase.call(this,name,machines,'SmState');! this._all = null;
this._machine = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmState, SmBase);
! Object.defineProperty(SmState.prototype, "isActive", {
get: function() { return this._machine._activeState === this; }
});
SmState.prototype.addEvent = function(to, event) {
const m = this._machine;
to = to.trim();
const t = m._states.get(to);
// _D(${this}.addEvent '${event}' to state ${t} ${to} not existing in ${m.name}
,false);
if (!t) return _W(${this}.addEvent '${event}' to state ${t ? t : to} not existing in ${m.name}
,false);
return this.addListener(event,to);
};
SmState.prototype.onExit = function(st) {
this._machine._activeState = null;
if (this._timeout)
clearTimeout(this._timeout);
this._timeout = null;
const nst = new MState(st,exit(${this.name})
);
return (this._onExit ? this.runAction(this._onExit,nst) : _PR())
.catch(e => _W(${this}.onExit catch: ${_O(e)}
));
};
SmState.prototype.onChange = function(st) {
var nst = new MState(st,_onState(${this.name})
);
nst.val = this.isActive;
return (this._onState ? this.runAction(this._onState,nst) : _PR())
.then(() => {
nst = new MState(st,_onNotState(${this.name})
);
nst.val = !this.isActive;}).then(() => this._onNotState ? this.runAction(this._onNotState,nst) : false) .catch(e => _W(`${this}.onStates catch: ${_O(e)}`));
};
SmState.prototype.onEnter = function(st) {
const nst = new MState(st,enter(${this.name})
);
nst.val = this.name;
if (this._timer) {
if (this._timeout)
clearTimeout(this._timeout);
const tst = new MState(st);
tst.num = this._timerState; tst.from = '_timeout:'+this._timer;
this._timeout = setTimeout(function(that) {
that._timeout = null;
that._machine.setState(tst);
}, this._timer, this);
}
return (this._onEnter ? this.runAction(this._onEnter,nst) : _PR())
.then(x => this._machine._activeState = this)
.catch(e => _W(${this}.onEnter catch: ${_O(e)}
));
};
SmState.prototype.execute = function(st) {
if (!this.isActive || !this._machine._enabled) // _W(${this}.execute '${_O(st)}'
);
return _PR(false);
return this._machine.setState(st)
.catch(e => _W(${this}.execute catch: ${_O(e)}
));
};! SmState.prototype.init = function(option) {
var e;
if (_T(option)==='string') {
this._machine.addListener(option.trim(),this.name);
option = {};
}
if (this._machine._all) {
for(e in this._machine._all) {
var aa = this._machine._all[e];
switch(e) {
case '_onState':
case '_onNotState':
case '_onEnter':
case '_onExit':
option[e]=aa;
break;
default:
this.addEvent(e,aa);
}
}
}
const st = this._machine.name;
const k = this._shortname;
for(e in option) {
var ea = option[e];
switch(e) {
case '_timeout':
var t = ea.split(':');
_E(t.length!=2 || (parseInt(t[1])>0)===false,Timer in ${st}.${k} has incorrect definition '${ea}'
);
this._timer = parseInt(t[1]);
this._timeout = null;
this._timerState = t[0];
break;
case '_onState':
case '_onNotState':
case '_onEnter':
case '_onExit':
this[e] = ea;
break;
case '_default':
this._machine._activeState = this;
this._default = ea;
break;
default:
this.addEvent(e,ea);
break;
}
}
};
! function SmEvent(name, machines, ini) {
if (!(this instanceof SmEvent)) return new SmEvent(name, machines, ini);
SmBase.call(this,name,machines,'SmEvent');
this._fun = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmEvent, SmBase);
! SmEvent.prototype.init = function(option) {
if(_T(option)==='array') {
const f = option.slice(-1)[0];
var l = option;
if (typeof f === 'function') {
this._fun = f;
l = l.slice(0,-1);
}
if (l.length< 1) return _W(Event Definition Error: SmEvent has no funcion or no values to process: ${this}=${_O(l)}, ${_O(f)}
);
l.forEach((v,i) => (this.list.push(null),this.addListener(v,i)));
} else this.addListener(option);
};
SmEvent.prototype.run = function(st) {
if (this._fun && st.num>=0 && st.num< this.list.length) {
this.list[st.num] = st.val;
if (this.getDebug(3)) _D(${this} ${st.num}=${st.val} ? ${st.sfrom}
);
}
return _PR(this._fun ? this._fun(this.val,this,st) : st.val).then(x => this.val = x)
.then(x => (!x && this._fun) ? SmBase.ExitRun : true )
.catch(e => _W(${this}.run catch: ${_O(e)}
));
};
function SmAction(name, machines, ini) {
if (!(this instanceof SmAction)) return new SmAction(name, machines, ini);
SmBase.call(this,name,machines,'SmAction');
this._action = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmAction, SmBase);
! SmAction.prototype.init = function variableInit(option) {
switch(_T(option)) {
case 'function':
case 'array': this._action = option; break;
case 'string': this._action = option.trim(); break;
default:
return _W(Action definition Err: Action needs a string, function, array or {id/val} object: ${_O(option)}
);
}
const ma = this.name.match(/^\se:\s(.+)$/);
if (ma)
this.addListener(ma[1].trim());
};SmAction.prototype.run = function(st) {
_E(!this._action,Action.execute: No action to execute!
);
return this.runAction(this._action,st)
.catch(e => _W(${this}.run catch: ${_O(e)}
));
}; // Line 900!!! // ================= StateMachine end ===================`! Was sich geändert hat:
! der Setter verarbeitet nun Signale sofort und wartet erst danach 'delay' ms um das nächste Signal zu verarbeiten. Dadurch wird die Ansprechzeit verbessert.
! In den States wurde der Befehl '_onChange' durch '_onState' und '_onNotState' ersetzt.
! Bei _onState wird die Aktion mit Wert true ausgeführt falls der State aktiv ist und sonst false. Bei _onNotSTate ist es umgekehrt.
! Wenn man eine Statemashcine baut die einen aus-Zustand hat und mehrere Ein-Zustände kann z.b. im aus-State mit _onNotState:'Verbraucher' der Verbraqucher angeschaltet werden wenn die Machine nicht im Aus-Zustand ist, und automatisch ausgeschaltet werden wenn sie auf aus geht. Das vereinfacht dann auch meinen Makro für die Tasten/Timer-Maschinen auf:
!~~[code]~~ ToggleTimer(lampe, taus, tein, ttoggle, ttimer, ttimerlen, t2timer, t2timerlen) { const m = { _all: { aus:taus, ein:tein, timer:ttimer}, aus: { ein: ttoggle, _onNotState:lampe, _default:true }, ein: { aus: ttoggle }, timer: { aus: ttoggle, _timeout:'aus:'+(ttimerlen*1000)}, }; if (t2timer && t2timerlen) { m.timer2 = { aus: ttoggle, _timeout:'aus:'+(t2timerlen*1000)}; m._all.timer2 = t2timer; } return m; }, [/code]
Lampe kann dan auch ein Array sein das mehrere Verbraucher schaltet.[/i][/i][/i] -
Hallo wieder!
Ein weiteres kurzes Update (auch auf erstem Post aber nicht dazwischen) der bugfix-code:
! ```
// globale StateMachine FJ v1.3 2017/03/26 const util = require("util"), http = require("http"), https = require('https'), suncalc = require('suncalc'), jsadapter = getObject("system.adapter.javascript.0"); // https.get var debuglog = 'debug'; // default level von _D(), kann im script dann z.B. auf 'info' geschalten werden um _D() auch im Info-Level anzuzeigen function _T(i) {var t=typeof i; if(t==='object'){ if(Array.isArray(i)) t = 'array'; else if(i instanceof RegExp) t = 'regexp'; else if(i===null) t = 'null'; } else if(t==='number' && isNaN(i)) t='NaN'; return t;} function _O(obj,level) { return util.inspect(obj, false, level ? level-1 : 2, false).replace(/\n/g,' ');} // print object as string like node would do on console but remove new lines function _J(str) { try { return JSON.parse(str); } catch (e) { return {'error':'JSON Parse Error of:'+str}}} // convert a JSON string securely to an object function _D(str,val) { log(
debug: ${str},debuglog); return val !== undefined ? val : str; } // Write debug message in log, optionally return 2nd argument function _W(str,val) { log(
warn: ${str},'warn'); return val !== undefined ? val : str; } // Write warning message in log, optionally return 2nd argument function _I(str,ret) { log(str); return ret !== undefined ? ret : str; } // Write normal information to log, optionally return 2nd argument function _N(fun) { return setTimeout.apply(null,[fun,0].concat(Array.prototype.slice.call(arguments, 1))); } // execute fun after pending IO in next schedule keeping arguments function _E(test, str) { if (typeof test !== 'string' && !test) return; throw new Error(typeof test !== 'string' ? str : test); } function _PR(v) { return Promise.resolve(v); } function _PE(v) { return Promise.reject(v); } function idn(id) { // return object name name if it exist, otherwise return id. const obj = getObject(id); return ((obj && obj.common && obj.common.name) ? obj.common.name : id) + " "; } function pSeries(obj,promfn,delay) { // makes an call for each item 'of' obj to promfun(item) which need to return a promise. All Promises are executed therefore in sequence one after the other var p = Promise.resolve(); const nv = [], f = (k) => p = p.then(() => promfn(k).then(res => wait(delay || 0,nv.push(res)))); for(var item of obj) f(item); return p.then(() => nv); } function pSeriesIn(obj,promfn,delay) { // makes an call for each item 'in' obj to promfun(key,obj) which need to return a promise. All Promises are executed therefore in sequence one after the other var p = Promise.resolve(); const nv = [], f = k => p = p.then(() => promfn(k,obj).then(res => wait(delay || 0, nv.push(res)))); for(var item in obj) f(item); return p.then(() => nv); } function c2pP(f) {// turns a callback(err,data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((err, result) => (err && rej(err)) || res(result)); f.apply(this, args); }); }; } function c1pP(f) { // turns a callback(data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((result) => res(result)); f.apply(this, args); }); }; } function pRetry(nretry, fn, arg) { return fn(arg).catch(err => { if (nretry <= 0) throw err; return pRetry(nretry - 1, fn,arg); }); } // these functions retry a promise max nretry times or repeat a promise nrepeat times with calling fn(arg) as the function which needs to return a promise function pRepeat(nrepeat, fn, arg, r) { r = r || []; return fn(arg).then(x => (nrepeat <= 0 ? Promise.resolve(r) : pRepeat(nrepeat - 1, fn,arg, r, r.push(x)))); } function wait(time,arg) { return new Promise(res => parseInt(time)>=0 ? setTimeout(res,parseInt(time), arg) : res(arg))} // wait time (in ms) and then resolve promise with arg, returns a promise thich means the '.then()' will be executed after the delay function pGet(url,retry) { // get a web page either with http or https and return a promise for the data, could be done also with request but request is now an external package and http/https are part of nodejs. const fun = url.startsWith('https') ? https.get : http.get; return (new Promise((resolve,reject)=> { fun(url, (res) => { const statusCode = res.statusCode; const contentType = res.headers['content-type']; if (statusCode !== 200) { const error = new Error(
Request Failed. Status Code: ${statusCode}); res.resume(); // consume response data to free up memory return reject(error); } res.setEncoding('utf8'); var rawData = ''; res.on('data', (chunk) => rawData += chunk); res.on('end', () => resolve(rawData)); }).on('error', (e) => reject(e)); })).catch(err => { if (!retry) throw err; return wait(100,retry -1).then(a => pGet(url,a)); }); } const pExec = c2pP(exec), pCst = c1pP(createState), pSst = c2pP(setState), pGst = c2pP(getState); function pSubs(scriptname) { // Print subscriptions const subscriptions = getSubscriptions(); var subscriptionCount = 0; if (scriptname === undefined) scriptname = 'alle'; for (var dp in subscriptions) for (var s of subscriptions[dp]) if ((scriptname == 'alle') || (scriptname == s.name) || dp.match(scriptname) || s.name.match(scriptname)) _D(
on(${s.pattern.id}), im Script: ${s.name}, change: ${s.pattern.change}, logic: ${s.pattern.logic},subscriptionCount++); return _D(subscriptionCount < 1 ? "keine Subscrition enthalten. Filter: (" + scriptname +")" : 'Anzahl Subscriptions: ' + subscriptionCount + ' in javascript.' + instance + ', Filter: ("' + scriptname + '")',subscriptionCount); } function telegram(text) { _D(
Send '${text}' to Telegram!,pSst("telegram.0.communicate.response"/*Send text through telegram*/,text,false)); } // ================ StateMachine start---------------- function Setter(fun, min) { // fun = function returng a promise, min = minimal time in ms between next function call if (!(this instanceof Setter)) return new Setter(fun, min); if (typeof fun !=='function') throw Error('Invalid Argument - no function for Setter'); ! this._efun = fun; this._list = []; this._current = null; this._min = min || 20; this._enabled = false; return this; } ! Setter.stripEx = function(s) { return typeof s === 'string' ? (s.trim().startsWith('!') ? s.slice(1).trim() : s.trim()) : s; }; Setter.prototype.toString = function() { return
Setter(${this._min},${this._enabled},${this.length})=${this.length>0 ? this._list[0] : 'empty'}; }; Setter.prototype.clearall = function() { this._list = []; return this; }; Setter.prototype.add = function(id) { ! function execute (that) { if (that.length>0 && !that._current) { that._current = that._list.shift(); that._efun.apply(null,that._current) .then(() => wait(that._min),e => e) .then(() => execute(that,that._current = null),() => execute(that,that._current = null)); } } ! const args = Array.prototype.slice.call(arguments); if (this._enabled && (typeof id !== 'string' || !id.startsWith('!'))) { this._list.push(args); if (this._list.length === 1) execute(this); return this; } if (typeof id === 'string' && id.startsWith('!')) args[0]=id.slice(1); return this._efun.apply(null,args); }; ! Object.defineProperty(Setter.prototype, "min", { get: function() { return this._min; }, set: function(y) { this._min = y>5 ? y : 5;} }); Object.defineProperty(Setter.prototype, "length", { get: function() { return this._list.length; }, }); Object.defineProperty(Setter.prototype, "enabled", { get: function() { return this._enabled; }, set: function(y) { this._enabled = !! y;} }); ! function MState(options,afrom) { if (!(this instanceof MState)) return new Mstate(options,afrom); if(options===undefined || _T(options)!=='object') options = {val:options}; if (options.val !== undefined) this.val = options.val; if (options.ack !== undefined) this.ack = options.ack; if (options.ts !== undefined) this.ts = options.ts; else this.ts = Date.now(); if (options.lc !== undefined) this.lc = options.lc; if (options.from !== undefined) this.from = options.from; if (options.num !== undefined) this.num = options.num; if (options._obj !== undefined) this._obj = options._obj; if(typeof afrom === 'string') this.addFrom(afrom); return this; } ! MState.prototype.addFrom = function(from) { return (this.from = from + (this.from ? ' | ' + this.from : '')) ; }; MState.prototype.toString = function() { return
MS(${_T(this.val)=== 'symbol' ? this.val.toString() : this.val}, ${this.stime}, '${this.sfrom}', ${this.num !== undefined ? this.num : ''}); }; Object.defineProperty(MState.prototype, "sfrom", { get: function() { return this.from.length>52 ? this.from.slice(0,25)+' ... '+ this.from.slice(-25) : this.from; } }); Object.defineProperty(MState.prototype, "stime", { get: function() { var t = new Date(this.ts); const ts = t.toTimeString().slice(0,8); t = (''+t.getMilliseconds()); return ts +'.'+'0'.repeat(4-t.length)+t; } }); Object.defineProperty(MState.prototype, "value", { get: function(d) { return (this.val = (this.val !== undefined ? this.val : d)); }, set: function(y) { this.val = y;} }); Object.defineProperty(MState.prototype, "time", { get: function() { return this.ts; }, set: function(y) { this.val = y;} }); ! function SmBase(name,machines,cn) { const sm = cn === 'StateMachine'; this[SmBase.Name] = cn ? cn : 'SmBase'; _E(typeof name !== 'string',name + ' err: invalid name:'+_O(name,1)); this._name = name.trim(); _E(!(machines instanceof StateMachine),
${this[SmBase.Name]} err: invalid type of Machines: ${_O(machines,1)}); this[SmBase.Machine] = machines; if(!sm) { _E(machines._items.get(name),this[SmBase.Name] + ' err: Machine has already item "'+name+'"'); machines._items.set(name,this); } this._val = null; this._list = []; this._listeners = []; return this; } ! SmBase.instanz = "javascript." + instance + "."; SmBase.stSetter = new Setter(pSst,50); SmBase.setter = function(a,b,c) {return _PR(SmBase.stSetter.add(a,b,c).length);}; SmBase.printArgs = function (val) {return _D(
printArgs(${Array.prototype.slice.call(arguments)}));}; SmBase.toggleState = function(id) {return SmBase.mGst(id).then(st => st.notExist ? _PE() : SmBase.setter(id,!st.val));}; SmBase.readState = function(id) { return SmBase.mGst(id).then(st => st.notExist ? undefined : st.val);}; SmBase.mGst = function(id) { // Get an id or if nothing returned try to get 'SmBase.instanz'+id id = Setter.stripEx(id); return pGst(id) .then(x => x ? x : pGst(SmBase.instanz+id), x => pGst(SmBase.instanz+id)) .then(x => x ? x : _W(
Could not get state for ${_O(id)},null),x => _W(
Could not get state for ${_O(id)},null)); }; ! SmBase.Machine = Symbol('machine'); SmBase.Name = Symbol('name'); SmBase.ExitRun = Symbol('exitRun'); SmBase.Toggle = Symbol('toggle'); SmBase.Empty = Symbol('empty'); ! Object.defineProperty(SmBase.prototype, "machines", { get: function() { return this[SmBase.Machine]; } }); Object.defineProperty(SmBase.prototype, "type", { get: function() { return this[SmBase.Name]; } }); Object.defineProperty(SmBase.prototype, "name", { get: function() { return this._name; }, set: function(n) { this._name = n; } }); Object.defineProperty(SmBase.prototype, "val", { get: function() { return this._val; }, set: function(y) { this._val = y;} }); Object.defineProperty(SmBase.prototype, "sname", { get: function() { return this.machines.name + '.' + this.name; }, }); Object.defineProperty(SmBase.prototype, "items", { get: function() { return this.machines._items; }, }); Object.defineProperty(SmBase.prototype, "list", { get: function() { return this._list; }, }); Object.defineProperty(SmBase.prototype, "lname", { get: function() { return SmBase.instanz+this.sname; }, }); ! SmBase.prototype.run = function(st) { return _PR(_W('.run of SmBase should never happen!')); }; SmBase.prototype.toString = function() { return this.type+'('+this.name+')'; }; SmBase.prototype.getMachine = function(m) { return this.machines.getMachine(m); }; SmBase.prototype.getDebug = function(l) { return this.machines.getDebug(l); }; SmBase.prototype.getVariable = function(v) { return this.machines.getVariable(v); }; ! SmBase.prototype.execute = function(st) { const nst = new MState(st,this.name); // _D(
${this}.execute ${nst}); return this.run(nst) .then(r => r === SmBase.ExitRun ? _PR(null) : (nst.val = this.val, pSeries(this._listeners.filter((ni) => !(ni.item instanceof SmState) || ni.item.isActive), (l) => (nst.num=l.num,l.item.execute(nst)),-1))) .catch(e => _W(
${this}.execute catch: ${_O(e)})); }; ! SmBase.prototype.setItem = function(item,val,num) { item = this.items.get(item.trim()); if (!item) return; const nst = new MState({val:val,num:num,_noFun:true},'execute:'+item.name); _D(
${item}.executeItem ${nst}); return item.run(nst) .catch(e => _W(
${item}.execute catch: ${_O(e)})); }; ! SmBase.prototype.runAction = function(action,st) { var sa; switch(_T(action)) { case 'function': if(this.getDebug(2)) _D(
SmAction(${action}), from=${st}); return _PR(action(st.val,this,st)) .catch(e => _W(
${action}.runAction catch1: ${_O(e)})); case 'array': return pSeries(action,x => this.runAction(x,st),-1) .catch(e => _W(
${action}.runAction catch2: ${_O(e)})); case 'string': action = action.trim(); break; default: return _PR(_W(
runAction invalid action type: ${_T(action)}=${action}; st=${st})); } if(this.getDebug(2)) _D(
SmAction(${action}) st=${st}); var onoff = action.slice(-1), id = action.slice(0,-1).trim(), val = SmBase.Empty; // st.val === undefined ? SmBase.Empty : st.val; if((sa=action.match(/^\s*wait\:\s*(\d+)\s*$/))) return wait(parseInt(sa[1])); switch (onoff) { case '-': val = false; break; case '+': val = true; break; case '~': val = SmBase.Toggle; break; case '!': val = !st.val; break; case '?': val = st.val; break; default: sa = action.split('='); if (sa.length==2) { id = sa[0].trim(); val = sa[1].trim(); // _D(
runAction ${action} = ${val}, ${arg}, ${from},val = arg); } else id = action; break; } const nst = new MState(st); if (val !== SmBase.Empty) nst.num = val; val = nst.val = val === SmBase.Empty ? nst.val : val; sa = this.items.get(id); while (sa && sa instanceof SmAction && typeof sa._action === 'string') { id = sa._action.trim(); sa = this.items.get(id); } if (sa) { if (sa instanceof SmAction) { return sa.runAction(sa._action,nst) } if (sa instanceof SmState) { val = sa.name.split('.')[1]; sa = sa._machine; } if (sa instanceof SmMachine) { var stt = sa._states.get(val); nst.num = val; return stt ? sa.setState(nst) : (typeof val === 'boolean' ? sa.enable(val) : _PR(_W(
runAction Wrong action: '${action} State ${val} not found in ${sa}'))); } if (sa instanceof SmVariable || sa instanceof SmEvent) { if(val !== SmBase.Empty) nst.val = sa.val = val; nst.num = -1; return sa.execute(nst); } if (typeof sa._action === 'function') return _PR(sa._action(val === SmBase.Empty ? null : val, sa, st)) .catch(e => _W(
${action}.runAction catch3: ${_O(e)})); } if(this.getDebug(2)) _D(
Set ${_O(id,1)} to ${val.toString()}); if(id === '_debug') return _PR(this.machines._debug = val); return (val === SmBase.Toggle ? SmBase.toggleState(id) : SmBase.setter(id,val)) .catch(e => _W(
${action}.runAction catch4: ${_O(e)})); }; ! SmBase.prototype.addListener = function(what,num) { var options = {}, test = true, idd = what, t; // if (this.getDebug(2)) _D(
${this}.addListener for ${_O(what)} with ${num}); switch(_T(what)) { case 'array': for(t of what) this.addListener(t,num); return true; case 'string': idd = what.trim(); if (this.items.has(idd)) { var there = this.items.get(idd); _E( !there,
addListener Error: wanted to add wrong listeners ${this} to ${idd}); for (var l of there._listeners) if (l.item === this && l.num === num) return; return there._listeners.push({item:this, "num":num}); } if((t=idd.match(/^astro\:\s*([a-zA-Z]{4,})\s*(([\+\-])\s*(\d+))?$/))) { options = {astro:t[1]}; if(t[2]) options.shift = parseInt(t[3]+t[4]); } else if((t=idd.match(/^\s*every\:\s*(\d+)\s*(h|m?s?)$/))) { options = parseInt(t[1]) * (t[2]=='s' ? 1000 : (t[2]=='m' ? 60000 : (t[2]=='h' ? 3600000 : 1))); } else if(idd.match(/^((\d\d?\s*)(,\s*\d\d?\s*)*)|\*(\s*\:((\s*\d\d?\s*)(,\s*\d\d?\s*)*)|(\s*\*)){1,2}$/)) { t = idd.split(':'); options = {time: {}}; t.forEach((str,index,arr) => { str = str.trim(); var item = ['hour','minute','second'][index]; if (str == '*') return; if (str.indexOf(',')<0) return (options.time[item]=parseInt(str)); options.time[item]=str.split(',').map(x => parseInt(x.trim())); }); } else { while(idd.length>0 && test) { l = idd.slice(-1); t = idd; idd = t.slice(0,-1).trim(); switch(l) { case '-': options.val = false; break; case '+': options.val = true; break; case '!': options.change = 'ne'; break; case '~': options.change = 'all'; break; case '/r>: options.ack = 'false'; break; case '&': options.ack = 'true'; break; default: idd = t; test = false; break; } } if (idd.startsWith('/') && idd.endsWith('/')) idd = new RegExp(idd.slice(1,-1)); options.id = idd; } idd = options; break; case 'object': _E(!(what.id || what.name || what.time || what.astro),
addListener has wrong object to process: ${_O(what)}); idd = what; break; case 'regexp': idd = what; break; default: _E(true,
wrong SmVariable source-id ${_O(what)}); } var lname = _O(idd); const that = this; t =this.machines._ioevents.get(lname); if (!t) { t = []; this.machines._ioevents.set(lname,t); // _D(
CReateOnListenerIoBroker ${lname}=${_O(idd)}(${num})); if (this.getDebug(2)) _D(
${this}.addListener for ${_T(idd)+' '+_O(idd)} with ${num}); if (typeof idd === 'number') setInterval((obj) => pSeries(t, fu => fu('I'+idd),-1), idd); else on(idd,(obj) => pSeries(t, fu => fu(obj),-1)); } if (typeof idd === 'number') lname = what; t.push(function onIoBrokerEvent(obj) { const nst = new MState( obj && obj.state ? obj.state : obj,lname); if (nst.val === undefined) nst.val = nst.ts; if (obj && obj.oldState && obj.oldState.lc) nst.lc = obj.oldState.lc; nst._obj = obj; // _D(
event ${that} with ${_O(obj)}:${nst}); if (num!==undefined) nst.num = num; return that.execute(nst).catch(e => _W(
${this}.execute on event catch: ${_O(e)})); }); }; ! function StateMachine(options) { if (!(this instanceof StateMachine)) return new StateMachine(options); SmBase.call(this, options && typeof options.name === 'string' ? options.name.trim() : 'StateMachine',this,'StateMachine'); this._options = options || null; this._items = new Map(); this._debug = 0; this._ioevents = new Map(); ! if (options) this.init(options); return this; } util.inherits(StateMachine, SmBase); ! StateMachine.prototype._preInit = function(options,Fun) { _E(!options || !Fun,
StateMachine.preInit has no valid definition (${options}) or ${Fun}); for(var i in options) { var name = i.trim(); if (this._items.has(name)) { _W(
StateMachine item '${name}' already defined. 2nd definition ignored!); continue; } var option = options[i]; var item = new Fun(name,this); item._option = option; } }; ! StateMachine.prototype.init = function(options) { _E(!options,
Empty options in SateMachine.init(${_O(options)})); ! if (options.actions) this._preInit(options.actions,SmAction); if (options.events) this._preInit(options.events,SmEvent); if (options.variables) this._preInit(options.variables,SmVariable); if (options.machines) this._preInit(options.machines,SmMachine); ! if(options.name) this.name = options.name.trim(); if (options.debug !== undefined) this._debug = options.debug || 0; if (options.setdelay) { var s = parseInt(options.setdelay); s = !s || isNaN(s) ? 0 : s; SmBase.stSetter.min = s; SmBase.stSetter.enabled = s>0; } else SmBase.stSetter.enabled = false; _E(!this._items.size,'State machine includes no definitions, cannot start!'); if (this.getDebug(1)) _D(
StateMachione initialize with name '${this.name}', debug=${this._debug} and setter:${SmBase.stSetter}`);
var t = [];
for(var i of this._items.values())
t.push(i);return pSeries(t.reverse(),x => _PR(x.init(x._option)),1) .then(x => { const init = this._items.get('_init'); if (init && init instanceof SmAction) { return init.execute(new MState({val:'_init'},'_init')); } return x; }) .then(x => on({id: new RegExp('^'+SmBase.instanz+this.name+'\.'), change:'ne', ack:false, fromNe:'system.adapter.'+SmBase.instanz.slice(0,-1) },(obj) => { // _W('Command for StateMachine ' +obj.id+ ' auf '+_O(obj.state)); if (!obj || !obj.state) return; var id = obj.id, val = obj.state.val, from = obj.state.from, ie = id.endsWith('._enabled'), nst = new MState(obj.state); id = id.split('.').slice(-1)[0]; var m = this._items.get(id); if (m && m instanceof SmMachine && m.list[val]) { if(m.getDebug(2)) _I(`Command for StateMachine ${id} auf ${_O(obj.state)}`); nst.num = m.list[val]; return ie ? (m._enabled = val) : m.setState(nst); } else if(m && !ie && m instanceof SmVariable) { nst.val = m.val = val; return m.execute(nst); } return null; })).catch(e => _W(`${this}.init catch: ${_O(e)}`));
};
! StateMachine.prototype.getDebug = function(l) { return typeof l === 'number' ? this._debug>=(l>0 ? l : 1) : this._debug; };
StateMachine.prototype.getMachine = function(machine) {
const m = this._items.get(machine.trim());
return m && m instanceof SmMachine ? m : undefined;
};
StateMachine.prototype.getActiveState = function(machine) {
const m = this.getMachine(machine);
return m && m._activeState ? m._activeState : undefined;
};
StateMachine.prototype.getVariable = function(variable) {
const v = this._items.get(variable);
return v && v.val !== undefined ? v.val : undefined;
};
! function SmMachine(name, machines) {
if (!(this instanceof SmMachine)) return new SmMachine(name, machines);
SmBase.call(this,name,machines,'SmMachine');
! this._fun = null;
this._activeState = null;
this._enabled = true;
this._states = new Map();
return this;
}
util.inherits(SmMachine, SmBase);
! SmMachine.prototype.init = function(option) {
switch(_T(option)) {
case 'function': this._fun = option; return true;
case 'array':
_E(option.length<=1 ,${this} array len too short ${_O(option)} !
);
const mf = this.items.get(option[0]);
_E(!mf || typeof mf._option !== 'function',${this} array len or no function ${_O(mf)} !
);
this._option = option = mf._option.apply(null,option.slice(1));
break;
case 'object': break;
default:
_W(SmMachine ${this.name} has wrong config ${_O(option)}
);
return false;
}
var def = null;
this._all = option._all;
for(var s in option) {
var sname = s.trim();
if (sname === '_all')
continue;
var soption = optionfunction SmState(name, machines, ini) {
if (!(this instanceof SmState)) return new SmState(name, machines, ini);
SmBase.call(this,name,machines,'SmState');! this._all = null;
this._machine = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmState, SmBase);
! Object.defineProperty(SmState.prototype, "isActive", {
get: function() { return this._machine._activeState === this; }
});
SmState.prototype.addEvent = function(to, event) {
const m = this._machine;
to = to.trim();
const t = m._states.get(to);
// _D(${this}.addEvent '${event}' to state ${t} ${to} not existing in ${m.name}
,false);
if (!t) return _W(${this}.addEvent '${event}' to state ${t ? t : to} not existing in ${m.name}
,false);
return this.addListener(event,to);
};
SmState.prototype.onExit = function(st) {
this._machine._activeState = null;
if (this._timeout)
clearTimeout(this._timeout);
this._timeout = null;
const nst = new MState(st,exit(${this.name})
);
return (this._onExit ? this.runAction(this._onExit,nst) : _PR())
.catch(e => _W(${this}.onExit catch: ${_O(e)}
));
};
SmState.prototype.onChange = function(st) {
var nst = new MState(st,_onState(${this.name})
);
nst.val = this.isActive;
return (this._onState ? this.runAction(this._onState,nst) : _PR())
.then(() => {
nst = new MState(st,_onNotState(${this.name})
);
nst.val = !this.isActive;}).then(() => this._onNotState ? this.runAction(this._onNotState,nst) : false) .catch(e => _W(`${this}.onStates catch: ${_O(e)}`));
};
SmState.prototype.onEnter = function(st) {
const nst = new MState(st,enter(${this.name})
);
nst.val = this.name;
if (this._timer) {
if (this._timeout)
clearTimeout(this._timeout);
const tst = new MState(st);
tst.num = this._timerState; tst.from = '_timeout:'+this._timer;
this._timeout = setTimeout(function(that) {
that._timeout = null;
that._machine.setState(tst);
}, this._timer, this);
}
return (this._onEnter ? this.runAction(this._onEnter,nst) : _PR())
.then(x => this._machine._activeState = this)
.then(x => pSeries(this._listeners.filter((ni) => !(ni.item instanceof SmState) || ni.item.isActive), (l) => (nst.num=l.num,l.item.execute(nst)),-1))
.catch(e => _W(${this}.onEnter catch: ${_O(e)}
));
};
SmState.prototype.execute = function(st) {
if (!this.isActive || !this._machine._enabled) // _W(${this}.execute '${_O(st)}'
);
return _PR(false);
return this._machine.setState(st)
.catch(e => _W(${this}.execute catch: ${_O(e)}
));
};! SmState.prototype.init = function(option) {
var e;
if (_T(option)==='string') {
this._machine.addListener(option.trim(),this.name);
option = {};
}
if (this._machine._all) {
for(e in this._machine._all) {
var aa = this._machine._all[e];
switch(e) {
case '_onState':
case '_onNotState':
case '_onEnter':
case '_onExit':
option[e]=aa;
break;
default:
this.addEvent(e,aa);
}
}
}
const st = this._machine.name;
const k = this._shortname;
for(e in option) {
var ea = option[e];
switch(e) {
case '_timeout':
var t = ea.split(':');
_E(t.length!=2 || (parseInt(t[1])>0)===false,Timer in ${st}.${k} has incorrect definition '${ea}'
);
this._timer = parseInt(t[1]);
this._timeout = null;
this._timerState = t[0];
break;
case '_onState':
case '_onNotState':
case '_onEnter':
case '_onExit':
this[e] = ea;
break;
case '_default':
this._machine._activeState = this;
this._default = ea;
break;
default:
this.addEvent(e,ea);
break;
}
}
};
! function SmEvent(name, machines, ini) {
if (!(this instanceof SmEvent)) return new SmEvent(name, machines, ini);
SmBase.call(this,name,machines,'SmEvent');
this._fun = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmEvent, SmBase);
! SmEvent.prototype.init = function(option) {
if(_T(option)==='array') {
const f = option.slice(-1)[0];
var l = option;
if (typeof f === 'function') {
this._fun = f;
l = l.slice(0,-1);
}
if (l.length< 1) return _W(Event Definition Error: SmEvent has no funcion or no values to process: ${this}=${_O(l)}, ${_O(f)}
);
l.forEach((v,i) => (this.list.push(null),this.addListener(v,i)));
} else this.addListener(option);
};
SmEvent.prototype.run = function(st) {
if (this._fun && st.num>=0 && st.num< this.list.length) {
this.list[st.num] = st.val;
if (this.getDebug(3)) _D(${this} ${st.num}=${st.val} ? ${st.sfrom}
);
}
return _PR(this._fun ? this._fun(this.val,this,st) : st.val).then(x => this.val = x)
.then(x => (!x && this._fun) ? SmBase.ExitRun : true )
.catch(e => _W(${this}.run catch: ${_O(e)}
));
};
function SmAction(name, machines, ini) {
if (!(this instanceof SmAction)) return new SmAction(name, machines, ini);
SmBase.call(this,name,machines,'SmAction');
this._action = null;
if (ini) this.init(ini);
return this;
}
util.inherits(SmAction, SmBase);
! SmAction.prototype.init = function variableInit(option) {
switch(_T(option)) {
case 'function':
case 'array': this._action = option; break;
case 'string': this._action = option.trim(); break;
default:
return _W(Action definition Err: Action needs a string, function, array or {id/val} object: ${_O(option)}
);
}
const ma = this.name.match(/^\se:\s(.+)$/);
if (ma)
this.addListener(ma[1].trim());
};SmAction.prototype.run = function(st) {
_E(!this._action,Action.execute: No action to execute!
);
return this.runAction(this._action,st)
.catch(e => _W(${this}.run catch: ${_O(e)}
));
}; // Line 900!!! // ================= StateMachine end ===================`! Man kann nun einen 'state' als Event angeben und zum Beispiel bei den Actions sowas machen:
~~[code]~~'e:VorzimmerLicht.aus': 'eingangsLicht-'[/code]
Wenn immer die StateMachine 'VorzimmerLicht' auf den State 'aus' geschaltet wird wird auch das 'eingangsLicht' ausgeschaltet.
! Leider hat das vorher nicht funktioniert und das wurde korrigiert[/i][/i][/i] -
Ich möchte das Thema mal wieder nach oben bringen.
Ich glaube so eine Statemachine ist optimal zur Steuerung der Hausautomatisierung, die Szenen gehen ja schon in die Richtung…
Ich habe mal eine Verständnissfrage:
Mit diesem Skript möchte ich immer wenn sich der Wert von "fhem.0.MyHarmony.activity" ändert die Lampe "fhem.0.WZIT01.state" togglen.
const testMachine = { // Die Definition der Maschinenlogik events: { activity: {id: "fhem.0.MyHarmony.activity", change: "ne"}, }, actions: { "e:activity" : "fhem.0.WZIT01.state~" , }, };
Ich glaube das ist richtig so.
Aber was, wenn die Umschaltung nur bei Temperaturen unter 15° (mysensors.0.100.1_TEMP.V_TEMP) erfolgen soll? (Nur als Beispiel natürlich!!! :mrgreen: Im Produktiv System würde ich erst ab 16° schalten :mrgreen: )
Ludger
-
Eine Möglichkeit wäre:
Eine Variable zu erzeugen mittels:
variables: { v_temp: ['mysensors.0.100.1_TEMP.V_TEMP', (val,that,st) => that.list[0] ] }
und dann beim Event eine Bedingiung anzugeben:
activity: {id: "fhem.0.MyHarmony.activity", change: "ne"}, activity_ifTemp: ['activity', (old, that) => that.getVariable('v_temp')<15]
Dann wird activity_ifTemp nur ausgeführt wenn die Temperatur <15° ist.