Beispiele für in p44script implementierte Custom Devices

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

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

Internet-Wächter

Dieses einfache Beispiel implementiert ein Schaltsignal-Gerät, welches anzeigt, ob die Internet-Verbindung funktioniert oder nicht.

Init-Message (Details dazu hier):

{
  'message':'init',
  'inputs':[ {
    'inputtype':0, 'usage':2, 'group':8, /* schwarz: Joker */
    'hardwarename': 'Internet-Test',
    'updateinterval': 300, 'alivesigninterval': 900
  }]
}

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

// Diese URL muss im Internet erreichbar sein. Das Apple-Beispiel funktioniert as-is,
// aber Sie können jede andere URL nehmen, die zuverlässig erreichbar ist und eine möglichst kleine
// Antwort liefert (aus Effizienzgründen)
var testurl = "http://www.apple.com/library/test/success.html"

// alle 5 Minuten:
on (every(0:05)) {
  var msg = {'message':'input', 'index':0 }
  try {
    // Wenn "testurl" innert 10 Sek eine fehlerfreie Antwort liefert, ist die Internetverbindung da
    var ans = geturl(testurl, 10)
    log(6, "Internet is ok")
    msg.value = 1
  }
  catch as e {
    // Fehler oder Timeout beim Zugriff auf die "testurl"
    log(4, "Kein Internet-Zugriff")
    msg.value = 0
  }
  message(msg)
}

// 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 Input-Gerät, das alle 5 Minute die Internet-Verbindung testet, und den Status (1=ok, 0=unterbrochen) meldet.

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.

Dingz: Dimmer und Taster

Der dingz.ch ist ein Gerät in der Bauform eines Unterputzschalters, der 4 Taster und bis zu vier Dimmerausgänge bietet, und noch vieles andere kann. Dieses Gerät wird hier als Beispiel verwendet, weil es eine REST-API hat, die aus dem LAN direkt angesteuert werden kann zur Ansteuerung der Ausgänge hat, sowie über sogenannte Action URLs auch Tastendrücke melden kann. Im Folgenden zwei Beispiele, eines für einen Dimmer-Ausgang, das andere für einen Taster-Eingang (auf ähnliche Weise könnten auch Storensteuerung und Bewegungssensor integriert werden).

Dingz: Dimmer-Ausgang

Init-Message (Details dazu hier):

{
  'message':'init',
  'name':'dingz dimmer',
  'output':'light'
}

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

// Konfiguration
// - IP-Adresse des dingz
var ip='192.168.1.123'
// - welcher Dimmer?
var dimmerindex=0

// Implementation
on (message()) as m {
  if (m.message=='channel' && m.index==0) {
    var ramp = 25 // dingz default = 25*10mS
    if (isok(m.transition)) ramp = m.transition*100 // Sek -> 10mS
    var post = format(
      'http://%s/api/v1/dimmer/%d/on?value=%d&ramp=%d',
      ip, dimmerindex, m.value, ramp
    )
    log(6, "posting: %s", post)
    posturl(post)
  }
}

return true

Diese einfache Implementation integriert den Dingz-Dimmer inklusive einstellbarer Übergangszeiten bei Szenenaufrufen als vollwertige dS-Leuchte.

Dingz: Taster-Eingänge

P44-Firmware 2.6.6 bzw. 2.6.6.0 oder neuer benötigt

Dieses Beispiel macht Gebrauch von Features, die erst ab Firmware 2.6.6 bzw. Beta 2.6.6.0 komplett richtig funktionieren (Ein einzelner Taster funktioniert auch mit 2.6.5/2.6.5.9 schon, aber mehrere stören einander). Bitte P44-xx entsprechend aktualisieren (Freischaltung für Beta-Versionen auf Anfrage).

Das Taster-Beispiel ist ein bisschen komplizierter, weil hier der Dingz via Action URL (s. Dingz-Dokumentation) die P44 erreichen können muss, wenn eine Taste gedrückt wird. Dafür gibt es die p44script-Funktion webrequest() (s. hier), mit der der Endpoint /api/json/scriptapi des P44-Webservers bedient werden kann.

Weil aber mehrere Geräte, u.U. auch noch andere als nur Dingz, diesen gleichen Endpoint verwenden wollen, braucht es für alle Geräte gemeinsam eine Implementation im sogenannten mainscript (welches unter http://ip-meiner-p44/p44script.html aufgerufen werden kann, s. auch hier).

Mainscript-Code für alle Geräte gemeinsam

Der folgende Code muss als Teil des Mainscripts eingegeben werden, damit die P44 auf Action URLs vom Dingz reagieren kann (aber es kann auch noch anderer Code im mainscript sein, dann sollte der folgende Code einfach unten angehängt werden):

// http-Requests von Geräten
on(webrequest()) as request
{
  log(6, "webrequest = %s", request)
  if (isok(request.dingz)) {
    // das ist ein Request von einem dingz
    // - weiterleiten via dignz_signal_192_168_ip_ip
    var s = ifok(globalvars()['dingz_signal_'+replace(request.peer,'.','_')], false)
    if (isok(s)) s.send(request)
  }
  request.answer({ "status":"ok" })
}

Diesen mainscript-Code braucht es nur einmal, er funktioniert so, dass er beliebig viele Taster-Devices bedienen kann, indem die hereinkommenden requests via signal.send() an das entsprechende Gerät weitergeleitet werden.

Konfiguration der Action URLs

Damit der Dingz bei Tastendruck entsprechende Action URLs ausführt, müssen diese im Dingz wie folgt konfiguriert werden:

1x click:
get://user:password@ip.der.p44.box/api/json/scriptapi?dingz=button1&click=1
2x click:
get://user:password@ip.der.p44.box/api/json/scriptapi?dingz=button1&click=2
3x click:
get://user:password@ip.der.p44.box/api/json/scriptapi?dingz=button1&click=3
4x click:
get://user:password@ip.der.p44.box/api/json/scriptapi?dingz=button1&click=4
hold:
get://user:password@ip.der.p44.box/api/json/scriptapi?dingz=button1&click=5
release:
get://user:password@ip.der.p44.box/api/json/scriptapi?dingz=button1&click=0

wobei...

Hinweis: http ist schneller

Für die Action URLs wird hier das unverschlüsselte get: verwendet. Das verschlüsselte (https) gets: funktioniert auch, doch der https-Verbindungsaufbau benötigt auf den kleinen CPUs ca. 3 sek, die dann als Tasterverzögerung stark spürbar ist. Da dieser Aufruf im lokalen Netz (LAN) passiert, und nicht ins Internet geht, wäre die fehlende Verschlüsselung nur dann ein Thema, wenn das LAN bereits als unsicher gilt (sprich: einfach hackbar oder gehackt ist).

Scripted-Device für Dingz-Taster

Wenn das obige erledigt ist, kann das zu den Action URLs passende Taster-Device angelegt werden.

Hier ist die Init-Message für einen einfachen Einzeltaster (Details dazu hier):

{
  'message':'init',
  'group':1, /* Gelb/Licht */
  'name':'dingz button 1', /* initialer Name */
  'buttons':[
    { "buttontype":1, "group":1 }
  ]
}

Alternativ können die folgenden zwei Init-Messages paarweise verwendet werden, um zwei Taster zu bekommen, die im dSS zu einem Zweiwege-Taster zusammengefügt werden können, genauso wie das mit zwei benachbarten Eingängen von digitalSTROM Tasterklemmen geht. Folgende Zusatzfelder in der Init-Message machen dies möglich:

Im Übrigen macht in diesem Beispiel die Angabe von "group":8 den Taster zum Joker, der zwar wegen dem "group":1 innerhalb von buttons standardmässig als gelber Lichttaster erscheint, aber danach via dSS auch z.B. auf grau umgeschaltet werden kann für Jalousiensteuerung.

Init-Message für den ersten Taster (nicht vergessen, eigenen String für uniqueid verwenden, es reicht auch, das 1234 in eine andere Zahl zu ändern):

{
  "message": "init",
  "uniqueid": "dingz_twoway_1234", /* gleich für beide Teile, aber unterschiedlich für jedes Tasterpäärchen! */
  "subdeviceindex": 0, /* erste Teil des Zweiwege-Tasters */
  "group":8, /* Joker */
  "buttons": [{
    "buttontype": 0, "group":1, "combinables": 2, "hardwarename": "up"
  }]
}

Init-Message für den zweiten Taster (Unterschiede: subdeviceindex 1 statt 0, hardwarename down statt up, wobei dieser Name nur fürs Log ist, für die Funktion spielt er keine Rolle - aber gleiche uniqueid wie beim ersten Taster!):

{
  "message": "init",
  "uniqueid": "dingz_twoway_1234", /* gleich wie beim ersten Teil */
  "subdeviceindex": 1, /* anderer Teil des Zweiwege-Tasters */
  "group":8, /* Joker */
  "buttons": [{
    "buttontype": 0, "group":1, "combinables": 2, "hardwarename": "down"
  }]
}

Die Implementation, gleich für alle der oben gezeigten Init-Message-Varianten (die richtige IP-Adresse des Dingz sowie der richtige button muss eingetragen werden, s. Kommentar im Code):

// Konfiguration
// - IP-Adresse des dingz
var ip='192.168.1.123'
// - welcher Button?
var button="button1"

// Implementation (verbessert 2022-10-18)
var signame = 'dingz_signal_'+replace(ip,'.','_')
var sig
if (!isok(globalvars()[signame])) {
  // signal fuer diese IP existiert noch nicht: neu anlegen
  globalvars()[signame] = signal()
}
// existierendes oder neu angelegtes signal verwenden
sig = globalvars()[signame]

on (sig) as s {
  log(6, "s = %s", s)
  if (s.dingz==button) {
    // das ist der richtige button
    var msg = {'message':'button', 'index':0 }
    if (s.click==0) {
      // Button losgelassen
      msg.value = -10
    }
    else if (s.click==5) {
      // Button länger gehalten
      msg.value = -11
    }
    else {
      // 1..4-fach click, direkt einspeisen (negativer value)
      msg.value = -s.click
    }
    message(msg)
  }
}

return true;

Nun funktioniert der Dingz-Taster wie ein normaler dS-Taster, inkl. Mehrfachclick sowie Dimmen/deep-off mit lange halten. Wer gar keine Mehrfachclicks oder Dimmverhalten will, lässt einfach die enstprechenden Action URLs im Dingz leer.

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.

Virtuelle Wetterstation

Das folgende Beispiel benutzt den kostenlosen openweathermap.org Service, der ziemlich gute und ausführliche aktuelle Wetterdaten für einen geographischen Ort liefert und damit (je nach Bedürfnissen) eine eigene Hardware-Wetterstation ergänzen oder sogar ersetzen kann.

Diese Scripted-Device-Anwendung hat Andreas Kleeb von www.redIT.ch Smart Solutions entwickelt, im Einsatz getestet und für dieses Beispiel zur Verfügung gestellt - vielen Dank!

Das Beispiel zeigt, dass auch ein komplexeres Device mit vielen Sensoren relativ einfach umgesetzt werden kann.

Bitte beachten

Um die Wetterdaten von openweathermap.org abfragen zu können, muss dort ein Account angelegt werden. In diesem Account kann dann ein API key generiert werden, der im Implementationsscript an der bezeichneten Stelle eingefügt werden muss. Einen "How to Start"-Guide gibt es hier.

Init-Message (Details dazu hier):

{
  'message': 'init',
  'protocol': 'simple',
  'group': 8, /* Joker */
  'name': 'OpenWeatherMap.org', /* initialer Name */
  /* Definition der Wetter-Sensoren */
  'sensors': [
    /* S0 */ { 'sensortype': 1,  'usage': 2, 'group': 0, 'hardwarename': 'temperature',   'min': 0, 'max': 100,   'resolution': 0.1, 'updateinterval': 120 },
    /* S1 */ { 'sensortype': 18, 'usage': 2, 'group': 0, 'hardwarename': 'pressure',      'min': 0, 'max': 1200,  'resolution': 1,   'updateinterval': 120 },
    /* S2 */ { 'sensortype': 2,  'usage': 2, 'group': 0, 'hardwarename': 'humidity',      'min': 0, 'max': 100,   'resolution': 1,   'updateinterval': 120 },
    /* S3 */ { 'sensortype': 29, 'usage': 2, 'group': 0, 'hardwarename': 'visibility',    'min': 0, 'max': 10000, 'resolution': 1,   'updateinterval': 120 },
    /* S4 */ { 'sensortype': 13, 'usage': 2, 'group': 0, 'hardwarename': 'wind speed',    'min': 0, 'max': 300,   'resolution': 0.1, 'updateinterval': 120 },
    /* S5 */ { 'sensortype': 19, 'usage': 2, 'group': 0, 'hardwarename': 'wind deg',      'min': 0, 'max': 360,   'resolution': 1,   'updateinterval': 120 },
    /* S6 */ { 'sensortype': 23, 'usage': 2, 'group': 0, 'hardwarename': 'wind gust',     'min': 0, 'max': 300,   'resolution': 0.1, 'updateinterval': 120 },
    /* S7 */ { 'sensortype': 21, 'usage': 2, 'group': 0, 'hardwarename': 'precipitation', 'min': 0, 'max': 1000,  'resolution': 0.1, 'updateinterval': 120 },
    /* S8 */ { 'sensortype': 0,  'usage': 2, 'group': 0, 'hardwarename': 'clouds %',      'min': 0, 'max': 100,   'resolution': 1,   'updateinterval': 120 }
  ]
}

Implementation (Details zum Inhalt der gesendeten message() hier). Damit dieses Beispiel funktioniert, müssen der richtige API-Key und der richtige Ort im Code eingefügt werden, siehe Kommentare:

function updateweather()
{
  try {
    var t = geturl(baseurl,30)
  }
  catch {
    return true
  }

  log (7, "OpenWeatherMap reply = %s", t)
  var weather = json(t);
  log (6, "OpenWeatherMap weather = %s", weather)

  if (isvalid(weather.main.temp)) {
    message(format("S0=%.1f",weather.main.temp))
  }
  if (isvalid(weather.main.pressure)) {
    message(format("S1=%.1f",weather.main.pressure))
  }
  if (isvalid(weather.main.humidity)) {
    message(format("S2=%.1f",weather.main.humidity))
  }
  if (isvalid(weather.visibility)) {
    message(format("S3=%.1f",weather.visibility))
  }
  if (isvalid(weather.wind.speed)) {
    message(format("S4=%.1f",weather.wind.speed))
  }
  if (isvalid(weather.wind.deg)) {
    message(format("S5=%.1f",weather.wind.deg))
  }
  /* ohne Boeninfo -> keine Boe */
  message(format("S6=%.1f", ifvalid(weather.wind.gust,0)))
  /* Niederschlag */
  var precipitation = ifvalid(
    weather.snow['1h'], // Schnee..
    ifvalid(
      weather.rain['1h'], // falls kein Schnee: Regen
      0 // weder Schnee noch Regen
    )
  )
  message(format("S7=%.1f",precipitation))
  /* Bewoelkung */
  if (isvalid(weather.clouds.all)) {
    message(format("S8=%.1f",weather.clouds.all))
  }
}

// hier muss der OpenWeatherMap API key, Ort und Land eingetragen werden
var openweathertoken = 'API_key'; // API key
var openweathercity = 'Zug'; // Ortsname
var openweathercountry = 'CH'; // Land
var baseurl = string('https://api.openweathermap.org/data/2.5/weather?q=' + openweathercity + '%2C' + openweathercountry +  '&units=metric&appid=' + openweathertoken)

// Alle 120 Sekunden
on (every(120)) {
  // Wetter einlesen
  updateweather()
}

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

Dies erzeugt ein Wetterstations-Gerät mit 8 Sensoren für Temperatur, Druck, Feuchtigkeit, Sichtweite, Windgeschwindigkeit, Windrichtung, Böen, Niederschlag und Bewölkung.

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.

Vielen Dank an Erik van Dort smile-in.ch für die Forschung und Korrespondenz mit dem VKG, die zu den folgenden Anpassungen an die im September 2022 geänderten Parameter des VKG-Services geführt haben!

Mögliche Probleme mit Zertifikatsprüfung

Im September 2022 wurden die Server für den Hagelservice aktualisiert, was zur Folge hat dass die Zertifikatsprüfung in der https-Verbindung plötzlich nur noch mit aktueller P44-Firmware (>=2.6.5) funktioniert hat. Um dieses Problem zu vermeiden, besonders auch für zukünftige Änderungen serverseits, wurde im untenstehenden Script ein Ausrufezeichen vor die URL gestellt, welches die Zertifikatsprüfung ausschaltet. Das ist in diesem Fall kein praktisches Sicherheitsrisiko, da keine heiklen Daten übertragen werden.

Neue API v1

Ebenfalls im September 2022 wurde eine neue, erweiterte API v1 (vorher: v0) eingeführt. Das untenstehende Beispiel wurde deshalb auch angepasst, die (nur ganz geringfügig geänderte) v1-API zu verwenden, um zukunftssicherer zu sein. Derzeit funktioniert auch die v0-API weiterhin.

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"

var firstpoll = "false"

// Gemäss Anleitung des VKG muss die Warnung alle 120 Sekunden abgefragt werden
on (every(120)) {
  var msg = {'message':'input', 'index':0 }
  try {
    // Anmerkung: Das Ausrufezeichen am Anfang der URL schaltet die Zertifikationsprüfung aus,
    // damit die Installation möglichst langfristig läuft, auch wenn der Server
    // später einmal wieder auf andere Root-Zertifikate setzt (wie im Sept 2022 geschehen)
    var url = "!https://meteo.netitservices.com/api/v1/devices/" + deviceid + "/poll?firstpoll=" + firstpoll + "&hwtypeId=" + hwtypeld
    var ans = json(geturl(url))
    // Eingangswert auf 1 falls Hagel (echt oder test), sonst auf 0
    msg.value = if(ans.hailState!=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

Bitte beachten: Nach Einrichtung Gerät neustarten!

Dank Rückmeldungen von Anwender:innen hat sich herausgestellt, dass auf Grund eines Fehlers in der Version 2.6.0.10 beta bzw. 2.6.0 die Abfrage (on(every(120)) { ... }) nach Änderungen am Skript u.U. mehrfach aktiv bleiben kann, und dann zuviele Anfragen an den VKG-Service versendet werden. Deshalb sollte nach der Einrichtung bzw. späteren Änderungen am Script die P44-DSB einmal neugestartet werden. In zukünftigen Firmware-Versionen wird das Problem dann behoben sein.

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.