Beispiele für in p44script implementierte Custom Devices

Eine Implementation eines Custom-Devices besteht immer aus zwei Teilen:

  • die sogenannte init-Message, welche die Art des Geräts, so wie es im Smarthome-System sichtbar sein soll, beschreibt. Die init-Message muss beim Anlegen des Geräts eingegeben werden und kann nachträglich nicht mehr verändert werden, ohne es zu löschen und neu anzulegen (aus Sicht des SmartHome-System ist es danach ein neues, anderes Gerät).

  • Die eigentliche Implementation in p44script. Diese kann beliebig immer wieder verändert werden, bis die Funktion so ist, wie gewünscht.

Komplette Dokumentation

Die vollständige Dokumentation finden Sie auf der Seite Custom Devices mit der vollständigen Spezifikation der externen Geräte-API (init message und andere Gerätekommunikation).

Custom-Device erzeugen

Ein neues Gerät wird mit dem "+ Scripted"-Knopf angelegt:

Custom Devices vDC header line

Im erscheinenden Dialog kann die init-Message eingebenen werden.

Custom Devices Create Dialog

Hier muss eine gültige Init-Message eingegeben werden. Eine vollständige Dokumentation der init-Message gibt es hier; für die ersten Experimente empfiehlt es sich, eines der folgenden Beispiele zu nehmen.

Bitte beachten

  • Die init-Message ist kompatibel mit den init-Messages die über die external device API via TCP-Socket für extern implementierte Geräte verwendet wird. Bei in p44script implementierten Geräten ist es aber nicht notwendig, eine 'uniqueid' anzugeben, und i.d.R. auch nicht empfehlenswert.
  • Für p44Script-Implementierte Geräte kann die init-Message auf mehrere Zeilen verteilt und mit C-Style-Kommentaren /* ... */ versehen werden (während die init-Message über die external device API immer auf einer einzigen Zeile sein muss).

Custom-Device Implementation bearbeiten

Die Implementation kann jederzeit über den Edit-Knopf (Bleistiftsymbol) angepasst werden Custom Device line Einfache Scripts können direkt im erscheinenden Dialog eingegeben werden, aber für längere/komplexere Scripts empfiehlt es sich, den eingebauten Fullscreen-Codeeditor zu verwenden (über den Link unten rechts am Eingabefeld) Custom Device edit dialog

In den folgenden Beispielen wird jeweils die JSON init-Message und der Script-Code der Implementation in separaten Textfeldern gezeigt, aus welchen die Beispiele einfach per Copy&Paste übernommen werden können.

Gebrauchsfertige Beispiele

Für P44-DSB-X auf Raspberry Pi: CPU-Temperatur als Sensorwert

Auf RaspberryPi-Geräten ist die CPU-Temparatur als Zahl im File /sys/class/thermal/thermal_zone0/temp verfügbar. Mit folgendem einfachen Custom Device kann daraus ein Sensor-Gerät erstellt werden:

Init-Message (Details dazu hier):

{
  'message':'init',
  'protocol':'simple',
  'group':3, /* Blau/Klima */
  'name':'RPi CPU temp.', /* initialer Name */
  'sensors':[
    /* sensortype1 = Temperatur, updateinterval = 60 = einmal ausgelesen pro Minute */
    {'sensortype':1,'usage':1,'hardwarename':'RPiCPU','min':0,'max':100,'resolution':0.1, 'updateinterval':60}
  ]
}

Implementation (Details zum Inhalt der gesendeten message() hier).

// Jede Minute:
on(every(0:01)) {
  // den Wert aus dem File auslesen, als Zahl behandeln und skalieren (File liefert 1/1000 Grad)
  var t = number(readfile('/sys/class/thermal/thermal_zone0/temp'))/1000;
  // als Sensorwert melden
  message(format("S0=%.1f",t))
}

// Wichtig: signalisiert, dass es ok ist dass das Skript hier beendet
//   Nur der Handler läuft im Hintergrund.
//   Fehlt dieses "return true", wird die Implementation 20 Sekunden nach
//   Beenden neugestartet (in der Annahme, es sei ein Fehler aufgetreten)
return true

Dies erzeugt ein Sensor-Gerät, das jede Minute die aktuelle CPU-Temperatur aus dem System liest, und als Sensorwert ins Smarthome-System meldet. Es unterscheidet sich in der Art und Weise, wie es funktioniert, nicht von einem anderen Temperatursensor (z.B. EnOcean-Geräte etc.).

RPi CPU temperature Custom Device

Userlevel 1 benötigt

Weil dieses Beispiel readfile() mit einem absoluten Pfad verwendet, muss das Gerät aus Sicherheitsgründen mindestens userlevel 1 haben. Produktionsgeräte haben standardmässig userlevel 0. Experimentelle DIY-Geräte und Geräte mit Beta-Versionen haben userlevel 1. Der Userlevel wird durch /flash/p44userlevel festgelegt. Bei Geräten mit Zugriff auf die Commandline (z.B. P44-DSB-X) kann der Userlevel somit einfach auf 1 gesetzt werden mit echo 1 >/flash/p44userlevel.

myStrom Schaltsteckdose: Einfache HTTP-API

Von mystrom.ch gibt es Schaltsteckdosen, die über WiFi verbunden und über eine App geschaltet und programmiert werden können. Dieses Gerät wird hier als Beispiel verwendet, weil es eine einfache, aus dem LAN direkt ansteuerbare API hat. Über diese API lässt sich so eine Steckdose einfach als schaltbarer Ausgang für das Smarthome-System verwenden (ohne von einer externen Cloud abhängig zu sein!):

Init-Message (Details dazu hier):

{
  'message':'init',
  'output':'light', /* mit Ausgang vom Typ "Licht"... */
  'dimmable':false, /* ...jedoch nicht dimmbar */
  'colorclass':8, /* universell verwendbar, in digitalSTROM: schwarze Joker-Klemme */
}

Implementation (Details zum Inhalt der empfangenen message() m hier): Damit das Beispiel funktioniert, muss die richtige IP-Adresse der Steckdose eingetragen werden, s. Kommentar im Code:

// hier muss die richtige IP-Adresse der Steckdose eingetragen werden
var baseurl = "http://192.168.1.42"

// Meldungen vom System (hier: Änderung des Ausgangswerts) behandeln
on (message()) as m {
  // wenn die Meldung den Kanal mit Index 0 (=Helligkeit) betrifft...
  if (m.message=="channel" && m.index==0) {
    // mySTROM API ansteuern und einschalten, falls Helligkeit > 50%, sonst aus
    geturl(baseurl + format('/relay?state=%d', if(m.value>50, 1, 0)))
  }
}

return true // es ist ok, dass das Script hier endet

Damit entsteht ein Device, dessen Ausgang direkt in der P44-xx-Weboberfäche (über das Zahnrädchen-Icon) eingestellt werden kann, und sich im digitalSTROM-Smarthome mit allen Funktionen (Raumzuordnung, Szenenprogrammierung etc.) verhält wie jeder andere Ein-/Aus-Schaltaktor.

Power Outlet Device

Die myStrom-Steckdosen können auch den bezogenen Strom messen, und den aktuellen Schaltzustand zurückmelden (nützlich, wenn dieser mit der Taste an der Steckdose geändert wurde). Auch das lässt sich mit einer etwas ausgebauteren Implementation verwenden:

Init-Message (neu kommt die Zeile sensors dazu, details s. hier):

{
  'message':'init',
  'output':'light', /* mit Ausgang vom Typ "Licht"... */
  'dimmable':false, /* ...jedoch nicht dimmbar */
  'colorclass':8, /* universell verwendbar, in digitalSTROM: schwarze Joker-Klemme */
  /* Definition eines Leistungs-Sensor */
  'sensors':[{"sensortype":14,"usage":0,"hardwarename":"power","min":0,"max":2300,"resolution":1,"updateinterval":30,"alivesigninterval":300}]
}

Implementation, die nun zusätzlich alle 30 Sekunden den Status abfragt, und den effektiven Zustand des Ausgangs (relay) sowie den Stromverbrauch (power) zurückmeldet.

function updatestate()
{
  var t = geturl(baseurl + '/report');
  log (7, "myStrom reply = %s", t)
  var state = json(t);
  log (6, "myStrom state = %s", state)
  if (isvalid(state.relay)) {
    var msg = { "message":"channel","index":0 }
    msg.value = if(state.relay, 100, 0);
    message(msg)
  }
  if (isvalid(state.power)) {
    var msg = { "message":"sensor", "index":0 }
    msg.value = state.power;
    message(msg)
  }
}

// hier muss die richtige IP-Adresse der Steckdose eingetragen werden
var baseurl = "http://192.168.1.42"

on (message()) as m {
  if (m.message=="channel" && m.index==0) {
    geturl(baseurl + format('/relay?state=%d', if(m.value>50, 1, 0)))
  }
}

// Alle 30 Sekunden
on (every(30)) {
  // Status lesen
  updatestate()
}

// beim Start des Geräts aktuellen Zustand sofort lesen
updatestate()

return true // es ist ok, dass das Script hier endet

Das neue Gerät hat nun zusätzlich einen Sensoreingang, der den momentanen Stromverbrauch anzeigt.

Hagelwarnung in der Schweiz vom vkg.ch: einfache HTTP REST API

Die "Vereinigung Kantonaler Gebäudeversicherungen" VKG bietet für die Schweiz einen kostenlosen Hagelwarn-Service, der über eine einfache HTTP REST API abgefragt werden kann.

Bitte beachten

Die Benützung der API muss beim VKG für jeden Standort beantragt werden. Es muss ein Antragsformular ausgefüllt werden, auf dem u.a. die MAC-Adresse des abfragenden Geräts erfragt wird - diese wird aber nur als eindeutige ID für den Abfrager und seinen Standort verwendet. Die MAC-Adresse kann über die Funktion macaddress() (oder die Weboberfläche, oder das Gerätelabel) gefunden werden. Nach erfolgreicher Inbetriebnahme muss dem VKG ein Abnahmeprotokoll eingereicht werden, in dem bestätigt wird, dass die Abfrage funktioniert, und die Storen/Jalousien tatsächlich eingezogen werden.

Init-Message (Details dazu hier):

{
  'message':'init',
  'inputs':[ {
    'inputtype':0, 'usage':2, 'group':6, /* rot: Sicherheit */
    'hardwarename': 'Hagelwarnung VKG',
    'updateinterval': 120, 'alivesigninterval': 360
  }]
}

Implementation (Details zum Inhalt der mit message() gesendenten msg hier): Damit das Beispiel funktioniert, müssen die vom VKG bestätigte deviceid (MAC-Adresse) und hwtypeld den entsprechenden Variablen zugewiesen werden, s. Kommentar im Code:

// hier muss die richtige "deviceid" (= beim VKG angemeldete MAC-Adresse) für den Standort und die
// richtige hwtypeld (so wie von VKG spezifiziert) eingegeben werden
var deviceid = "a1b2c3e4f542"
var hwtypeld = "199"

// Gemäss Anleitung des VKG muss die Warnung alle 120 Sekunden abgefragt werden
on (every(120)) {
  var msg = {'message':'input', 'index':0 }
  try {
    var url = "https://meteo.netitservices.com/api/v0/devices/" + deviceid + "/poll?hwtypeId=" + hwtypeld
    var ans = json(geturl(url))
    // Eingangswert auf 1 falls Hagel (echt oder test), sonst auf 0
    msg.value = if(ans.currentState!=0, 1, 0)
  }
  catch as e {
    log(4, "Fehler bei Abfrage des Hagelservice: %s", e)
    // Sensor als ungültig markieren
    msg.value = null
  }
  message(msg)
}

return true // es ist ok, dass das Script hier endet

Mit dieser Implementation erscheint ein Binäreingang (in der Gruppe "Sicherheit") im System, der aktiv wird, wenn der VKG-Service Hagel meldet (oder eine Test-Meldung ausgelöst wird, s. Dokumentation zum Hagelservice). Dieser Eingang kann dann für einen Scene Responder verwendet werden, um Storen/Jalousien einzuziehen bzw. die Szene "Hagel" aufzurufen.

Experimente

Experimentelle Integration eines Casambi-Dimmers

Casambi ist ein auf Bluetooth basierendes Automations-System v.a. für Beleuchtung, zu dem es viele Komponenten von verschiedenen Herstellern gibt. Da das Protokoll aber proprietär ist, gibt es (derzeit: 2021) keine direkte Integrationsmöglichkeit, sondern nur den Weg über eine Cloud-API (derzeit im Beta-Stadium). Eine alltagstaugliche und zukunftsstabile Integration müsste m.E. unbedingt direkt lokal funktionieren können, ohne Cloud. Dennoch ist Casambi ein interessantes System, deshalb die folgende experimentelle(!) Integration eines einfachen Dimmers damit.

Experiment (Proof of Concept) - keine vollständige Integration!

Das folgende Beispiel soll zeigen, dass auch komplexere APIs eingebunden werden können, aber es soll auf keinen Fall als fertige Implementation angesehen werden! Dazu kommt, dass die aktuelle Casambi-Cloud-API erst im Beta-Stadium ist. Um die API benutzen zu können, muss ein API-Key bei Casambi beantragt werden. Für die Verbindung zwischen Cloud und Casambi-Devices braucht es einen Gateway - für Experimente wie dieses kann dies laufende Casambi-App auf einem Mobiltelefon sein, für eine wirklich brauchbare Integration müsste aber ein separates Gateway-Gerät verwendet werden (z.B. von Holders Technology).

Nur für Einzelgerät geeignet!

Diese Experimental-Implementation ist nur für ein Einzelgerät geeignet. Es kann nicht dupliziert und in mehreren scripted devices eingesetzt werden (schon wegen der globalen Variablen). Eine ausgefeiltere Implementation wird eine einzige Verbindung zur Cloud für alle Devices haben müssen (im mainscript), welche die Device-Scripts dann ansteuern können.

Aktuelle (Beta-)Firmware notwendig

Dieses Beispiel braucht mindestens Beta-Firmware 2.6.0.7 (bzw. später das Produktions-Release 2.6.0)

Init-Message (Details dazu hier):

{ 'message':'init', 'name':'Casambi demo light', 'output':'light' }

Implementation (Details zum Inhalt der mit message() gesendenten msg hier).

Damit das Beispiel funktioniert, müssen der Casambi API-Key, sowie User und Passwort den entsprechenden Variablen zugewiesen werden und die "unitid" entsprechend dem anzusteuernden Dimmer gesetzt sein, s. Kommentar im Code. Ebenso muss ein Gateway die Verbindung zur Cloud herstellen (für Tests kann das die Casambi-App auf einem Mobiltelefon sein).

glob casambi_api_key = "XXXXX" // Hier API-Key eintragen
glob casambi_user = "test@testhaus.ch" // Hier Casambi API Login-Email eintragen
glob casambi_password = "xxxxxx" // Hier Casambi API Passwort eintragen
var unitid = "1" // Unit-ID (Nummer) des Dimmers hier eintragen

glob casambi_network
glob casambi_websocket
glob casambi_reconnect_signal

casambi_reconnect_signal = signal()

while (true) {
  if (!isvalid(casambi_network)) {
    var req = {
      "url":"https://door.casambi.com/v1/networks/session",
      "method":"POST",
      "data":{},
      "headers":{}
    }
    req.headers['X-Casambi-Key'] = casambi_api_key
    req.data.email = casambi_user
    req.data.password = casambi_password
    var resp = json(httprequest(req))
    casambi_network=resp[resp[0]];
  }
  log("casambi_network: %s", casambi_network)

  if (!isvalid(casambi_websocket)) {
    var wsurl = "wss://developer.casambi.com/v1/bridge/"
    casambi_websocket = websocket(wsurl, casambi_api_key, 60)
    var wsopen = {
      "method":"open",
      "type":1,
      "id":"NETWORK-ID",
      "ref":"REFERENCE-ID",
      "wire":1
    }
    wsopen.session = casambi_network.sessionId
    wsopen.id = casambi_network.id
    casambi_websocket.send(wsopen)
  }


  on (casambi_websocket.message()) evaluating as msg {
    log("casambi websocket message: %s", msg)
  }

  // forward channel value changes
  on (message()) as m {
    log("device API message: %s", m)
    if (m.message=="channel" && m.index==0) {
      var wsmsg = { "wire": 20, "method":"controlUnit", "targetControls": {"Dimmer": {"value": 0}}}
      wsmsg.id = number(unitid)
      wsmsg.targetControls.Dimmer.value = m.value/100
      log("- sending websocket message: %s", wsmsg)
      try {
        casambi_websocket.send(wsmsg)
      }
      catch as e {
        if (errordomain(e)=="websocket" && errorcode(e)==6) {
          // connection problem
          casambi_websocket = null
          // reconnect
          casambi_reconnect_signal.send()
        }
      }
    }
  }

  // make sure we don't try to reconnect too often
  delay(20)
  // wait for need to reconnect
  await(casambi_reconnect_signal)
  log(4, "Needs reconnect")
}

return true;

Kein Support ;-)

Wie erwähnt, ist dies ein Experimental-Beispiel. Ich stelle es as-is zur Verfügung, es funktioniert bei mir mit einem ARDITI DEBT230-150 Dimmer, aber ich kann keinerelei Support dafür bieten, besonders nicht zu Casambi-Fragen. Ich habe selber noch kaum Casambi-Kentnisse, der Code folgt ziemlich genau den Beispielen auf der Casambi Developer Seite.