JSON-RPC mit HTML/Javascript und IP-Symcon Beispiele und Fragen

Hallo,

ich habe mich intensiver mit dem Datenaustausch zwischen HTML/Javascript und IP-Symcon per XMLHttpRequest mit JSON-RPC beschäftigt. Dazu gibt es dankenswerter Weise einen Thread von Paresy, der die Grundlagen zeigt. Weiteren Threads und meine eigenenVersuche zeigen, dass das Thema nicht ganz trivial ist. Ich möchte daher meine Ergebnisse mit euch teilen. Im beigefügten Skript sind einige Anwendungsfälle abgedeckt.
Damit es bei euch lauffähig ist, müsst ihr eine Variable vom Typ String als HTML-Box erstellen. Der Wert wird später per Skript gefüllt. Zusätzlich benötigt ihr eine Variable als Boolean, eine Variable als String, sowie 3 Skriptvariablen. Der Boolean und die einfache Stringvariable können leer bleiben. Diese werden später durch den XMLHttpRequest manipuliert.
Bevor wir das eigentliche Skript für unsere HTML-Seite erstellen, werden die ersten beiden Skriptvariablen wie folgt gesetzt.

  1. Skript mit einfachem Rückgabewert:
<?php
echo "ein Skript ist toll";
?>
  1. Skript zur Rückgabe aggregierter Daten:
<?php

$id = $_IPS['varID'];
$end = $_IPS['end'];
$dur = $_IPS['duration'];
$agg = $_IPS['agg'];
$type = $_IPS['type'];
$localOffset =(new DateTime(date("Ymd\THis", $end),new DateTimeZone('Europe/Berlin')))->getOffset(); // Offset zur UTC-Zeit berechnen
$end = $end-$localOffset;
$start = $end - $dur;
if ($agg == 7) {
    $aggValues = AC_GetLoggedValues(27427,$id,$start, $end, 0);
    $type = 'Value';
}
else {
    $aggValues = AC_GetAggregatedValues(27427,$id,$agg, $start, $end, 0);
}

$arrData = array();

foreach(array_reverse($aggValues) as $value) {
    $arrData[]= array(($value["TimeStamp"]+$localOffset)*1000, round($value[$type],2));
}

header('Content-Type: application/json'); //Rückgabetyp definieren
echo json_encode($arrData);

?>

Im nun folgenden Skript wird der Inhalt für die HTML-Box erstellt. Darin müsst ihr folgende ID’s ersetzen:

(ID’s in der HTML-Tabelle)

53498 → ID eurer neu erstellten Boolean-Variable
21714 → ID eurer neu erstellten String-Variable
17192 → ID eines beliebigen Integer-Werts (wird nur gelesen, nicht verändert)
55626 → ID eines beliebigen Float-Werts (wird nur gelesen, nicht verändert)
17661 → ID des Skript 1 (Skript mit einfachem Rückgabewert)

(ID’s in der Funktion getValues())
19649 → ID einer geloggten Variable (Ich habe eine Temperaturvariable genommen, ist aber egal)
52809 → ID eures Skriptes 2 (Skript zur Rückgabe aggregierter Daten)
36049 → ID eurer neu erstellten HTML-Box

Zusätzlich müsst ihr den Fernzugang aktivieren und im Usernamen und Passwort in der Funktion window.xhrRPC (relativ weit unten) anpassen.

  <?
$val = "
    <table border=1 width=100%>
        <tr><td height=\"50\">RPC Request</td><td onclick=window.xhrRPC('/api/','SetValueBoolean',[53498,false]);>SetValueBoolean -- Klick mich!</td></tr>
        <tr><td height=\"50\">RPC Request</td><td ><label for=\"text\"></label>SetValueString <input type=\"text\" id=\"text\" onchange=window.xhrRPC('/api/','SetValueString',[21714,document.getElementById('text').value]);> Wert eingeben!</td></tr>
        <tr><td height=\"50\">RPC Request</td><td onclick=window.xhrRPC('/api/','GetValue',[21714]);>GetValue String-- Klick mich!</td></tr>
        <tr><td height=\"50\">RPC Request</td><td onclick=window.xhrRPC('/api/','GetValue',[17192]);>GetValue Integer-- Klick mich!</td></tr>
        <tr><td height=\"50\">RPC Request</td><td onclick=window.xhrRPC('/api/','GetValue',[55626]);>GetValue Float-- Klick mich!</td></tr>
        <tr><td height=\"50\">RPC Request</td><td onclick=window.xhrRPC('/api/','IPS_RunScriptWait',[17661]);>Skript mit Rückgabewert -- Klick mich!</td></tr>
        <tr><td height=\"50\">RPC Request</td><td onclick=window.xhrRPC('/api/','IPS_RunScript',[17661]);>Skript ohne Rückgabewert -- Klick mich!</td></tr>
        <tr><td height=\"50\">RPC Request</td><td onclick=getValues();>Skript mit Array Rückgabewert (Aggregierte Werte) -- Klick mich!
        </td></tr>
    </table>
    <div>Zeitraum und Aggregationswert auswählen</div>
    <p>
        <select id=\"duration\"> 
            <option>Tag</option>
            <option>Woche</option>
            <option>Monat</option>
            <option>Jahr</option>
        </select>
        <select id=\"type\"> 
            <option>Avg</option>
            <option>Min</option>
            <option>Max</option>
        </select>
    </p>
    <p><div id=\"test\">Hier wird der Testfall ausgegeben.</div></p>
    <p><div id=\"perf\">Hier wird die Dauer von Anfrage bis Rückgabe Ergebnis ausgegeben.</div></p> 
    <p><div id=\"get\">Hier wird das Ergebnis des der Rückgabe ausgegeben.</div></p> 
    <p><div id=\"rpc\">Hier wird der jsonrpc-String ausgegeben.</div></p>
    <p><div id=\"vtype\">Hier wird der Variablentyp der Rückgabe ausgegeben.</div></p>  
    <p><div id=\"resp\">Hier wird das Response-Ergebnis von JSON-RPC ausgegeben.</div></p>
<script type='text/javascript'>
    var start = performance.now();
    var end = performance.now();

    window.xhrRPC=function xhrRPC(o, name, params) { 
        start = performance.now();
        var HTTP = new XMLHttpRequest();
        HTTP.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            alert (HTTP.getResponseHeader('Content-Type'));
            var objReturn = JSON.parse(HTTP.response);
            var objValue = objReturn.result;
            if (params[0] == 52809) {
                var arr = JSON.parse(objValue);  //objValue hat den Typ String - durch JSON.parse erzeugen wir ein Array
                }
            end = performance.now();
            switch(true){
                case name == 'SetValueBoolean':
                case name == 'SetValueString': 
                    document.getElementById('test').innerHTML = 'Der Testfall '.concat(name,' wurde ausgeführt.');
                    document.getElementById('perf').innerHTML = 'Dauer: ' + (end - start) + ' ms.'
                    document.getElementById('get').innerHTML = 'Die Variable '.concat(params[0],' wurde auf den Wert ',params[1], ' gesetzt.');
                    document.getElementById('resp').innerHTML = 'Der Responsewert ist: '.concat(objValue);
                    document.getElementById('vtype').innerHTML = 'Der zurück gegebene Variablentyp '.concat(params[0],' ist: ',typeof objValue);
                break;
                case params[0] == 52809:
                    document.getElementById('test').innerHTML = 'Der Testfall '.concat(name,'mit Array-Rückgabe wurde ausgeführt.');
                    document.getElementById('perf').innerHTML = 'Dauer: ' + (end - start) + ' ms.'
                    document.getElementById('get').innerHTML = 'Es wurde kein Wert gesetzt ';
                    document.getElementById('resp').innerHTML = 'Das erste zurückgegebene Wertepaar (Timestamp, Value) ist: '.concat(arr[0][0], ', ' , arr[0][1]);
                    document.getElementById('vtype').innerHTML = 'Der zurück gegebene Variablentyp '.concat(params[0],' ist: ',typeof arr);
                break;
                case name == 'GetValue': 
                case name == 'IPS_RunScriptWaitEx':
                case name == 'IPS_RunScriptWait':
                    document.getElementById('test').innerHTML = 'Der Testfall '.concat(name,' wurde ausgeführt.');
                    document.getElementById('perf').innerHTML = 'Dauer: ' + (end - start) + ' ms.'
                    document.getElementById('get').innerHTML = 'Es wurde kein Wert gesetzt ';
                    document.getElementById('resp').innerHTML = 'Der Responsewert ist: '.concat(objValue);
                    document.getElementById('vtype').innerHTML = 'Der zurück gegebene Variablentyp '.concat(params[0],' ist: ',typeof objValue);
                break;
                case name == 'IPS_RunScript' && objValue == true: 
                    document.getElementById('test').innerHTML = 'Der Testfall '.concat(name,' wurde ausgeführt.');
                    document.getElementById('perf').innerHTML = 'Dauer: ' + (end - start) + ' ms.'
                    document.getElementById('get').innerHTML = 'Das Skript '.concat(params[0],' wurde erfolgreich durchgeführt');
                    document.getElementById('vtype').innerHTML = 'Der zurück gegebene Variablentyp '.concat(params[0],' ist: ',typeof objValue);
                    document.getElementById('resp').innerHTML = 'Der Responsewert ist: '.concat(objValue);
                break;
                case name == 'IPS_RunScript' && objValue == false: 
                    document.getElementById('test').innerHTML = 'Der Testfall '.concat(name,' wurde ausgeführt.');
                    document.getElementById('perf').innerHTML = 'Dauer: ' + (end - start) + ' ms.'
                    document.getElementById('script').innerHTML = 'Das Skript '.concat(params[0],' konnte nicht ausgeführt werden');
                    document.getElementById('vtype').innerHTML = 'Der zurück gegebene Variablentyp '.concat(params[0],' ist: ',typeof objValue);
                    document.getElementById('resp').innerHTML = 'Der Responsewert ist: '.concat(objValue);
                break;
            
               }

            }
        };
        HTTP.open('POST',o,true);
        var rpc = JSON.stringify({'jsonrpc':'2.0', 'method':name, 'params':params, 'id':0});
        document.getElementById('rpc').innerHTML = 'Der JSON-RPC String ist: '.concat(rpc);
        HTTP.setRequestHeader('Content-type', 'application/json');
        HTTP.setRequestHeader('Authorization', 'Basic ' + btoa('uhebel@web.de:1qa2ws3ed'));
        //HTTP.responseType = 'json';
        HTTP.send(rpc);
        }

    // Übergabewerte für JSON-RPC als assoziatives Array erstellen
    var getValues = function(){
        
        var duration=document.getElementById('duration').value; 
        var arrSkript = {};
        switch(duration){
            case 'Tag': 
                arrSkript['duration'] = (24*60*60); 
                arrSkript['agg'] = 6;
                break;
            case 'Woche':
                arrSkript['duration'] = (7*24*60*60);
                arrSkript['agg'] = 0;
                break;
            case 'Monat':
                arrSkript['duration'] = (31*24*60*60);
                arrSkript['agg'] = 1;
                break;
            case 'Jahr':
                arrSkript['duration'] = (31*24*60*60);
                arrSkript['agg'] = 1;
                break;
            }
        arrSkript['varID'] = 19649;
        arrSkript['end'] = ((new Date().getTime()-new Date().getTimezoneOffset()*60*1000) / 1000 | 0);
        arrSkript['type'] = document.getElementById('type').value;;
        window.xhrRPC('/api/','IPS_RunScriptWaitEx',[52809,arrSkript]);
        }

    </script>



  
 ";

 SetValue(36049, $val);

?>  

Dann sollte alles laufen…

1)Arrays
Im Grunde genommen klappt das alles gut. Nur gelingt es mir nicht, ein Array von php an javascript per JSON-RPC auch als Array zu übergeben. Innerhalb von Javascript ist der Variablentyp immer „string“. Geht das überhaupt? Wie müsste man das machen?

2)Sicherheit
Bei mir läuft das nur lokal für mein IPSView-Frontend. Aber wenn man das per Fernzugriff machen möchte, stellt sich mir die Frage nach Passwort und Username - kann man die nicht auch verschlüsselt übergeben?

Im Voraus vielen Dank für jedwedes Feedback oder Anregeung zur Verbesserung.

Gruß,

Uwe

Du kannst einen Array z.B. mit json_encode in ein String umwandeln und diesen an Javascript übergeben und dort in einer String Variable nutzten bzw. auf Javascript Seite dann eben wieder per mit JSON.parse umwandeln.

Hallo Fonzo,

danke für die schnelle Antwort. So ähnlich hatte ich das beim Einlesen in das Thema auch verstanden.In der Praxis kriege ich es aber nicht hin. In meinem Beispielskript übergebe ich per json_encode:

$arrData = array();
foreach(array_reverse($aggValues) as $value) {
    $arrData[]= array($value["TimeStamp"]*1000, $value[$type]);
}
echo json_encode($arrData);

Im Javascript wird der JSON-RPC String per JSON.parse in seine Bestandteile aufgeteilt. Das Ergebnis kann dann per Result-Eigenschaft abgerufen werden. Dieses ist dann dummerweise ein String.

window.xhrRPC=function xhrRPC(o, name, params) { 
        start = performance.now();
        var HTTP = new XMLHttpRequest();
        HTTP.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            var objReturn = JSON.parse(HTTP.responseText);
            var objValue = objReturn.result;

typeof objValue → string. Interessanterweise funktioniert das JSON.parse auf dem responseText so wie gewünscht. Der responseText wird in ein Objekt umgewandelt.

Derzeit behelfe ich mir, indem ich von vornherein einen String übergebe und diesen mit Split und Typumwandlung anpasse. Funktioniert, aber die Performance ist auf meinem Tablet nicht wirklich berauschend.

Gibt es in Javascript eine Möglichkeit einen String der quasi schon Arrayform hat direkt in ein Array umzuwandeln?


var stringA = "[[12,3],[15,4]]";
var arrayA =  {}
arrayA = stringA;

oder so ähnlich?

Manchmal liegt die Lösung so nah und ist doch so fern…
Mit folgenden einfachen Änderungen funktioniert jetzt auch die Übergabe eines Arrays wie gewünscht.

[ol]
[li]Im php-Code habe ich den Content Type auf „header(‚Content-Type: application/json‘);“ gesetzt
[/li][li]Im Javascript uaf den objValue noch mal JSON.parse gesetzt und nun funktioniert es wie gewünscht.
[/li][/ol]

Ich baue die Änderungen im ersten Post ein - dann ist das Beispiel voll funktionsfähig.

Gruß,

Uwe