| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114 |
- # $Id: 83_IOhomecontrol.pm 16990 2018-07-16 11:02:03Z neubert $
- ##############################################################################
- #
- # 83_IOhomecontrol.pm
- # Copyright by Dr. Boris Neubert
- # e-mail: omega at online dot de
- #
- # This file is part of fhem.
- #
- # Fhem is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 2 of the License, or
- # (at your option) any later version.
- #
- # Fhem is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with fhem. If not, see <http://www.gnu.org/licenses/>.
- #
- ##############################################################################
- =for comment
- The interaction with the interface has three layers:
- - a FHEM get or set command enqueues a job consisting of one or several
- API calls combined with a callback to be executed on the result
- - the queues takes care of logging in, running the API calls and executing the
- callbacks, and logging out again
- - the core communication handles the REST API calls to the interface
- Commands
- ========
- A get or set command enqueues an API call with callback and then runs the queue.
- When the queue has executed the API call, the callback is executed and
- handles the reply from the API call according to the specific get or set
- command.
- Queue
- =====
- Processing the queue logs into the interface,executes the single API calls
- from the get or set commands in the queue, and then logs out from the
- interface. A function to reenter queue processing is added to the callback list
- for each call.
- Communication
- =============
- The communication with the KLF200 interface is effected through the
- IOhomecontrol_Call and IOhomecontrol_Callback functions.
- IOhomecontrol_Call makes a REST API call to the interface. In the non-blocking
- mode, the call is asynchronous. On completion, IOhomecontrol_Callback is called.
- The blocking mode is not used.
- Among others, IOhomecontrol_Call is called with the parameters api, action and
- params. These can take the following values:
- api action params description
- ------------------------------------------------------------------------------
- auth login log into the interface, return token
- auth logout log out from the interface
- scenes get get the scenes
- scenes run id run the scene given by id
- settings getLog
- The callbacks parameter of IOhomecontrol_Call is an array of function
- references that are called in order with the following parameters:
- hash: the hash reference to the FHEM device
- httpParams: the hash reference used for making the API call
- err: the error message from the call, if any, else undefined
- result: the JSON decoded to a hash reference, undefined in case of error
- Note: err is undefined if the call succeeded and a result has been returned
- even if that result contains error messages.
- Aforementioned evaluation and execution of callback function references is
- done in IOhomecontrol_Callback.
- =cut
- package main;
- use strict;
- use warnings;
- use HttpUtils;
- use JSON;
- #use Data::Dumper;
- use constant MESSAGES => <<'JSON';
- {
- "e401": "Falsches Passwort",
- "e402": "Unbekannter Fehler, Anmeldung fehlgeschlagen",
- "e403": "KLF 200 wird von einem anderen Benutzer konfiguriert. Bitte versuchen Sie die Anmeldung später.",
- "e404": "Fehler bei der Abmeldung!",
- "e405": "Sitzung abgelaufen!",
- "e406": "Ungültiges Zeichen!",
- "e407": "Passwort entspricht nicht den Erfordernissen!",
- "e408": "Login not possible, while reset to factory settings!",
- "e999": "Unbekannter Fehler",
- "e998": "Das Gerät ist ausgelastet!",
- "e997": "Das Gerät befindet sich im Fehlerzustand!",
- "e996": "Ein kritischer Fehler ist aufgetreten. Das Gerät kann nicht mehr im normalen Modus betrieben werden!",
- "e995": "Abrufen der gelöschten Programmliste fehlgeschlagen",
- "e994": "Abrufen der gelöschten Eingangsliste fehlgeschlagen",
- "e993": "Programm \"{{name}}\" wurde aufgrund gelöschter Produkte gelöscht",
- "e992": "Die Verbindung \"{{name}}\" wurde aufgrund gelöschter Produkte oder Programme gelöscht",
- "e991": "Ein GW_ERROR_NTF wurde empfangen.",
- "e449": "Nicht unterstützter Modus",
- "e450": "Wechsel in den Interface-Modus fehlgeschlagen",
- "e451": "Wechsel in den Repeater-Modus fehlgeschlagen",
- "e452": "Wechsel in die Werkseinstellungen fehlgeschlagen",
- "e485": "Modus-Wechsel fehlgeschlagen - allgemeiner Fehler",
- "e101": "Produktempfang fehlgeschlagen",
- "e102": "Produktlöschung fehlgeschlagen",
- "e103": "Empfangvorgang von Produkt(en) fehlgeschlagen",
- "e104": "Kurze Bewegungen/Aufleuchten fehlgeschlagen",
- "e105": "Kopiervorgang von Produkt(en) fehlgeschlagen",
- "e106": "Suchvorgang von Produkt(en) fehlgeschlagen",
- "e107": "Produktumbenennung fehlgeschlagen",
- "e108": "Länge des Namens darf 32 Zeichen nicht überschreiten",
- "e109": "Dieser Name wird bereits verwendet. Bitte wählen Sie einen anderen Namen",
- "e110": "Stornieren des Kopierens von Produkten fehlgeschlagen",
- "e111": "Das Datenfeld darf nicht leer sein",
- "e112": "Kopieren von Produkt(en) fehlgeschlagen!",
- "e113": "Produktsuche fehlgeschlagen!",
- "e114": "Unbekannter Fehler!",
- "e115": "Das Auffinden einer anderen Bedienung im Konfigurationsempfangmodus fehlgeschlagen.",
- "e116": "DTS nicht bereit",
- "e117": "DTS-Fehler. Werkseinstellungen müssen aufgrund eines Systemfehlers der Bedienung hergestellt werden.",
- "e118": "Konfiguration nicht bereit.",
- "e119": "Die Datenübertragung zu oder von der Bedienung unterbrochen.",
- "e120": "Konfigurationsempfang wurde in der Bedienung abgebrochen.",
- "e121": "Unterbrechung.",
- "e201": "Programmumbenennung fehlgeschlagen",
- "e202": "Wechsel in den Flüstermodus fehlgeschlagen",
- "e203": "Programmlöschen fehlgeschlagen",
- "e204": "Programmempfang fehlgeschlagen",
- "e205": "Länge des Namens darf 32 Zeichen nicht überschreiten",
- "e206": "Dieser Name wird bereits verwendet. Bitte wählen Sie einen anderen Namen",
- "e207": "Programmausführung fehlgeschlagen",
- "e208": "Start der Programmerstellung fehlgeschlagen",
- "e209": "Stornieren der Programmerstellung fehlgeschlagen",
- "e210": "Programmerstellung fehlgeschlagen. Versuchen Sie es noch einmal",
- "e211": "Das Datenfeld darf nicht leer sein",
- "e212": "Unbekannte Rückmeldung",
- "e213": "Keine Kommunikation zum Netzknoten",
- "e214": "Manuell betätigt durch einen Benutzer",
- "e215": "Netzknoten durch ein Objekt blockiert",
- "e216": "Netzknoten enthält einen falschen Systemschlüssel",
- "e217": "Netzknoten auf dieser Prioritätsstufe gesperrt",
- "e218": "Netzknoten in einer anderen Position gestoppt als erwartet",
- "e219": "Während der Ausführung des Befehls ist ein Fehler aufgetreten",
- "e220": "Keine Bewegung des Netzknoten-Parameters",
- "e221": "Netzknoten kalibriert die Parameter",
- "e222": "Netzknoten-Energieverbrauch ist zu hoch",
- "e223": "Netzknoten-Energieverbrauch ist zu niedrig",
- "e224": "Türschloss-Fehler. Türverriegelung fehlgeschlagen.",
- "e225": "Zielposition nicht rechtzeitig erreicht",
- "e226": "Netzknoten ist in den Temperaturschutzmodus gegangen",
- "e227": "Netzknoten ohne Funktion",
- "e228": "Filter muss gewartet werden",
- "e229": "Batteriestand ist niedrig",
- "e230": "Netzknoten hat den Zielwert des Befehls modifiziert",
- "e231": "Netzknoten unterstützt nicht den empfangenen Modus",
- "e232": "Netzknoten ist nicht in der Lage, die richtige Richtung durchzuführen",
- "e233": "Verriegelung ist während des Entriegelungbefehls manuell gesperrt",
- "e234": "Verriegelungsfehler",
- "e235": "Netzknoten ist in den automatischen Zyklus-Modus gegangen",
- "e236": "Falsche Last am Netzknoten",
- "e237": "Netzknoten ist nicht in der Lage, den empfangenen Farbcode zu erreichen",
- "e238": "Netzknoten ist nicht in der Lage, die empfangene Zielposition zu erreichen",
- "e239": "io-Protokoll hat einen ungültigen Index empfangen",
- "e240": "Befehl durch einen neuen Befehl außer Kraft gesetzt",
- "e241": "Netzknoten wartet auf Energierückmeldung",
- "e242": "Es können keine weiteren Programme gespeichert werden",
- "e243": "Programmerstellung nicht gestartet",
- "e244": "Keine io-homecontrol® Produkte wurden aktiviert",
- "e301": "Verbindung konnte nicht hinzugefügt werden",
- "e304": "Empfang von Verbindungen fehlgeschlagen",
- "e305": "Speichern von Ausgang fehlgeschlagen",
- "e306": "Löschen von Ausgang fehlgeschlagen",
- "e308": "Löschen von Eingang fehlgeschlagen",
- "e486": "Erstellung eines neuen Systemschlüssels fehlgeschlagen. Versuchen Sie es später noch einmal.",
- "e498": "Nicht alle Netzknoten in der Tabelle haben ihrem Systemschlüssel geändert. Die Schlüssel in diesen Knoten reparieren oder sie aus der Tabelle löschen.",
- "e499": "Nicht alle Produkte wurden gelöscht",
- "e500": "Schlüsselerstellung wird in der Werkseinstellungs- oder Repeater-Modus nicht unterstützt",
- "e501": "Die Verbindung mit einigen Netzknoten in der Tabelle ging verloren. Die Verbindung reparieren oder diese Knoten aus der Tabelle entfernen. ",
- "log.100": "KLF 200 application started",
- "log.101": "EFM chip start",
- "log.102": "EFM library start",
- "log.103": "Initial web content unpacking",
- "log.104": "STM restore to factory settings request",
- "log.105": "Firmware image - uploading started",
- "log.106": "Firmware image - creating file",
- "log.107": "Firmware image - receiving data",
- "log.108": "Firmware image - validating data",
- "log.109": "Firmware image - uploading finished",
- "log.110": "Reset to factory settings",
- "log.200": "WiFi module initialized",
- "log.201": "WiFi is running as access point ",
- "log.202": "WiFi is running as a client",
- "log.203": "WiFi access point stopped",
- "log.204": "WiFi client stopped",
- "log.205": "Reading WiFi settings",
- "log.206": "Setting WiFi SSID value",
- "log.300": "LAN module initialized",
- "log.301": "LAN module stopped",
- "log.400": "51200 port server started over LAN",
- "log.401": "51200 port server started over WiFi",
- "log.402": "51200 port server over LAN stopped",
- "log.403": "51200 port server over WiFi stopped",
- "log.500": "HTTP server started",
- "log.501": "HTTP server stopped",
- "log.600": "EFM GW_ERROR_NTF error",
- "log.601": "EFM GW_CS_DISCOVER_NODES_NTF error",
- "log.602": "EFM GW_CS_CONTROLLER_COPY_CFM in TCM mode error",
- "log.603": "EFM GW_CS_CONTROLLER_COPY_CFM in RCM mode error",
- "log.604": "EFM GW_INITIALIZE_SCENE_NTF error",
- "log.605": "EFM GW_RECORD_SCENE_NTF error",
- "log.606": "EFM GW_ACTIVATE_SCENE_NTF error",
- "log.607": "EFM GW_CS_KEY_CHANGE_CFM error",
- "log.608": "EFM GW_CS_GENERATE_NEW_KEY_NTF error",
- "log.609": "EFM GW_CS_REPAIR_KEY_NTF error",
- "log.620": "EFM is not responding",
- "log.650": "EFM was externally rebooted",
- "log.651": "EFM was externally reset to factory settings",
- "log.652": "EFM restore to factory settings request",
- "log.700": "FW update start phase",
- "log.701": "FW update validate binary image phase",
- "log.702": "FW update set version phase",
- "log.703": "FW update unpack binary phase",
- "log.704": "FW update move EFM chain phase",
- "log.705": "FW update update EFM phase",
- "log.706": "FW update move STM chain phase",
- "log.707": "FW update schedule STM update phase",
- "log.708": "FW update finished phase",
- "log.709": "FW update unknown phase",
- "log.710": "FW update web content update phase",
- "log.711": "FW update schedule KLF update phase",
- "log.712": "FW update move STM chain back phase",
- "log.713": "FW update webcontent rollback phase",
- "log.714": "FW update move EFM chain back phase",
- "log.715": "FW update EFM rollback phase",
- "log.800": "Token generation failed during login",
- "log.801": "Too many attempts to login with incorrect password",
- "log.802": "Error during logout",
- "log.803": "Unknown request received to login endpoint",
- "e460": "Validierung fehlgeschlagen.",
- "e461": "Die Installation der neuen Softwareversion ist fehlgeschlagen.",
- "e462": "Entpacken fehlgeschlagen.",
- "e463": "Verschiebung der EFM-Datei fehlgeschlagen",
- "e464": "EFM Aktualisierung fehlgeschlagen.",
- "e465": "Verschiebung der STM-Datei fehlgeschlagen.",
- "e466": "STM Aktualisierung fehlgeschlagen.",
- "e467": "Zeitpunkt für die Aktualisierung des KLF 200 fehlgeschlagen.",
- "e468": "WEB Aktualisierung fehlgeschlagen.",
- "e469": "Unbekannter Fehler beim Aktualisieren.",
- "e473": "Passwort muss aus mindestens 8 Zeichen mit Buchstaben und Zahlen bestehen",
- "e474": "Passwortwiederholung falsch",
- "e475": "Veraltete Software",
- "e476": "Keine KLF 200 Software",
- "e477": "Fehler beim Abrufen",
- "e481": "Fehler beim Ermitteln der Softwareversion",
- "e482": "Die Datei ist zu groß. Unbekannt Software",
- "e483": "Aktualisierungs-Start fehlgeschlagen",
- "e488": "Falsches Passwort",
- "e489": "Änderung des Passworts fehlgeschlagen",
- "e490": "Wi-Fi-Datenempfang fehlgeschlagen",
- "e491": "Empfang der Energieverwaltungseinstellungen fehlgeschlagen",
- "e494": "Ungültige Energiekonfiguration ",
- "e492": "Änderung der Energieverwaltungseinstellungen fehlgeschlagen",
- "e493": "Empfang der Systemprotokollmeldungen fehlgeschlagen",
- "e495": "Löschen des Systemprotokolls fehlgeschlagen",
- "e496": "Übertragung fehlgeschlagen",
- "e497": "Das Gerät führt die Formatierung des Flash-Speichers durch. Versuchen Sie es in 10 Minuten erneut.",
- "e430": "Empfang der LAN-Einstellungen fehlgeschlagen",
- "e431": "Aktualisierung der LAN-Einstellung fehlgeschlagen",
- "e432": "Falsche LAN-Einstellungen",
- "e433": "Ungültige IP-Adresse",
- "e434": "Ungültige Subnetzmaske",
- "e436": "Ungültiges Gateway",
- "e435": "LAN-Konfiguration ist nicht übereinstimmend: IP-Adresse und Gateway gehören nicht zum gleichen, durch die Netzwerk-Subnetzmaske definierten Netzwerk ",
- "e437": "IP-Adresse und Standard-Gateway-Adresse müssen unterschiedlich sein",
- "e438": "IP-Adresse und Standard-Gateway können mit der ersten, durch die Netzwerk-Subnetzmaske definierten Netzwerk Adresse nicht identisch sein",
- "e439": "IP-Adresse und Standard-Gateway-Adresse können mit der ersten, durch die Netzwerk-Subnetzmaske definierten Netzwerk Adresse nicht identisch sein",
- "e440": "IP-Adresse ist nicht im gültigen IP-Bereich",
- "e441": "Standard-Gateway ist nicht im gültigen IP-Bereich"
- }
- JSON
- our $messages = decode_json(MESSAGES);
- # https://github.com/MiSchroe/klf-200-api/tree/master/src
- #####################################
- # Initialize, Define, Undefine
- #####################################
- sub IOhomecontrol_Initialize($) {
- my ($hash) = @_;
- $hash->{DefFn} = "IOhomecontrol_Define";
- $hash->{UndefFn} = "IOhomecontrol_Undef";
- $hash->{GetFn} = "IOhomecontrol_Get";
- $hash->{SetFn} = "IOhomecontrol_Set";
- $hash->{NotifyFn} = "IOhomecontrol_Notify";
- $hash->{parseParams} = 1;
- #$hash->{AttrFn} = "IOhomecontrol_Attr";
- $hash->{AttrList} = "setCmds logTraffic " . $readingFnAttributes;
- }
- #####################################
- sub IOhomecontrol_getPassword($) {
- my $hash = shift;
- my $pwfile = $hash->{"pwfile"};
- if ( open( my $fh, "<", $pwfile ) ) {
- my @contents = <$fh>;
- close($fh);
- return unless @contents;
- my $password = $contents[0];
- chomp $password;
- return $password;
- }
- else {
- return;
- }
- }
- sub IOhomecontrol_Define($$) {
- # define <name> IOhomecontrol <model> <host> <pwfile>
- my ( $hash, $argref, undef ) = @_;
- my @def = @{$argref};
- if ( $#def != 4 ) {
- my $msg =
- "wrong syntax: define <name> IOhomecontrol <model> <host> <pwfile>";
- Log 2, $msg;
- return $msg;
- }
- my $name = $def[0];
- my $model = $def[2];
- my $host = $def[3];
- my $pwfile = $def[4];
- if ( $model ne "KLF200" ) {
- my $msg = "unsupported model $model, allowed models: KLF200";
- Log 2, $msg;
- return $msg;
- }
- $hash->{NOTIFYDEV} = "global";
- $hash->{"host"} = $host;
- $hash->{"pwfile"} = $pwfile;
- $hash->{fhem}{".password"} = IOhomecontrol_getPassword($hash);
- $hash->{fhem}{".token"} = undef;
- $hash->{fhem}{".scenes"} = undef;
- $hash->{fhem}{".running"} = 0;
- $hash->{fhem}{".log"} = [];
- IOhomecontrol_updateStateReadings( $hash, "Initialized" );
- return;
- }
- ###################################
- sub IOhomecontrol_Notify($$) {
- my ( $hash, $dev ) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- return if ( $dev->{NAME} ne "global" );
- return if ( !grep( m/^INITIALIZED|REREADCFG$/, @{ $dev->{CHANGED} } ) );
- return if ( $attr{$name} && $attr{$name}{disable} );
- Log3 $hash, 5,
- "IOhomecontrol $name: FHEM initialization or rereadcfg triggered getting scenes";
- # update readings
- IOhomecontrol_updateLoggedInReadings($hash);
- IOhomecontrol_clearQueue($hash);
- IOhomecontrol_getScenes($hash);
- return undef;
- }
- #####################################
- sub IOhomecontrol_Undef($$) {
- my ( $hash, $arg ) = @_;
- # we should call HttpUtils_Close here
- # for all pending non blocking gets
- # todo!
- return;
- }
- #####################################
- # Core communication with interface
- # Knows nothing about business logic
- #####################################
- sub IOhomecontrol_setDeviceResult($$) {
- my ( $hash, $result ) = @_;
- my $name = $hash->{NAME};
- my $errorsref = $result->{errors};
- my @errors = @{$errorsref};
- my $err = "";
- if (@errors) {
- $err = join( " ", @errors );
- Log3 $hash, 2, "IOhomecontrol $name: device has errors ($err).";
- }
- else {
- $err = "";
- }
- my $r = $result->{result};
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "deviceStatus", $result->{deviceStatus} );
- readingsBulkUpdate( $hash, "deviceErrors", $err );
- readingsBulkUpdate( $hash, "deviceResult", $r );
- readingsEndUpdate( $hash, 1 );
- }
- sub IOhomecontrol_Call($$$$$) {
- my ( $hash, $api, $action, $params, $callbacks ) = @_;
- my $name = $hash->{NAME};
- my $host = $hash->{"host"};
- my $token = $hash->{fhem}{".token"};
- # build url
- my $url = "http://$host/api/v1/$api";
- # build header
- my $header = {
- "Accept" => "application/json",
- "Content-Type" => "application/json;charset=utf-8",
- };
- if ( defined($token) ) {
- $header->{"Authorization"} = "Bearer $token";
- }
- # build payload
- my $payload = {
- "action" => $action,
- "params" => $params,
- };
- my $json = encode_json $payload;
- # https://wiki.fhem.de/wiki/HttpUtils
- # build HTTP request
- my $httpParams = {
- url => $url,
- timeout => 120, # it takes long to close the shutters
- method => "POST",
- noshutdown => 1,
- keepalive => 0,
- httpversion => "1.1",
- header => $header,
- data => $json,
- callback => \&IOhomecontrol_Callback,
- # additional data
- hash => $hash,
- api => $api,
- action => $action,
- params => $params,
- callbacks => $callbacks,
- };
- Log3 $hash, 5, "IOhomecontrol $name: $url, calling";
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "callState", "calling" );
- readingsBulkUpdate( $hash, "callURL", $url );
- if ( AttrVal( $name, "logTraffic", 0 ) ) {
- Log3 $hash, 5, "IOhomecontrol $name: request $json";
- readingsBulkUpdate( $hash, "callRequest", $json );
- }
- readingsBulkUpdate( $hash, "callResult", "pending" );
- readingsEndUpdate( $hash, 1 );
- HttpUtils_NonblockingGet($httpParams);
- }
- sub IOhomecontrol_Callback($$$) {
- my ( $httpParams, $err, $data ) = @_;
- my $hash = $httpParams->{hash};
- my $name = $hash->{NAME};
- my $url = $httpParams->{url};
- my $api = $httpParams->{api};
- my $json = $httpParams->{data};
- my $action = $httpParams->{action};
- my $callbacks = $httpParams->{callbacks};
- $err = undef if ( $err eq "" );
- $data = undef if ( $data eq "" );
- my $result = undef;
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "callState", "completed" );
- # process error
- if ( defined($err) ) {
- Log3 $hash, 5, "IOhomecontrol $name: $url, call failed";
- readingsBulkUpdate( $hash, "callResult", "error ($err)" );
- if ( AttrVal( $name, "logTraffic", 0 ) ) {
- readingsBulkUpdate( $hash, "callReply", "" );
- }
- }
- # decode data
- if ( defined($data) ) {
- # strip junk from the beginning
- $data =~ s/^\)\]\}\',\n*//;
- if ( AttrVal( $name, "logTraffic", 0 ) ) {
- Log3 $hash, 5, "IOhomecontrol $name: reply $data";
- readingsBulkUpdate( $hash, "callReply", $data );
- }
- eval { $result = decode_json $data };
- if ($@) {
- $err = "malformed JSON string";
- $data = undef;
- Log3 $hash, 5,
- "IOhomecontrol $name: $url, call returned malformed JSON";
- readingsBulkUpdate( $hash, "callResult", "error ($err)" );
- }
- else {
- Log3 $hash, 5, "IOhomecontrol $name: $url, call succeeded";
- readingsBulkUpdate( $hash, "callResult", "success" );
- IOhomecontrol_setDeviceResult( $hash, $result );
- }
- }
- readingsEndUpdate( $hash, 1 );
- # callbacks
- foreach my $callback ( @{$callbacks} ) {
- $callback->( ( $hash, $httpParams, $err, $result ) );
- }
- }
- #####################################
- # Command Queue
- #####################################
- sub IOhomecontrol_queueLength($) {
- my ($hash) = @_;
- return scalar @{ $hash->{fhem}{".queue"} };
- }
- sub IOhomecontrol_loggedIn($) {
- my ($hash) = @_;
- return defined( $hash->{fhem}{".token"} );
- }
- sub IOhomecontrol_updateStateReadings($$) {
- my ( $hash, $msg ) = @_;
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "state", $msg );
- readingsEndUpdate( $hash, 1 );
- }
- sub IOhomecontrol_updateQueueReadings($) {
- my ($hash) = @_;
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "queueLength",
- IOhomecontrol_queueLength($hash) );
- readingsEndUpdate( $hash, 1 );
- }
- sub IOhomecontrol_updateLoggedInReadings($) {
- my ($hash) = @_;
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "loggedIn",
- IOhomecontrol_loggedIn($hash) ? "yes" : "no" );
- readingsEndUpdate( $hash, 1 );
- }
- sub IOhomecontrol_enqueue($$$$$) {
- my ( $hash, $api, $action, $params, $callbacks ) = @_;
- # if a single function reference is passed, put it in an array
- # this is for the developer's convenience
- if ( ref($callbacks) ne 'ARRAY' ) {
- my @callbacks = ($callbacks);
- $callbacks = \@callbacks;
- }
- my $name = $hash->{NAME};
- my %job = (
- api => $api,
- action => $action,
- params => $params,
- callbacks => $callbacks
- );
- push @{ $hash->{fhem}{".queue"} }, \%job;
- Log3 $hash, 5,
- "IOhomecontrol $name: job enqueued, API $api, action $action";
- IOhomecontrol_updateQueueReadings($hash);
- }
- sub IOhomecontrol_dequeue($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $jobref = shift @{ $hash->{fhem}{".queue"} };
- my $api = $jobref->{api};
- my $action = $jobref->{action};
- Log3 $hash, 5,
- "IOhomecontrol $name: job dequeued, API $api, action $action";
- IOhomecontrol_updateQueueReadings($hash);
- return $jobref;
- }
- sub IOhomecontrol_clearQueue($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my @queue = ();
- $hash->{fhem}{".queue"} = \@queue;
- Log3 $hash, 5, "IOhomecontrol $name: queue cleared";
- IOhomecontrol_updateQueueReadings($hash);
- }
- sub IOhomecontrol_runJob($$$) {
- my ( $hash, $jobref, $callback ) = @_;
- my $name = $hash->{NAME};
- my $callbacks = $jobref->{callbacks};
- push @{$callbacks}, $callback;
- IOhomecontrol_Call( $hash, $jobref->{api}, $jobref->{action},
- $jobref->{params}, $callbacks );
- }
- sub IOhomecontrol_Login($$) {
- my ( $hash, $callback ) = @_;
- my $name = $hash->{NAME};
- Log3 $hash, 5, "IOhomecontrol $name: Logging in...";
- my $password = $hash->{fhem}{".password"};
- my $params = { "password" => $password };
- my @callbacks = ( \&IOhomecontrol_LoginCallback, $callback );
- IOhomecontrol_Call( $hash, "auth", "login", $params, \@callbacks );
- }
- sub IOhomecontrol_LoginCallback($$$$) {
- my ( $hash, $httpParams, $err, $result ) = @_;
- my $name = $hash->{NAME};
- if ( defined($err) ) {
- Log3 $hash, 2, "IOhomecontrol $name: Login failed ($err)";
- }
- else {
- my $token = $result->{token};
- if ( defined($token) ) {
- Log3 $hash, 5, "IOhomecontrol $name: Login successful";
- $hash->{fhem}{".token"} = $token;
- }
- else {
- Log3 $hash, 2,
- "IOhomecontrol $name: Login failed (no token received)";
- IOhomecontrol_clearQueue($hash); # forget
- Log3 $hash, 2, "IOhomecontrol $name: pending commands cancelled";
- }
- }
- IOhomecontrol_updateLoggedInReadings($hash);
- }
- sub IOhomecontrol_Logout($$) {
- my ( $hash, $callback ) = @_;
- my $name = $hash->{NAME};
- Log3 $hash, 5, "IOhomecontrol $name: Logging out...";
- my $params = {};
- my @callbacks = ( \&IOhomecontrol_LogoutCallback, $callback );
- IOhomecontrol_Call( $hash, "auth", "logout", $params, \@callbacks );
- }
- sub IOhomecontrol_LogoutCallback($$$$) {
- my ( $hash, $httpParams, $err, $result ) = @_;
- my $name = $hash->{NAME};
- if ( defined($err) ) {
- Log3 $hash, 2, "IOhomecontrol $name: Logout failed ($err)";
- }
- else {
- $hash->{fhem}{".token"} = undef;
- }
- IOhomecontrol_updateLoggedInReadings($hash);
- }
- sub IOhomecontrol_runQueue($) {
- my ($hash) = @_;
- return if ( $hash->{fhem}{".running"} );
- $hash->{fhem}{".running"} = 1;
- IOhomecontrol_updateStateReadings( $hash, "running" );
- IOhomecontrol_processQueue($hash);
- }
- sub IOhomecontrol_processQueue($) {
- my ($hash) = @_;
- if ( IOhomecontrol_queueLength($hash) > 0 ) {
- if ( IOhomecontrol_loggedIn($hash) ) {
- # already logged in => run jon
- IOhomecontrol_runJob(
- $hash,
- IOhomecontrol_dequeue($hash),
- \&IOhomecontrol_processQueue
- );
- }
- else {
- # not yet logged on => login
- IOhomecontrol_Login( $hash, \&IOhomecontrol_processQueue );
- }
- }
- else {
- # queue empty
- if ( IOhomecontrol_loggedIn($hash) ) {
- # still logged in => logout
- IOhomecontrol_Logout( $hash, \&IOhomecontrol_processQueue );
- }
- else {
- # processing ended => idle
- $hash->{fhem}{".running"} = 0;
- IOhomecontrol_updateStateReadings( $hash, "idle" );
- }
- }
- }
- #####################################
- # Get, Set
- #####################################
- ### scenes
- sub IOhomecontrol_makeScenes($$) {
- my ( $hash, $data ) = @_;
- my $sc = {};
- if ( defined($data) ) {
- #Debug "data: " . Dumper $data;
- foreach my $item ( @{$data} ) {
- #Debug "data item: " . Dumper $item;
- my $name = $item->{name};
- my $id = $item->{id};
- #Debug "$id: $name";
- $sc->{$id} = $name;
- }
- my $sns = "";
- foreach my $id ( sort keys %{$sc} ) {
- $sns .= "," if ($sns);
- $sns .= sprintf( "%d: %s", $id, $sc->{$id} );
- }
- readingsSingleUpdate( $hash, "scenes", $sns, 1 );
- }
- $hash->{fhem}{".scenes"} = $sc;
- return $sc; # a hash reference to id => name
- }
- sub IOhomecontrol_getScenes($) {
- my $hash = shift;
- IOhomecontrol_enqueue( $hash, "scenes", "get", {},
- \&IOhomecontrol_getScenesCallback );
- IOhomecontrol_runQueue($hash);
- }
- sub IOhomecontrol_getScenesCallback($$$$) {
- my ( $hash, $httpParams, $err, $result ) = @_;
- my $name = $hash->{NAME};
- if ( defined($err) ) {
- Log3 $hash, 2, "IOhomecontrol $name: getting scenes failed ($err)";
- }
- else {
- $hash->{fhem}{".scenes"} =
- IOhomecontrol_makeScenes( $hash, $result->{data} );
- }
- }
- sub IOhomecontrol_runSceneById($$$;$) {
- my ( $hash, $id, $sn, $callback ) = @_;
- my $name = $hash->{NAME};
- Log3 $hash, 5, "IOhomecontrol $name: running scene id $id, name $sn";
- my @callbacks= (\&IOhomecontrol_runSceneByIdCallback);
- push @callbacks, $callback if(defined($callback));
- IOhomecontrol_enqueue(
- $hash, "scenes", "run",
- { id => $id },
- \@callbacks
- );
- IOhomecontrol_runQueue($hash);
- }
- sub IOhomecontrol_runSceneByIdCallback($$$$) {
- my ( $hash, $httpParams, $err, $result ) = @_;
- my $name = $hash->{NAME};
- my $id= $httpParams->{params}{id};
- my $sn= $hash->{fhem}{".scenes"}->{$id};
- if ( defined($err) ) {
- Log3 $hash, 2, "IOhomecontrol $name: running scene id $id, name $sn, failed ($err)";
- }
- else {
- Log3 $hash, 5, "IOhomecontrol $name: running scene id $id, name $sn, completed";
- my $id= $httpParams->{params}{id};
- readingsSingleUpdate( $hash, "lastScene", $id, 1 );
- }
- }
- ### log
- sub IOhomecontrol_logLength($) {
- my ($hash) = @_;
- return scalar @{ $hash->{fhem}{".log"} };
- }
- sub IOhomecontrol_updateLogReadings($) {
- my ($hash) = @_;
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "logLength", IOhomecontrol_logLength($hash) );
- readingsEndUpdate( $hash, 1 );
- }
- sub IOhomecontrol_getLog($) {
- my $hash = shift;
- IOhomecontrol_enqueue( $hash, "settings", "getLog", {},
- \&IOhomecontrol_getLogCallback );
- IOhomecontrol_runQueue($hash);
- }
- sub IOhomecontrol_logEntry($) {
- my ($e) = @_; # log entry as hash reference
- return sprintf( "%s: %s, %s %s",
- $e->{time}, $e->{type}, $messages->{ $e->{text} },
- $e->{opt} );
- }
- sub IOhomecontrol_getLogCallback($$$$) {
- my ( $hash, $httpParams, $err, $result ) = @_;
- my $name = $hash->{NAME};
- if ( defined($err) ) {
- Log3 $hash, 2, "IOhomecontrol $name: getting log failed ($err)";
- }
- else {
- $hash->{fhem}{".log"} = $result->{data};
- }
- }
- #####################################
- sub IOhomecontrol_Get($@) {
- my ( $hash, $argsref, undef ) = @_;
- my @a = @{$argsref};
- return "get needs at least one parameter" if ( @a < 2 );
- my $name = $a[0];
- my $cmd = $a[1];
- my $arg = ( $a[2] ? $a[2] : "" );
- my @args = @a;
- shift @args;
- shift @args;
- my $answer = "";
- if ( $cmd eq "sceneList" ) {
- my $sc = $hash->{fhem}{".scenes"};
- if ( defined($sc) ) {
- foreach my $id ( sort keys %{$sc} ) {
- $answer .= "\n" if ($answer);
- $answer .= sprintf( "%2d: %s", $id, $sc->{$id} );
- }
- }
- }
- elsif ( $cmd eq "scenes" ) {
- IOhomecontrol_getScenes($hash);
- }
- elsif ( $cmd eq "log" ) {
- IOhomecontrol_getLog($hash);
- }
- elsif ( $cmd eq "showLog" ) {
- my @log = @{ $hash->{fhem}{".log"} };
- my $log = join( "\n", map( IOhomecontrol_logEntry($_), @log ) );
- return $log;
- }
- elsif ( $cmd eq "password" ) {
- $hash->{fhem}{".password"} = IOhomecontrol_getPassword($hash);
- return "password read from file " . $hash->{fhem}{".pwfile"};
- }
- else {
- return
- "Unknown argument $cmd, choose one of scenes:noArg sceneList:noArg log:noArg showLog:noArg password:noArg";
- }
- return $answer;
- }
- #####################################
- # sub IOhomecontrol_Attr($@) {
- #
- # my @a = @_;
- # my $hash= $defs{$a[1]};
- # my $name= $hash->{NAME};
- #
- # if($a[0] eq "set") {
- # if($a[2] eq "") {
- # }
- # }
- #
- # return undef;
- # }
- #####################################
- sub IOhomecontrol_getSetCmds($) {
- my $hash = shift;
- my $name = $hash->{NAME};
- my $attr = AttrVal( $name, "setCmds", "" );
- my ( undef, $setCmds ) = parseParams( $attr, "," );
- return $setCmds;
- }
- sub IOhomecontrol_setScene($$;$) {
- my ( $hash, $id, $callback ) = @_;
- my $sc = $hash->{fhem}{".scenes"};
- return "No scenes available." unless ( defined($sc) );
- if ( $id !~ /^\d+$/ ) {
- my %cs = reverse %{$sc};
- $id = $cs{$id};
- }
- my $sn = $sc->{$id};
- if ( defined($sn) ) {
- IOhomecontrol_runSceneById( $hash, $id, $sn, $callback );
- }
- else {
- return "No such scene $id";
- }
- }
- sub IOhomecontrol_Set($$$) {
- my ( $hash, $argsref, undef ) = @_;
- my @a = @{$argsref};
- return "set needs at least one parameter" if ( @a < 2 );
- my $name = shift @a;
- my $cmd = shift @a;
- my $setCmds = IOhomecontrol_getSetCmds($hash);
- my $usage = "Unknown argument $cmd, choose one of scene "
- . join( " ", ( keys %{$setCmds} ) );
- if ( exists( $setCmds->{$cmd} ) ) {
- readingsSingleUpdate( $hash, "state", $cmd, 1 );
- my $subst = $setCmds->{$cmd};
- Log3 $hash, 5,
- "IOhomecontrol $name: substitute set command $cmd by $subst";
- ( $argsref, undef ) = parseParams($subst);
- @a = @{$argsref};
- $cmd = shift @a;
- }
- if ( $cmd eq "scene" ) {
- if ($#a) {
- return "Command scene needs exactly one argument.";
- }
- else {
- my $id = $a[0];
- IOhomecontrol_setScene( $hash, $id );
- }
- }
- else {
- return $usage;
- }
- return undef;
- }
- #####################################
- 1;
- =pod
- =item device
- =item summary control IOhomecontrol devices via REST API
- =item summary_DE IOhomecontrol-Geräte mittels REST-API steuern
- =begin html
- <a name="IOhomecontrol"></a>
- <h3>IOhomecontrol</h3>
- <ul>
- <a name="IOhomecontroldefine"></a>
- <b>Define</b><br><br>
- <ul>
- <code>define <name> IOhomecontrol <model> <host> <pwfile> </code><br><br>
- Defines an IOhomecontrol interface device (gateway) to communicate with
- IOhomecontrol devices.
- <code><model></code> is a placeholder for future amendments.
- Currently only the Velux Integra KLF200 Interface model <code>KLF200</code> is supported
- as a gateway.
- <code><host></code> is the IP address or hostname of the IOhomecontrol
- interface device (gateway).
- <code><pwfile></code> is a file that contains the password to log into the device.<br><br>
- Example:
- <ul>
- <code>define myKLF200 IOhomecontrol KLF200 192.168.0.91 /opt/fhem/etc/veluxpw.txt</code><br>
- </ul>
- <br><br>
- </ul>
- <a name="IOhomecontrolset"></a>
- <b>Set</b><br><br>
- <ul>
- <code>set <name> scene <id></code>
- <br><br>
- Runs the scene identified by <code><id></code> which can be either the numeric id of the scene or the scene's name.
- <br><br>
- Examples:
- <ul>
- <code>set velux scene 1</code><br>
- <code>set velux scene "all shutters down"</code><br>
- </ul>
- <br>
- Scene names with blanks must be enclosed in double quotes.
- <br><br>
- </ul>
- <a name="IOhomecontrolget"></a>
- <b>Get</b><br><br>
- <ul>
- <code>get <name> scenes</code>
- <br><br>
- Retrieves the ids and names of the scenes from the device. This is done
- automatically after FHEM is initialized. So you should need this only
- if you have altered scenes in the interface device.
- <br><br>
- Example:
- <ul>
- <code>get myKLF200 scenes</code><br>
- </ul>
- <br><br>
- <code>get <name> sceneList</code>
- <br><br>
- Displays the scenes.
- <br><br>
- <code>get <name> log</code>
- <br><br>
- Retrieves the event log from the device.
- <br><br>
- <code>get <name> showLog</code>
- <br><br>
- Displays the event log.
- <br><br>
- <code>get <name> password</code>
- <br><br>
- Reads the password from the password file <pwfile>. This is done
- automatically after FHEM is initialized. So you should need this only
- if you have altered the password in the file.
- </ul>
- <br><br>
- <a name="IOhomecontrolattr"></a>
- <b>Attributes</b>
- <br><br>
- <ul>
- <li>setCmds: a comma-separated list of set command definitions.
- Every definition is of the form <code><shorthand>=<command></code>.
- This defines a new single-word command <code><shorthand></code> as a substitute for <code><command></code>.<br>
- Example: <code>attr velux setCmds evening=scene "close all",morning=scene "open all"</code><br>
- <br></li>
- <li>logTraffic: if set to a nonzero value, request and reply JSON strings
- are logged with log level 5 and stored in the <code>callRequest</code> and
- <code>callReply</code> readings. Use with caution because the password is
- transmitted in plain text in the authentication request.
- <br><br></li>
- <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
- </ul>
- <br><br>
- </ul>
- =end html
- =cut
|