IoT angewendet auf AirZone

Das Internet der Dinge (IoT) ist die Fähigkeit der Elektronik, die uns umgibt zu interagieren und unser Leben zu erleichtern.

Der unten dargestellte UniPlot-Anwendungsfall gibt ein ausführliches Beispiel dafür, wie man die UniPlot-Funktion in diesem Rahmen zu verwenden, während man durch.

Bullet cURL requests

Bullet REST API

Bullet SQL DataBase

Bullet Services in UniPlot

Bemerkung

Der gesamte Code und die Vorlage für den Anwendungsfall sind im Beispielverzeichnis Verzeichnis UniPlot\Samples\IoT

1. Warum ein Klimasystem mit UniPlot steuern?

In diesem Beispiel handelt es sich um eine Wohnung, in der das AirZone-System installiert ist, mit dem der Benutzer steuern kann, wie die Klimaanlage jeden einzelnen Raum erwärmt oder kühlt. einzelnen Raum.

1.1 Das System

Eine Darstellung dieses Systems ist unten abgebildet. Jeder Ausgang der Leitungen sind mit einem elektromotorisch gesteuerten Gitter ausgestattet, das vom AirZone-System auf der Grundlage der Analyse der jeweiligen Raumtemperatur gesteuert wird.

Die Hauptmaschine bläst Luft mit einer bestimmten Temperatur ein, und jedes Gitter öffnet oder sich selbst, um die Temperatur in den Räumen zu regulieren.

Jeder Raum ist nämlich mit einem Thermostat ausgestattet, an dem eine bestimmte Temperatur für den Raum gewählt werden kann.

../../_images/airzone-housesystem.png

Dieses System soll selbständig arbeiten, aber es liefert keine über den Zustand des Systems im Laufe der Zeit.

1.2 UniPlot und AirZone

AirZone bietet dem Benutzer die Möglichkeit, mit dem System zu interagieren dank einer RESTful API.

UniPlot kann dann mit dieser API interagieren, um:

Bullet Abrufen von Daten (Temperatur, Sollwert, ON/OFF…) mit der Methode POST:

POST http://XXX.XXX.XXX.XX:3000/api/v1/hvac With the following body:
{
  "systemID": n (system number),
  "zoneID": m (zone number)
}

Bullet Senden von Temperatursollwerten über eine selbst erstellte Benutzeroberfläche mit der PUT Methode:

PUT http://XXX.XXX.XXX.XX:3000/api/v1/hvac With the following body
{
  "systemID": n (system number),
  "zoneID": m (zone number),
  "parameter" (parameter to modify, e.g "setpoint"): f (value)
}

Da wir Daten von der API erhalten können, werden wir sie in einer SQL-Datenbank speichern, um alle Daten über einen bestimmten Zeitraum aufzeichnen zu können und Statistiken und System analysieren.

2. Diskutieren Sie mit der API: AirZone_curl_POST()

2.1 Das cURL POST-Verfahren

Als Erstes müssen wir die API kontaktieren, um einige Informationen zu erhalten.

UniPlot wird mit der cURL library geliefert, die uns helfen wird unsere Anfrage zu erstellen.

// Initialize the request
curl = curl_easy_init();
o = [. cnt = 0, data = [.]];
// Preparing options
oOptions = [.
    URL =  "http://" + IPCASA + ":3000/api/v1/hvac",
    COPYPOSTFIELDS = "{\"systemID\":0,\"zoneID\":" + ltostr(key) + "}",
    HTTPHEADER = ["charset: utf-8"; "Accept: application/json"; "Content-Type: application/json"],
    WRITEFUNCTION = "_DownloadCBData",
    CURLOPT_WRITEDATA = o];
// Setting options
curl_easy_setopt(curl, oOptions);
// Send the request
r = curl_easy_perform(curl);

Wie oben zu sehen ist, erfolgt die Erstellung eines Antrags in 3 Schritten.

Bullet Initialisieren der Anfrage

curl = curl_easy_init();

Bullet Vorbereiten der Optionen

oOptions = [.
    URL =  "http://" + IPCASA + ":3000/api/v1/hvac",
    COPYPOSTFIELDS = "{\"systemID\":0,\"zoneID\":" + ltostr(key) + "}",
    HTTPHEADER = ["charset: utf-8"; "Accept: application/json"; "Content-Type: application/json"],
    WRITEFUNCTION= "_DownloadCBData",
    CURLOPT_WRITEDATA = o];

Dies ist der schwierigste Teil, in dem alle wichtigen Informationen enthalten sind. Jede Zeile ist ein Kennzeichen, das einer bestimmten Aktion in unserer Anfrage entspricht. Einige von ihnen werden immer vorhanden sein, andere hängen davon ab, was Sie erreichen wollen. Alle Flaggen werden hier erklärt: https://curl.se/libcurl/c/curl_easy_setopt.html

  • URL: die URL, an die die Anfrage gesendet wird. Sie wird immer vorhanden sein
  • COPYPOSTFIELDS: Dies ist der in der Anfrage benötigte Körper, der die Informationen über das System enthält, dessen Status wir wissen wollen.
  • HTTPHEADER: Es gibt eine Beschreibung dessen, was wir senden (hier JSON kodiert in utf-8).
  • WRITEFUNCTION: Es handelt sich um eine Rückruffunktion, die zum Speichern der die von der API empfangen wurde.
  • CURLOPT_WRITEDATA: Sie sagt der vorherigen Callback-Funktion, was sie verwenden soll um die Daten zu speichern (hier das Objekt o).

Bullet Verknüpfung der Optionen mit der curl-Anfrage:

curl_easy_setopt(curl, oOptions);

Bullet Ausführen des Ersuchens:

r = curl_easy_perform(curl);

2.2 Erhalt der Antwort

Der folgende Code prüft, ob eine Antwort empfangen wurde oder nicht und wandelt dann die Informationen um, um sie in UniPlot zu verwenden.

if (r != 0) {
    return curl_easy_strerror(r); // Return a possible error message
} else {
    bool = curl_easy_cleanup(curl); // Needed to close the curl
    s = "";
    if (o.cnt == 0) {
        return s;
    }
    for (i in 1:o.cnt) { // Unpack the data stored by the callback function
        s = s + mem_unpack(o.data[i]);
    }
    s_txt = utf8_decode(s);
    print(s_txt);
    svDataName = AirZonesvDataName(); // Vector of human readable room names.

    systemID[0].zoneID[key] = [.];
    // We store the information in a UniScript object
    for (ssName in svDataName) {
        s_txt_pos = strfind(s_txt, "\"" + ssName + "\"");
        if (s_txt_pos != 0) {
            s_txt_cut = strextract(s_txt, s_txt_pos + strlen("\"" + ssName + "\"") + 1);
            val = strtok(s_txt_cut, ",")[1];
            systemID[0].zoneID[key][ssName] = strtrim(strtok(s_txt_cut, ",")[1]);
        }
    }
}
Die Ausgabe dieser Funktion ergibt das folgende Objekt, das in UniPlot verwendet
werden kann
[.
         = [.
                zoneID = [.
                        [1] = [.
                                air_demand = "0",
                                coldStage = "1",
                                coldStages = "1",
                                errors = "[]",
                                floor_demand = "0",
                                heatStage = "1",
                                heatStages = "1",
                                humidity = "65",
                                maxTemp = "27",
                                minTemp = "15",
                                mode = "3",
                                name = "\"Salon\"",
                                on = "0",
                                roomTemp = "21.600000381469727",
                                setpoint = "22.5",
                                systemID = "1",
                                units = "0",
                                zoneID = "1",
                        ],
                        [2] = [.
                                air_demand = "0",
                                coldStage = "1",
                                coldStages = "1",
                                errors = "[]",
                                floor_demand = "0",
                                heatStage = "1",
                                heatStages = "1",
                                humidity = "74",
                                maxTemp = "27",
                                minTemp = "15",
                                mode = "3",
                                name = "\"Hab.Matrimo\"",
                                on = "0",
                                roomTemp = "21",
                                setpoint = "18",
                                systemID = "1",
                                units = "0",
                                zoneID = "2",
                        ],

3. Speichern der Daten: AirZone_DB()

Nun, da wir die API kontaktieren und die Informationen unseres Systems erhalten können, sollten wir speichern wir sie in einer SQL-Datenbank, um später zu entscheiden, was wir damit machen wollen.

Bullet Wir beginnen mit der Erstellung der Datenbank.

db = sqlite3_open(ssSrcDir + "/AirZone.db");

Bullet Wir erstellen dann die benötigten Tabellen, falls sie noch nicht existieren.

for (zone in zones) {
    sqlite3_execute(db, "create table if not exists " + zone ...
        + "(" + svValName[1] + " varchar(100), " ...
        + svValName[2] + " varchar(100), " ...
        + svValName[3] + " varchar(100), " ...
        + svValName[4] + " varchar(100), " ...
        + svValName[5] + " varchar(100));");
}
    b = sqlite3_execute(db, "create table if not exists TimeStamp(" ...
        + "TimeStamp varchar(100), " ...
        + "Year varchar(100), " ...
        + "Month varchar(100), "...
        + "Day varchar(100), ...
        + "YearMonthDay varchar(100));");
}

In diesem Beispiel erstellen wir 1 Tabelle pro Raum und jede Tabelle enthält 5 Informationen:

  • Zeitstempel
  • Raumtemperatur
  • Raumtemperatur-Sollwert
  • Zustand der Raumregelung (EIN/AUS)
  • Raumluftbedarf (blasend oder nicht)

Neben diesen Tabellen erstellen wir eine weitere Tabelle, um Datumsinformationen in anderen Form:

  • Zeitstempel
  • Jahr
  • Monat
  • Tag
  • JahrMonatTag

Bullet Dann rufen wir die zuvor erstellte Funktion auf, um einen Satz frischer Daten zu erhalten und fragen auch den aktuellen Zeitstempel ab.

Temperatures = AirZone_curl_POST();
t = DT_GetCurrentTime();

Bullet Der nächste Schritt besteht darin, die Informationen in den Tabellen zu speichern.

for (zone in 1:len(zones)) {
    obj = Temperatures[0].zoneID[zone];
    sqlite3_execute(db, "insert into " + zones[zone] + " values("+ sprintf("%.4f", t) + "," ...
                                        + obj.roomTemp + "," ...
                                        + obj.setpoint + "," ...
                                        + obj.on + "," ...
                                        + obj.air_demand+ ");");
}

Bullet Und für die Zeitstempel

ssYear = DT_Format(t, "20%y");
ssMonth = DT_Format(t, "%m");
ssDay = DT_Format(t, "%d");
ssYearMonthDay = "'" + DT_Format(t, "20%y.%m.%d") + "'";
request = "insert into TimeStamp values("+ sprintf("%.4f", t) + "," + ssYear + "," + ssMonth + "," + ssDay + "," + ssYearMonthDay + ");";
sqlite3_execute(db, request);

Bullet Schließlich schließen wir die Datenbank ordnungsgemäß

db = sqlite3_close(db);

3. UniPlot arbeiten lassen: AirZone_DB_service_ON()

Wir haben eine Struktur geschaffen, mit der wir eine REST-API mit der curl-Bibliothek aufrufen, einige Informationen über unser System abrufen einige Informationen über unser System abrufen und in einer Datenbank speichern.

Wir wollen nun Daten für einen bestimmten Zeitraum sammeln, um zu analysieren, wie unser System funktioniert.

Zu diesem Zweck kann UniPlot eine Art von Dienst namens Timer erstellen.

Die Funktion AirZone_DB_service_ON() ist dafür zuständig, diesen Timer zu erstellen für uns.

def AirZone_DB_service_ON() {
    _g().AirZone = [.];
    _g().AirZone["Timer"] = AppNewTimer("AirZone_DB", AirZone_dtUpdate * 1000);
    log_warning("uniscript", "AirZone", "AirZone Service ON (Refresh rate: " + ltostr(AirZone_dtUpdate) + "s)");
}

Wir benutzen das globale Objekt _g(), um ein eigenes Objekt AirZone zu speichern dem wir den Bezeichner des Timers zuweisen.

_g().AirZone["Timer"] = AppNewTimer("AirZone_DB", AirZone_dtUpdate * 1000);

Wenn diese Funktion ausgeführt wird, wird die Funktion AirZoneDB alle AirZone_dtUpdate`` Sekunden aufgerufen.

Um ihn zu stoppen, haben wir auch die folgende Funktion erstellt, die den Timer beendet, indem sie seine Kennung dank des zuvor erstellten Objekts erhält.

def AirZone_DB_service_OFF() {
    AppKillTimer(_g().AirZone["Timer"]);
    log_warning("uniscript", "AirZone", "AirZone Service OFF");
}

4. Zeit für die Nutzung der Daten

Nun, da wir unsere Datenbank eingerichtet haben und sie in einer bestimmten Frequenz den Zustand des Wechselstromreglers und die Temperatur der Wohnung speichert, wollen wir diese Daten sinnvoll nutzen diese Daten nutzen und sie aufzeichnen.

In den folgenden Abschnitten werden wir sehen, wie wir die Daten in eine Daten über eine kleine Schnittstelle in ein bestehendes Template zu plotten und wie man die UniPlot-Funktionen zur die Diagramme zu aktualisieren.

4.1 Zeichnen Sie die Daten auf: AirZone_FetchAndPlot(bWatch, DateMin, DateMax)

Diese Funktion akzeptiert 3 Parameter:

  • bWatch: Für die kontinuierliche Aufzeichnung verwenden. Wir werden später darauf zurueckkommen.
  • DateMin, DateMax: Dies ist der Bereich der Daten, die wir darstellen wollen.

Die Daten werden in der folgenden Vorlage aufgezeichnet.

../../_images/airzone-template.png

Bullet Zu Beginn müssen wir eine Vorlage öffnen, die für den Empfang der Daten vorbereitet ist.

// Open the template
if (bWatch == 0) {
    ssName = strcat(SplitPath(source(stack(0))), "");
    ssSrcDir = sum(SplitPath(ssName)[1, 2]);
    hPage = auto_LoadTemplate(ssSrcDir+"\\AirZone.ipz");
    _g().Airzone = [. "ssSrcDir" = ssSrcDir, ...
        "hPage" = hPage];
    print(_g().Airzone);
} else {
    ssSrcDir = _g().Airzone["ssSrcDir"];
    hPage = _g().Airzone["hPage"];
}

Bullet Dann sammeln wir die zu aktualisierenden Datensatz-Handles und speichern sie in einem Objekt, um die Interaktion zu erleichtern. Jeder Datensatz hat einen Namen in der Vorlage. Wir speichern also den Datensatz-Handle in einem Schlüssel, der den Namen des Datensatzes enthält.

// Prepare data replacement
//hvPage = DocGetAllPages(hDoc, FALSE, FALSE);
hvData = PageGetAllDatasets(hPage);
o_Assignment = [.]
for (i in 1:len(hvData)) {
    o_Assignment[ObjGetName(hvData[i])] = hvData[i];
}

Bullet Es ist Zeit, die Datenbank abzufragen, um die Daten abzurufen.

Anfrageerstellung Wir erstellen die richtigen Anfragen für jede Zone mit SQL Syntax.

"SELECT TimeStamp.TimeStamp, RoomTemp, SetPoint, AcOn, AirDemande "... // We select the columns to extract
    + "FROM TimeStamp "... // In the main TimeStamp table
    + "INNER JOIN " + zone + " " ... // Making an inner join with the table corresponding to the zone we analyze
    + "ON TimeStamp.TimeStamp = " + zone + ".TimeStamp " ... // using TimeStamp as index
    + " WHERE TimeStamp.YearMonthDay >= " + DateMin + " AND TimeStamp.YearMonthDay <= " + DateMax ... // only within the requested time frame
    + ";";

Der Code sieht wie folgt aus

ssName = strcat(SplitPath(source( stack(0))), "");
db = sqlite3_open(ssSrcDir + "/AirZone.db");

o = [.];
for (zone in zones) {
    if (bWatch == 0) {
        request = "SELECT TimeStamp.TimeStamp, RoomTemp, SetPoint, AcOn, AirDemande "...
                                        + "FROM TimeStamp "...
                                        + "INNER JOIN " + zone + " " ...
                                        + "ON TimeStamp.TimeStamp = " + zone + ".TimeStamp " ...
                                        + " WHERE TimeStamp.YearMonthDay >= " + DateMin + " AND TimeStamp.YearMonthDay <= " + DateMax ...
                                        + ";";
    } else {
        request = "SELECT TimeStamp.TimeStamp, RoomTemp, SetPoint, AcOn, AirDemande "...
                                        + "FROM TimeStamp "...
                                        + "INNER JOIN " + zone + " " ...
                                        + "ON TimeStamp.TimeStamp = " + zone + ".TimeStamp " ...
                                        + "WHERE TimeStamp.TimeStamp=(SELECT max(TimeStamp.TimeStamp) FROM TimeStamp);";
    }
}

Ausführung beantragen Wir bitten UniPlot, die Anfrage auszuführen und die Ausgabe zu speichern in smTimeStamps.

smTimeStamps = sqlite3_execute(db, request);

Ersetzen der Daten Diese Daten müssen dem entsprechenden Datensatz zugeordnet werden.

Wie unten dargestellt, wurde jedem Datensatz ein Name gegeben, der die Verkettung des Zonennamens (Büro, Schlafzimmer usw…) und der Variablen (Temperatur, Sollwert, …).

../../_images/airzone-template-datasetlist.png

Der Code erstellt ein Objekt, dessen Schlüssel der Name des Datensatzes und die Werte die Handles.

for (idx in 2:len(svValName)) {
    hData = o_Assignment[zone + "_" + svValName[idx]];
    rvX = strtod(smTimeStamps[;1]);
    rvY = strtod(smTimeStamps[;idx]);
    if (bWatch == 1) {
        rmXY = XYGetData(hData);
        rvX = [rmXY[;1];rvX];
        rvY = [rmXY[;2];rvY];
    }
    bool = XYSetData(hData, rvX, rvY);
}

Schließen der Datenbank

bool = sqlite3_close(db);

Aktualisierung der Vorlage

PageReplot(hPage);
log_info("uniscript", "AirZone", "AirZone Plot update");

Nach all diesen Bemühungen sollten wir die folgende Ausgabe erhalten.

../../_images/airzone-template_filled.png

4.2 Wir wollen es benutzerfreundlich gestalten: AirZone_Interface()

Die im vorigen Kapitel erwähnte Funktion AirZone_FetchAndPlot(bWatch,DateMin,DateMax) würde vorigen Kapitel erwähnte Funktion sollte über eine Schnittstelle aufgerufen werden, in der der Benutzer Start- und Enddatum für die Darstellung auswählen kann. Dies ist der Zweck der Funktion AirZone_Interface() Funktion gemeint.

../../_images/airzone-interface_main_date.png

Bei der Erstellung der Schnittstelle wird die folgende Philosophie zugrunde gelegt. Wie man sehen kann, zeigt sie die Monate an, für die Daten verfügbar sind. Zu diesem Zweck müssen wir die Datenbank abfragen, um diese Informationen zu erhalten. Dann formatieren wir sie richtig und erstellen die Dialogvorlage. Dann wird der Dialog gestartet und der Benutzer wählt den Zeitraum aus, den er analysieren möchte. Mit diesen Informationen wird die Funktion AirZone_FetchAndPlot aufgerufen.

Bullet Rufen wir die Datenbank ab und speichern ihre Kennung in db

// Database
// Opening and creation if needed
ssName = strcat(SplitPath(source( stack(0))), "");
ssSrcDir = sum(SplitPath(ssName)[1, 2]);
db_file = ssSrcDir + "AirZone.db";
db = sqlite3_open(db_file);

Bullet Wir führen dann einen SQL-Aufruf an db aus, um alle Zeitstempelinformationen abzurufen Informationen abzurufen und die Datenbank zu schließen.

smTimeStamps = sqlite3_execute(db, "select * from TimeStamp;");
bool = sqlite3_close(db);

Bullet Ein wenig Formatierung der smTimeStamps-String-Matrix führt uns zu einen brauchbaren ListBoxText, der im Init-String der Dialogbox.

svYearMonth = ST_set(DT_Format(strtod(smTimeStamps[;1]), "20%y.%m"));
svYearMonth_List = CreateListBoxText(svYearMonth, 2);
svYearMonthDay = ST_set(DT_Format(strtod(smTimeStamps[;1]), "%x"));
svYearMonthDay_List = CreateListBoxText(svYearMonthDay, svYearMonthDay[1]);

Bullet Um unser Dialogfeld zu erstellen, bereiten wir nun den Init-String und die Vorlage vor. Wenn Sie Zweifel haben, wie es funktioniert, können Sie die DialogBox Dokumentation.

// Initialization string
svInitString = ["Select date: ", svYearMonth_List, "0"]; //svYearMonthDay_List, "0"]
// Creation of template
svTemplate = ["Select the range of time to plot.", ...
                "|G10                        |", ...
                "|M10                          |", ...
                strempty(1, 10), ...
                "|BActivate live plotting|"];

Bullet Die Dialogbox ist fertig, wir starten sie mit der Funktion DialogBox und speichern die Ausgabe in DateMin und DateMax. Beachten Sie auch die Variable bContinuousPlot, die ich später noch erwähnen werde.

// Launch DialogBox
svOutput = DialogBox(svTemplate, svInitString, "Airzone Interface");
if (svOutput[1] == "DLG_CANCEL") {
    return 0;
}
<svList, svSel> = GetListBoxText(svOutput[2]);
DateMin = "'" + svList[1] + ".01'";
DateMax = "'" + svList[len(svList)] + ".31'";
bContinousPlot = strtol(svOutput[3]);

Bullet Nun ist es an der Zeit, unsere Funktion FetchAndPlot aufzurufen. Und die Vorlage wird mit den gewünschten Daten aufgefüllt.

AirZone_FetchAndPlot(0, DateMin, DateMax);
../../_images/airzone-template_filled.png

4.3 Kontinuierliches Plotten: FileWatchAdd()

Wir haben jetzt zwei Datenquellen, die wir analysieren können.

Bullet Direktes Lesen aus der API

Bullet Unsere SQL-Datenbank mit unseren Aufzeichnungen

Zu diesem Zweck haben wir eine einfache Schnittstelle erstellt, die dem Benutzer die verfügbaren Daten anzeigt zu zeigen und sie wie im vorherigen Kapitel darzustellen. In diesem früheren Teil habe ich den bContinuousPlot erwähnt, den der Benutzer auf 1 setzen kann, indem er auf das das entsprechende Kontrollkästchen klickt. Dieses Kontrollkästchen aktiviert eine automatische Aktualisierung der Vorlage, wenn neue Daten verfügbar sind.

../../_images/airzone-interface_activate_live_plotting.png

Wenn auf 1 gesetzt, wird in der AirZone_Interface() Funktion die FileWatchAdd Funktion zusammen mit der Callback-Funktion AirZone_FetchAndPlot_WATCH() aufgerufen.

// Setting up continous plotting
if (bContinousPlot) {
    bool = FileWatchAdd(db_file, "AirZone_FetchAndPlot_WATCH", 1);
}

Das Ziel ist es, UniPlot anzuweisen, zu prüfen, ob die Datenbank geändert wurde (also ein neuen Datensatz gespeichert wurde) und die Funktion AirZone_FetchAndPlot_WATCH`` aufzurufen. Diese ruft ihrerseits AirZone_FetchAndPlot auf aufruft, wobei der boolesche Wert bWatch auf 1 gesetzt wird.

def AirZone_FetchAndPlot_WATCH(rsFile, bWatch) {
    // This function has been created to support the FileWatch which has only 1 argument
    AirZone_FetchAndPlot(bWatch);
}

Wir haben bereits gesehen, dass die Variable bWatch das Verhalten des Codes verändert. Da wir hier eine kontinuierliche Aufzeichnung aktivieren wollen, hat diese Variable 2 Auswirkungen:

  • Es ruft die aktuell verwendete Vorlage ab, die in _g().Airzone gespeichert ist gespeichert ist, um sie zu aktualisieren, anstatt eine neue Vorlage zu erstellen.
ssSrcDir = _g().Airzone["ssSrcDir"];
hPage = _g().Airzone["hPage"];
  • Wenn die SQL-Anfrage geändert wird, um alle bis zum aktuellen Zeitpunkt verfügbaren Daten abzurufen Zeit.
request = "SELECT TimeStamp.TimeStamp, RoomTemp, SetPoint, AcOn, AirDemande "...
                               + "FROM TimeStamp "...
                               + "INNER JOIN " + zone + " " ...
                               + "ON TimeStamp.TimeStamp = " + zone + ".TimeStamp " ...
                               + "WHERE TimeStamp.TimeStamp=(SELECT max(TimeStamp.TimeStamp) FROM TimeStamp);"

Der Rest der Funktion wird wie zuvor verwendet, um das aktualisierte Diagramm zu zeichnen.

5. Steuern Sie das Klima AirZone-System mit UniPlot

Ich habe im Kapitel UniPlot und AirZone erwähnt, dass die API zwei Methoden bietet.

  • POST um Informationen aus dem System zu lesen
  • PUT um Steueranfragen an das System zu senden.

Dank dieser Methode kann ich meine Vorlage nun mit Steuerelementen (Schaltflächen) ausstatten, mit denen ich meine Klimaanlage ein- und ausschalten und sogar die gewünschte Temperatur einstellen kann!

../../_images/airzone-template_control_main.png

Zu diesem Zweck wird jeder Schaltfläche meiner Vorlage eine Callback-Funktion und spezifische Parameter je nach Bedarf.

5.1 Hinzufügen des Rückrufs zu unserer Schaltfläche

Um eine Callback-Funktion zu einem Textfeld hinzuzufügen, muss man mit der rechten Maustaste darauf klicken und im Kontextmenü Eigenschaften im Kontextmenü wählen. Gehen Sie dann auf die Registerkarte Eigenschaften. Auf dieser Registerkarte:

  • Markieren Sie Als Schaltfläche anzeigen.
  • Markieren Sie OnClick-Callback-Name.
  • Fügen Sie einen Namen für eine Rückruf-Funktion wie folgt hinzu: Funktionsname?Param1,Param2,Param3
../../_images/airzone-button-callback.png
AirZone_curl_PUT?1,setpoint,1

Die Zeichenkette, die im Namen der Callback-Funktion steht, wird in der Funktion geparst geparst.

5.2 Die Rückruffunktion AirZone_curl_PUT()

bool = AirZone_curl_PUT(_zoneID ,_action, _val)

Returnwert

Rückgabewert

bool ist TRUE (1), wenn während des Prozesses kein Fehler aufgetreten ist.

Die Funktion erhält 3 Argumente

Parameter

Parameters

_zoneID

_zoneID ist die Zone, für die wir handeln wollen.

_action

_action ist die Aktion, die ausgeführt werden soll.

_val

_val ist der Wert für die Aktion. Zum Beispiel der Temperatursollwert.

Die Parameter können aus dem UniScript-Aufruf stammen, wo sie explizit angegeben sind, oder aus dem Namen der Callback-Funktion, wie oben erklärt.

Bullet Parsen der Argumente

hTB ist das Handle des Textfeldes und mit diesem Parameter und der Funktion ObjectGetCallbackFunctionName erhalten wir den Namen der Callback-Funktion, die wir mit der Funktion strtok parsen. Wir speichern dann nur die Parameter als einen Vektor in sv.

hTB = _g()._handle();
ssFuncName = ObjectGetCallbackFunctionName(hTB, "ObjectClickCallback");
sv = strtok(ssFuncName, "?");
    // sv = FunctioName?Param1,Param2,Param3
    // sv[1] --> function names
    // sv[2] --> coma separated arguments
if (len(sv) == 2) {
    sv = strtok(sv[2], ", ");
} else {
    log_warning("uniscript", "Check parameter input for AirZone request -> function?zoneID,action,val");
    return 0;
}

Bullet Speichern Sie die Argumente

Wenn die Funktion ohne Argumente aufgerufen wird, wird das verwendet, was aus der sv Variable, andernfalls werden die Argumente verwendet.

if (nargsin() == 0) { // Sub Call
    if (len(sv) == 3) {
        zoneID = sv[1];
        action = sv[2];
        val = sv[3];
    } else {
    log_warning("uniscript", "Check parameter input for AirZone request -> ?zoneID,action,val");
    return 0;
    }
} else if (nargsin() == 3) {
        zoneID = _zoneID;
        action = _action;
        val = _val;
}

Bullet Vorbereitung des Sollwerts

Wenn der Benutzer auf die + oder - Aktion unserer Steuerung geklickt hat, müssen wir den Temperatursollwert kennen, um ihn um ein Grad zu erhöhen oder zu verringern und später an das A/C-System zu senden. Wir nutzen auch die Gelegenheit, um die Zone einzuschalten, indem wir AirZone_curl_PUT(zoneID, "on", "1").

// Request preparation if action is temperature setpoint
if (action == "setpoint") {
    setpoint = strtod(AirZone_curl_POST(strtol(zoneID), "setpoint"));
    if (val == "1") val = setpoint + 1 else val = setpoint - 1;
    val = ltostr(val);
    AirZone_curl_PUT(zoneID, "on", "1")
}

Bullet Absenden der Anfrage

Der folgende Code sendet die PUT-Anfrage an das System mit der richtigen Zone, Aktion und Wert an das System.

// Curl request creation
curl = curl_easy_init();
data = "{\"systemID\":0,\"zoneID\":4}"
o = [. cnt = 0, data = [.]];
// Preparing options
oOptions = [.
    URL =  "http://"+IPCASA+":3000/api/v1/hvac",
    CUSTOMREQUEST = "PUT",
    COPYPOSTFIELDS = "{\"systemID\":0,\"zoneID\":"+zoneID+",\"" + action + "\":" + val + "}",
    HTTPHEADER = ["charset: utf-8";"Accept: application/json";"Content-Type: application/json"]];
// Setting options
curl_easy_setopt(curl, oOptions);
r = curl_easy_perform(curl);
//curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL) // To avoid further requests to be set as PUT
if (r != 0) {
    return curl_easy_strerror(r);
} else {
    bool = curl_easy_cleanup(curl);
    // Setting up format
    __FF_FieldUpdate(0, 0, 0, 0, FFU_ONDATACHANGE);
    b=PageReplot(GetParent(GetParent(hTB)));
    return 1;
}

5.3 Airzone-Feldfunktion

Um die Schleife zu schließen, öffnen Sie die Vorlage AirZone.ipz und markieren eines der Textfeld, um die Temperatur und ihren Sollwert anzuzeigen, verwende ich eine Feld Funktion, die alles verwendet, was wir bisher erstellt haben, um eine numerische Informationen über den Zustand unseres Systems.

../../_images/airzone-_FF_airzone.png

Die Feldfunktion verwendet AirZone_curl_POST wie zuvor definiert und wird jedes Mal aufgerufen, wenn die Seite erneuert wird.

def __ff_airzone(hDoc, hPage, hLayer, hText, nAction, svParameter)
{
    // Argument parsing
    if (len(svParameter) == 2) {
        zoneID = strtol(svParameter[1]);
        action = svParameter[2];
    } else {
        log_warning("uniscript", "Check parameter input for AirZone request -> ?zoneID,action");
        return "??";
    }
    info = AirZone_curl_POST(zoneID, action);
    return info;
}

Schlussfolgerung

Dieser Anwendungsfall deckt eine breite Palette von UniPlot-Funktionen ab, so dass Sie diese in Ihren eigenen Projekten anwenden können. auf Ihre eigenen Projekte anwenden können. Wir haben gesehen, dass UniPlot Ihre neue Schnittstelle zu Ihrem IoT zu Hause, aber auch zur Überwachung und Steuerung von Installationen an Ihrem Arbeitsplatz und gibt Ihnen natürlich alle Werkzeuge an die Hand, die Sie brauchen, um einen Einblick in die Daten zu erhalten.

id-1870160