@blackeagle998 sagte in Test Adapter iQontrol 1.3.x:
@s-bormann
Du bist uns definitiv einige Schritte / Meilen voraus 
- wie kriege ich von einem State nicht den Wert, sondern den dazugehörigen String (bspw. "Ampel rot")?
Beispiel im RAW:
states: {
"0": "Ampel rot",
"1": "Ampel gelb",
"2": "Ampel grün"
}
--> getState liefert hier ja nur 0, 1 oder 2.
Nein, ich bin Euch da nicht sehr weit voraus. Auch bei mir ist das viel abgucken und sehr viel try'n'error.
So habe ich das mit dem String (oder wie ich es bei mir intern genannt habe 'plainText') gar nicht bedacht. Habe deshalb die Rückmeldung für die postMessage-Kommunikation jetzt in der aktuellen Github-Version noch mal erweitert:
Jetzt wird nicht nur der nackte State geliefert, sondern es werden noch einige Daten (z.B. value, min, max, readonly etc.) aus dem zugehörigen Objekt des Datenpunktes geliefert - oder einfach gesagt, genau das, was iQontrol auch intern auswertet und sieht. Die Rückmeldung auf ein getState, getStateSubscribed, getWidgetState oder getWidgetStateSubscribed sieht jetzt so aus:
event.data.value = {
val: <value>,
unit: "<unit>",
plainText: "<clear text of val, for example taken from valuelist>",
min: <minimum>,
max: <maximum>,
valuelist: {<object with possible values and corresponding clear text>},
targetValues: {<target value list>},
ack: <true|false>,
readonly: <true|false>,
custom: {<object with custom settings>},
from: "<source of state>",
lc: <timestamp of last change>,
ts: <timestamp of last actualization>,
q: <quality of signal>,
role: "<role of state>",
type: "<string|number|boolean>"
}
- Nochmal die Frage, wie ich den getState Rückgabewert in eine andere Funktion als receivePostMessage bekomme.
Folgendes Beispiel soll ungefähr verdeutlichen worauf ich hinaus will:
function showBatteryGIF() {
let vacuum_state = sendPostMessage("getState", 'mihome-vacuum.0.info.state');
if (vacuum_state == 8){
blende GIF Battery ein
}
}
function clearCleanCounter(){
let vacuum_state = sendPostMessage("getState", 'mihome-vacuum.0.info.state');
if (vacuum_state == 1){
setze CleanCounter auf 0 zurück
}
}
Ja, da muss man etwas umdenken und weg vom "linearen", hin zum parallelen oder - fachlich korrekt "synchronen" ausführen von Befehlen. Musste ich auch erst mal kapieren. Die Idee ist die:
Als erstes definierst Du, was passieren soll, wenn Dir ein State geliefert wird. Das machst Du alles (wie Du es unten schon im nächsten Beispiel getan hast) innerhalb der receivePostMessage-Funktion. Die wird also am Ende ziemlich lang werden, weil da die ganze Logik reingepackt wird. Also im konkreten Beispiel müsste da stehen:
Wenn Du einen neuen Zustand der Batterie empfängst, dann zeige das Battrie-Logo, wenn der Zustand leer ist. Ansonsten verstecke das Bild.
Erst als letztes subscribest Du dann, ganz am Ende des Scripts, alle States, die Du für Dein Widget benötigst.
Ich habe zwei verschiedene Funktionen, die beide den State desselben Datenpunktes abfragen, aber zu völlig unterschiedlichen Zeiten benutzt werden können.
Bisher habe ich verstanden, dass die Funktion receivePostMessage den Rückgabewert verarbeiten kann, deshalb habe ich teshalber folgendes gebaut, was auch funktioniert:
var img_locked = 'http://192.x.x.x:8082/iqontrol.meta/userimages/usericons/vacuum/locked.png';
var img_unlocked = 'http://192.x.x.x:8082/iqontrol.meta/userimages/usericons/vacuum/unlocked.png';
window.addEventListener("message", receivePostMessage, false);
function receivePostMessage(event) { //event = {data: message data, origin: url of origin, source: id of sending element}
if(event.data && event.data.command) switch(event.data.command){
case "getState":
if (((event.data.stateId == '0_userdata.0.vacuum.rooms.Bad.locked'){
let img_room_locked = document.getElementById('img_room_locked');
if (event.data.value.val){
img_room_locked.setAttribute("onclick", "setState('0_userdata.0.vacuum.room_lock', 'Bad')");
img_room_locked.setAttribute("src", img_locked);
}else{
img_room_locked.setAttribute("onclick", "setState('0_userdata.0.vacuum.room_lock', 'Bad')");
img_room_locked.setAttribute("src", img_unlocked);
}
}
break;
}
}
Genau so ist es richtig. Hier muss alle Logik rein.
Wenn man Logiken benötigt, die mehrere States kombinieren, dann muss man sich die States, die man empfängt, in Variablen ablegen und dann jeweils eine ausgelagerte Funktion aufrufen. Schau mal das map.html-Beispiel an:
map.html
Hier wird jeweils für altitude, longitude und zoom beim Empfangen des States eine entsprechende Variable gesetzt und danach repositionMap() aufgerufen:
case "Map.Position.latitude":
console.log("Set latitude to " + event.data.value.val);
mapPositionLatitude = parseFloat(event.data.value.val) || 0;
if(mymap) repositionMap();
break;
In der repositionMap()-Funktion wird dann aus den drei Variablen altitude, longitude und zoom die neue Kartenposition angefahren. Wenn sich eine der drei Variablen ändert, weil eben ein aktualisierter State geliefert wird, wird die Karte neu positioniert.
Man könnte das jetzt auch noch so erweitern, dass die repositionMap()-Funktion zuerst schaut, ob schon alle drei Variablen empfangen wurden if(altitude != null && longitude != null && zoom != null){...}
(das fehlt im map.html-Beispiel eigentlich noch).
Wenn man dann die drei States für alt, long und zoom subscribed, kommen die Werte (in ggf. auch zufälliger Reihenfolge!) kurze Zeit später an:
Dann wird meinetwegen zuerst altitude empfangen, repositionMap() aufgerufen -> bricht ab, weil longitude und zoom noch fehlen.
Dann kommt z.B. zoom als nächstes rein, repositionMap() wird wieder aufgerufen -> bricht ab, weil longitude immer noch fehlt.
Dann kommt als letztes irgendwann longitude rein -> jetzt läuft die Funktion durch, weil alle drei Werte da sind.
Jetzt etwas verständlicher?