Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Skripten / Logik
    4. Meine StateMachine

    NEWS

    • ioBroker@Smart Living Forum Solingen, 14.06. - Agenda added

    • ioBroker goes Matter ... Matter Adapter in Stable

    • Monatsrückblick - April 2025

    Meine StateMachine

    This topic has been deleted. Only users with topic management privileges can see it.
    • frankjoke
      frankjoke last edited by

      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:
      1489_2017-03-06_statemachines.png

      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 ….

      1 Reply Last reply Reply Quote 0
      • frankjoke
        frankjoke last edited by

        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 true

        
        Ich 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ß
        1 Reply Last reply Reply Quote 0
        • frankjoke
          frankjoke last edited by

          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.

          1 Reply Last reply Reply Quote 0
          • frankjoke
            frankjoke last edited by

            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.

            1 Reply Last reply Reply Quote 0
            • frankjoke
              frankjoke last edited by

              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äft

              in 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.
              1 Reply Last reply Reply Quote 0
              • frankjoke
                frankjoke last edited by

                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 = option

                ;
                if(typeof soption === 'string')
                soption = soption.trim();
                _E(_T(soption) !== 'object' && _T(soption) !== 'string',wrong state definition ${soption} in ${this});
                if (sname === '_default') {
                _E(typeof soption !== 'string',wrong _default state _definition in ${_O(soption)});
                def = soption;
                continue;
                }
                var state = new SmState(this.name + '.' + sname,this.machines);
                this._states.set(sname,state);
                this.list.push(sname);
                state._machine = this;
                state._option = soption;
                state._shortname = sname;
                state._all = this._all;
                }
                if (def)
                this._activeState = this._states.get(def);
                for(s of this._states.values())
                s.init(s._option);
                if (this.list.length<2)
                return _PR(_W(${this} err: not engough (<2) states!: ${_O(option)}));
                if (!this._activeState) {
                if (this.list.length>0)
                this._activeState = this._states.get(this.list[0]);
                _W(No '_default' state defined in ${this}, ${this._achiveState} defined as activeState!);
                }
                var mx = this.list.length-1,
                s = '', ns = '', ds = null, da = false;
                const mn = this.sname;
                for (var i in this.list) {
                s += ';'+ i + ':' + this.list[i];
                ns += ';' + i;
                }
                ns = ns.slice(1);
                s = s.slice(1);
                if (this.getDebug(2))
                _D(${this}.init create state ${mn} with ${s});
                this._enabled = true;
                var va = null;
                return SmBase.mGst(SmBase.instanz+mn)
                .then(x => {
                if (x.val !== undefined) {
                va = x.val;
                ds = this._states[va];
                da = true;
                }
                }, x => _PR())
                .then(() => SmBase.mGst(SmBase.instanz+mn+'._enabled'))
                .then(x => this._enabled = (x.val !== undefined) ? x.val : this._enabled, x => true)
                .then(x => pCst(mn, va, true, {type: 'number',name:SmBase.instanz+mn, unit: '',role: 'state',write:true,max:mx,min:0,states:s,desc:this.name+,${ns},${this.list.join(';')}},{}))
                .then(x => ds ? this.setState({num:ds,from:'init' ,always:da}) : null)
                .then(x => pCst(mn+'._enabled', this._enabled, true, {type: 'boolean',name:SmBase.instanz+mn+'._enabled', unit: '',role: 'state',write:true},{}))
                .catch(e => _W(${this}.init catch: ${_O(e)}));
                };
                ! SmMachine.prototype.run = function(st) {
                if (!st.num)
                return _PR();
                const num = st.num.split('.');
                if (num.length !== 2)
                return _PR();
                const nst = new MState(st);
                nst.num = num[1].trim();
                const m = this.getMachine(num[0]);
                return (m ? m.setState(st) : _PE())
                .catch(e => _W(${this}.run-setState catch: ${_O(e)}));
                };
                ! SmMachine.prototype.enable = function(bool) {
                this._enabled = bool;
                if(this.getDebug(1))
                _D(${this} will be ${bool?'enabled':'disabled'});
                return SmBase.setter(this.lname+'._enabled',{val:bool, ack:false},false)
                .catch(e => _W(${this}.enable catch: ${_O(e)}));
                };
                ! SmMachine.prototype.setState = function(st) {
                if (!this._enabled && !st.always)
                return _PR();
                var to = st.num.trim();
                if (to.indexOf('.')>0)
                to = to.split('.')[1].trim();
                const t = this._states.get(to);
                _E(!t,setState Error: ${this} has no state '${to}'!,this);
                const f = this._activeState;
                const fn = (f ? f._shortname : 'undefined');
                if (f===t && !st.always && !t._timer)
                return _PR(this.getDebug(1) ? _D(moveToState equal ${f} = ${st}) : null);
                _E(!t,moveToState in ${this} to ${to} invalid to: ${_O(t)});
                ! if (this.getDebug(1))
                _I(${this.name}.${to} was ${fn}: ${st.from});
                ! return ( f ? f.onExit(st).then(x => f.onChange(st)) : _PR())
                .then(x => t.onEnter(st))
                .then(x => t.onChange(st))
                .then(x => SmBase.setter(this.lname,this.list.indexOf(to),false))
                .catch(e => _W(${this}.setState catch: ${_O(e)}));
                };
                ! function SmVariable(name, machines, ini) {
                if (!(this instanceof SmVariable)) return new SmVariable(name, machines, ini);
                SmBase.call(this,name,machines,'SmVariable');
                this._fun = null;
                if (ini) this.init(ini);
                return this;
                }
                util.inherits(SmVariable, SmBase);
                ! SmVariable.prototype.init = function(option) {
                if(Array.isArray(option)) {
                const l = option.slice(0,-1);
                this._fun = option.slice(-1)[0];
                if (typeof this._fun !== 'function' || l.length<1)
                return _W(Variable Definition Error ${this}: SmVariable has no funcion or no values to process: ${_O(option)});
                for (var i in l) {
                this.addListener(l[i],i);
                this.list.push(null);
                }
                } else
                this.addListener(option);
                const mn = this.sname;
                if (this.getDebug(3))
                _D(${this}.init create variable ${mn});
                this.val = null;
                return SmBase.mGst(SmBase.instanz+mn)
                .then(x => this.val = x && x.val !== undefined ? x.val : null, x => null)
                .then(x => pCst(mn, undefined, true, {state:'state', role:'value', type: 'mixed', name:SmBase.instanz+mn, write:true, desc:'StateMachine Variable '+mn}))
                .then(x => pSst(mn, {val: this.val, ack: true}))
                .catch(e => _W(${this}.init catch: ${_O(e)}));
                };
                ! SmVariable.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});
                }
                const r = this._fun ? this._fun(this.val,this,st) : st.val;
                return (r instanceof Promise ? r : _PR(r))
                .then(x => {
                if (x !== this.val && x !== SmBase.Empty && x !== SmBase.ExitRun) {
                this.val = x;
                return pSst(this.sname, {val: this.val, ack: false})
                .then(x => this.getDebug(2) ? _D(${this} val = ${this.val},x) : x)
                }
                return _PR(SmBase.ExitRun);
                }).catch(e => _W(${this}.run catch: ${_O(e)}));
                };

                function 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:'+(t2timerlen
                1000), _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]

                1 Reply Last reply Reply Quote 0
                • frankjoke
                  frankjoke last edited by

                  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 = option

                  ;
                  if(typeof soption === 'string')
                  soption = soption.trim();
                  _E(_T(soption) !== 'object' && _T(soption) !== 'string',wrong state definition ${soption} in ${this});
                  if (sname === '_default') {
                  _E(typeof soption !== 'string',wrong _default state _definition in ${_O(soption)});
                  def = soption;
                  continue;
                  }
                  var state = new SmState(this.name + '.' + sname,this.machines);
                  this._states.set(sname,state);
                  this.list.push(sname);
                  state._machine = this;
                  state._option = soption;
                  state._shortname = sname;
                  state._all = this._all;
                  }
                  if (def)
                  this._activeState = this._states.get(def);
                  for(s of this._states.values())
                  s.init(s._option);
                  if (this.list.length<2)
                  return _PR(_W(${this} err: not engough (<2) states!: ${_O(option)}));
                  if (!this._activeState) {
                  if (this.list.length>0)
                  this._activeState = this._states.get(this.list[0]);
                  _W(No '_default' state defined in ${this}, ${this._achiveState} defined as activeState!);
                  }
                  var mx = this.list.length-1,
                  s = '', ns = '', ds = null, da = false;
                  const mn = this.sname;
                  for (var i in this.list) {
                  s += ';'+ i + ':' + this.list[i];
                  ns += ';' + i;
                  }
                  ns = ns.slice(1);
                  s = s.slice(1);
                  if (this.getDebug(2))
                  _D(${this}.init create state ${mn} with ${s});
                  this._enabled = true;
                  var va = null;
                  return SmBase.mGst(SmBase.instanz+mn)
                  .then(x => {
                  if (x.val !== undefined) {
                  va = x.val;
                  ds = this._states[va];
                  da = true;
                  }
                  }, x => _PR())
                  .then(() => SmBase.mGst(SmBase.instanz+mn+'._enabled'))
                  .then(x => this._enabled = (x.val !== undefined) ? x.val : this._enabled, x => true)
                  .then(x => pCst(mn, va, true, {type: 'number',name:SmBase.instanz+mn, unit: '',role: 'state',write:true,max:mx,min:0,states:s,desc:this.name+,${ns},${this.list.join(';')}},{}))
                  .then(x => ds ? this.setState({num:ds,from:'init' ,always:da}) : null)
                  .then(x => pCst(mn+'._enabled', this._enabled, true, {type: 'boolean',name:SmBase.instanz+mn+'._enabled', unit: '',role: 'state',write:true},{}))
                  .catch(e => _W(${this}.init catch: ${_O(e)}));
                  };
                  ! SmMachine.prototype.run = function(st) {
                  if (!st.num)
                  return _PR();
                  const num = st.num.split('.');
                  if (num.length !== 2)
                  return _PR();
                  const nst = new MState(st);
                  nst.num = num[1].trim();
                  const m = this.getMachine(num[0]);
                  return (m ? m.setState(st) : _PE())
                  .catch(e => _W(${this}.run-setState catch: ${_O(e)}));
                  };
                  SmMachine.prototype.enable = function(bool) {
                  this._enabled = bool;
                  if(this.getDebug(1))
                  _D(${this} will be ${bool?'enabled':'disabled'});
                  return SmBase.setter(this.lname+'._enabled',{val:bool, ack:false},false)
                  .catch(e => _W(${this}.enable catch: ${_O(e)}));
                  };
                  SmMachine.prototype.setState = function(st) {
                  if (!this._enabled && !st.always)
                  return _PR();
                  var to = st.num.trim();
                  if (to.indexOf('.')>0)
                  to = to.split('.')[1].trim();
                  const t = this._states.get(to);
                  _E(!t,setState Error: ${this} has no state '${to}'!,this);
                  const f = this._activeState;
                  const fn = (f ? f._shortname : 'undefined');
                  if (f===t && !st.always && !t._timer)
                  return _PR(this.getDebug(1) ? _D(moveToState equal ${f} = ${st}) : null);
                  _E(!t,moveToState in ${this} to ${to} invalid to: ${_O(t)});
                  ! if (this.getDebug(1))
                  _I(${this.name}.${to} was ${fn}: ${st.from});
                  ! return ( f ? f.onExit(st).then(x => f.onChange(st)) : _PR())
                  .then(x => t.onEnter(st))
                  .then(x => t.onChange(st))
                  .then(x => pSst(this.lname,this.list.indexOf(to),false))
                  .catch(e => _W(${this}.setState catch: ${_O(e)}));
                  };
                  ! function SmVariable(name, machines, ini) {
                  if (!(this instanceof SmVariable)) return new SmVariable(name, machines, ini);
                  SmBase.call(this,name,machines,'SmVariable');
                  this._fun = null;
                  if (ini) this.init(ini);
                  return this;
                  }
                  util.inherits(SmVariable, SmBase);
                  ! SmVariable.prototype.init = function(option) {
                  if(Array.isArray(option)) {
                  const l = option.slice(0,-1);
                  this._fun = option.slice(-1)[0];
                  if (typeof this._fun !== 'function' || l.length<1)
                  return _W(Variable Definition Error ${this}: SmVariable has no funcion or no values to process: ${_O(option)});
                  for (var i in l) {
                  this.addListener(l[i],i);
                  this.list.push(null);
                  }
                  } else
                  this.addListener(option);
                  const mn = this.sname;
                  if (this.getDebug(3))
                  _D(${this}.init create variable ${mn});
                  this.val = null;
                  return SmBase.mGst(SmBase.instanz+mn)
                  .then(x => this.val = x && x.val !== undefined ? x.val : null, x => null)
                  .then(x => pCst(mn, undefined, true, {state:'state', role:'value', type: 'mixed', name:SmBase.instanz+mn, write:true, desc:'StateMachine Variable '+mn}))
                  .then(x => pSst(mn, {val: this.val, ack: true}))
                  .catch(e => _W(${this}.init catch: ${_O(e)}));
                  };
                  ! SmVariable.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});
                  }
                  const r = this._fun ? this._fun(this.val,this,st) : st.val;
                  return (r instanceof Promise ? r : _PR(r))
                  .then(x => {
                  if (x !== this.val && x !== SmBase.Empty && x !== SmBase.ExitRun) {
                  this.val = x;
                  return pSst(this.sname, {val: this.val, ack: false})
                  .then(x => this.getDebug(2) ? _D(${this} val = ${this.val},x) : x)
                  }
                  return _PR(SmBase.ExitRun);
                  }).catch(e => _W(${this}.run catch: ${_O(e)}));
                  };

                  function 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]

                  1 Reply Last reply Reply Quote 0
                  • frankjoke
                    frankjoke last edited by

                    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 = option

                    ;
                    if(typeof soption === 'string')
                    soption = soption.trim();
                    _E(_T(soption) !== 'object' && _T(soption) !== 'string',wrong state definition ${soption} in ${this});
                    if (sname === '_default') {
                    _E(typeof soption !== 'string',wrong _default state _definition in ${_O(soption)});
                    def = soption;
                    continue;
                    }
                    var state = new SmState(this.name + '.' + sname,this.machines);
                    this._states.set(sname,state);
                    this.list.push(sname);
                    state._machine = this;
                    state._option = soption;
                    state._shortname = sname;
                    state._all = this._all;
                    }
                    if (def)
                    this._activeState = this._states.get(def);
                    for(s of this._states.values())
                    s.init(s._option);
                    if (this.list.length<2)
                    return _PR(_W(${this} err: not engough (<2) states!: ${_O(option)}));
                    if (!this._activeState) {
                    if (this.list.length>0)
                    this._activeState = this._states.get(this.list[0]);
                    _W(No '_default' state defined in ${this}, ${this._achiveState} defined as activeState!);
                    }
                    var mx = this.list.length-1,
                    s = '', ns = '', ds = null, da = false;
                    const mn = this.sname;
                    for (var i in this.list) {
                    s += ';'+ i + ':' + this.list[i];
                    ns += ';' + i;
                    }
                    ns = ns.slice(1);
                    s = s.slice(1);
                    if (this.getDebug(2))
                    _D(${this}.init create state ${mn} with ${s});
                    this._enabled = true;
                    var va = null;
                    return SmBase.mGst(SmBase.instanz+mn)
                    .then(x => {
                    if (x.val !== undefined) {
                    va = x.val;
                    ds = this._states[va];
                    da = true;
                    }
                    }, x => _PR())
                    .then(() => SmBase.mGst(SmBase.instanz+mn+'._enabled'))
                    .then(x => this._enabled = (x.val !== undefined) ? x.val : this._enabled, x => true)
                    .then(x => pCst(mn, va, true, {type: 'number',name:SmBase.instanz+mn, unit: '',role: 'state',write:true,max:mx,min:0,states:s,desc:this.name+,${ns},${this.list.join(';')}},{}))
                    .then(x => ds ? this.setState({num:ds,from:'init' ,always:da}) : null)
                    .then(x => pCst(mn+'._enabled', this._enabled, true, {type: 'boolean',name:SmBase.instanz+mn+'._enabled', unit: '',role: 'state',write:true},{}))
                    .catch(e => _W(${this}.init catch: ${_O(e)}));
                    };
                    ! SmMachine.prototype.run = function(st) {
                    if (!st.num)
                    return _PR();
                    const num = st.num.split('.');
                    if (num.length !== 2)
                    return _PR();
                    const nst = new MState(st);
                    nst.num = num[1].trim();
                    const m = this.getMachine(num[0]);
                    return (m ? m.setState(st) : _PE())
                    .catch(e => _W(${this}.run-setState catch: ${_O(e)}));
                    };
                    SmMachine.prototype.enable = function(bool) {
                    this._enabled = bool;
                    if(this.getDebug(1))
                    _D(${this} will be ${bool?'enabled':'disabled'});
                    return SmBase.setter(this.lname+'._enabled',{val:bool, ack:false},false)
                    .catch(e => _W(${this}.enable catch: ${_O(e)}));
                    };
                    SmMachine.prototype.setState = function(st) {
                    if (!this._enabled && !st.always)
                    return _PR();
                    var to = st.num.trim();
                    if (to.indexOf('.')>0)
                    to = to.split('.')[1].trim();
                    const t = this._states.get(to);
                    _E(!t,setState Error: ${this} has no state '${to}'!,this);
                    const f = this._activeState;
                    const fn = (f ? f._shortname : 'undefined');
                    if (f===t && !st.always && !t._timer)
                    return _PR(this.getDebug(1) ? _D(moveToState equal ${f} = ${st}) : null);
                    _E(!t,moveToState in ${this} to ${to} invalid to: ${_O(t)});
                    ! if (this.getDebug(1))
                    _I(${this.name}.${to} was ${fn}: ${st.from});
                    ! return ( f ? f.onExit(st).then(x => f.onChange(st)) : _PR())
                    .then(x => t.onEnter(st))
                    .then(x => t.onChange(st))
                    .then(x => pSst(this.lname,this.list.indexOf(to),false))
                    .catch(e => _W(${this}.setState catch: ${_O(e)}));
                    };
                    ! function SmVariable(name, machines, ini) {
                    if (!(this instanceof SmVariable)) return new SmVariable(name, machines, ini);
                    SmBase.call(this,name,machines,'SmVariable');
                    this._fun = null;
                    if (ini) this.init(ini);
                    return this;
                    }
                    util.inherits(SmVariable, SmBase);
                    ! SmVariable.prototype.init = function(option) {
                    if(Array.isArray(option)) {
                    const l = option.slice(0,-1);
                    this._fun = option.slice(-1)[0];
                    if (typeof this._fun !== 'function' || l.length<1)
                    return _W(Variable Definition Error ${this}: SmVariable has no funcion or no values to process: ${_O(option)});
                    for (var i in l) {
                    this.addListener(l[i],i);
                    this.list.push(null);
                    }
                    } else
                    this.addListener(option);
                    const mn = this.sname;
                    if (this.getDebug(3))
                    _D(${this}.init create variable ${mn});
                    this.val = null;
                    return SmBase.mGst(SmBase.instanz+mn)
                    .then(x => this.val = x && x.val !== undefined ? x.val : null, x => null)
                    .then(x => pCst(mn, undefined, true, {state:'state', role:'value', type: 'mixed', name:SmBase.instanz+mn, write:true, desc:'StateMachine Variable '+mn}))
                    .then(x => pSst(mn, {val: this.val, ack: true}))
                    .catch(e => _W(${this}.init catch: ${_O(e)}));
                    };
                    ! SmVariable.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});
                    }
                    const r = this._fun ? this._fun(this.val,this,st) : st.val;
                    return (r instanceof Promise ? r : _PR(r))
                    .then(x => {
                    if (x !== this.val && x !== SmBase.Empty && x !== SmBase.ExitRun) {
                    this.val = x;
                    return pSst(this.sname, {val: this.val, ack: false})
                    .then(x => this.getDebug(2) ? _D(${this} val = ${this.val},x) : x)
                    }
                    return _PR(SmBase.ExitRun);
                    }).catch(e => _W(${this}.run catch: ${_O(e)}));
                    };

                    function 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]

                    1 Reply Last reply Reply Quote 0
                    • L
                      lubeda last edited by

                      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

                      1 Reply Last reply Reply Quote 0
                      • frankjoke
                        frankjoke last edited by

                        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.

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post

                        Support us

                        ioBroker
                        Community Adapters
                        Donate

                        740
                        Online

                        31.7k
                        Users

                        79.8k
                        Topics

                        1.3m
                        Posts

                        2
                        12
                        3687
                        Loading More Posts
                        • Oldest to Newest
                        • Newest to Oldest
                        • Most Votes
                        Reply
                        • Reply as topic
                        Log in to reply
                        Community
                        Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                        The ioBroker Community 2014-2023
                        logo