diff --git a/fhem/CHANGED b/fhem/CHANGED index 2826964ec..8b6a3089f 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - change: 83_IOhomecontrol, 84_IOhomecontrolDevice: complete rewrite and + extended features - feature: 42_AptToDate: add Attribut upgradeListReading for Reading in JSON - change: 98_DOIFtools: added shortcut remove_DOIFtoolsLog to commandref - change: 01_FHEMWEB.pm: default plotEmbed to 0 (Forum #82351) @@ -23,7 +25,7 @@ - changed: 14_SD_WS09: standard battery readings - changed: 41_Oregon: standard battery readings - change: 76_SMAInverter: Internal MODEL added - - feature: 49_SSCam: V5.3.0, new createStreamDev generic, control elements + - feature: 49_SSCam: V5.3.0, new createStreamDev generic, control elements for runView content within fhemweb, some new CamLive- Readings, minor fixes - change: 95_Dashboard: V3.10.1, added FW_hideDisplayName, Forum #88727 @@ -33,10 +35,10 @@ - change: 82_LGTV_WebOS: change part of code - bugfix: 49_SSCam: fix state turns to "off" even though cam is disabled - bugfix: 38_netatmo: fixed camera recording and notification settings - - feature: 36_WMBUS: support for WMBUS type C and Kamstrup Multical 21 + - feature: 36_WMBUS: support for WMBUS type C and Kamstrup Multical 21 encoding. - ATTENTION: decryption has changed, please install - the perl modules Crypt::Mode::CBC and + ATTENTION: decryption has changed, please install + the perl modules Crypt::Mode::CBC and Crypt::Mode::CTR if you want to decrypt messages. (sudo cpan -i Crypt::Mode::CBC Crypt::Mode::CTR) - change: 93_DbLog: commandref hint for special character usage in passwords @@ -58,16 +60,16 @@ - feature: 98_Text2Speech: added Duch language for Google TTS - feature: 74_UnifiSwitch: initial version - feature: 74_Unifi: new child-Module UnifiSwitch - new reading UC_newClients for new clients - block clients by mac-address + new reading UC_newClients for new clients + block clients by mac-address - change: 98_dewpoint: new attribute legacyStateHandling - - feature: 93_DbRep: V7.18.0, possible use of y:(\d) in timeDiffToNow, + - feature: 93_DbRep: V7.18.0, possible use of y:(\d) in timeDiffToNow, timeOlderThan, delEntries considers executeBeforeDump, executeAfterDump, minor fixes - feature: 49_SSCam: PTZ-Panel integrated into created StreamDevice - - fixed: 74_Unifi: import encode_json for newest libs - - change: 10_KNX: new major release. See commandref for details. The - following attributes are deprecated now: readonly, + - fixed: 74_Unifi: import encode_json for newest libs + - change: 10_KNX: new major release. See commandref for details. The + following attributes are deprecated now: readonly, listenonly, slider and useSetExtensions. - change: 36_WMBUS: standard battery readings - change: 32_withings/38_netatmo/72_XiaomiDevice: standard battery readings diff --git a/fhem/FHEM/83_IOhomecontrol.pm b/fhem/FHEM/83_IOhomecontrol.pm index 32ec4989d..6c896b4c3 100644 --- a/fhem/FHEM/83_IOhomecontrol.pm +++ b/fhem/FHEM/83_IOhomecontrol.pm @@ -23,349 +23,981 @@ # ############################################################################## -package main; +=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 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) = @_; + my ($hash) = @_; - $hash->{DefFn} = "IOhomecontrol_Define"; - $hash->{UndefFn} = "IOhomecontrol_Undef"; - $hash->{GetFn} = "IOhomecontrol_Get"; - $hash->{SetFn} = "IOhomecontrol_Set"; - $hash->{parseParams} = 1; - #$hash->{AttrFn} = "IOhomecontrol_Attr"; - $hash->{AttrList} = "setCmds " . $readingFnAttributes; -} - -##################################### -sub IOhomecontrol_Define($$) { - - # define IOhomecontrol - my ($hash, $argref, undef) = @_; - - my @def= @{$argref}; - if($#def != 4) { - my $msg = "wrong syntax: define IOhomecontrol "; - Log 2, $msg; - return $msg; - } - - my $name = $def[0]; - my $unused = $def[2]; - my $host = $def[3]; - my $pwfile = $def[4]; - - $hash->{"host"}= $host; - $hash->{"pwfile"}= $pwfile; - $hash->{fhem}{".token"}= undef; - $hash->{fhem}{".scenes"}= undef; - - $hash->{STATE} = "Initialized"; - - return undef; -} - - -##################################### -sub IOhomecontrol_Undef($$) { - return undef; -} - -##################################### -# Internals -##################################### - -sub IOhomecontrol_Exec($$$$) { - - my ($hash, $api, $action, $params) = @_; - my $name = $hash->{NAME}; - my $host = $hash->{"host"}; - my $token = $hash->{fhem}{".token"}; - - # 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; - #Debug "IOhomecontrol $name: sending $json"; - - # build HTTP request - my $httpParams = { - url => "http://$host/api/v1/$api", - timeout => 2, - method => "POST", - noshutdown => 1, - keepalive => 0, - httpversion => "1.1", - header => $header, - data => $json, - }; - - my ($err, $data)= HttpUtils_BlockingGet($httpParams); - - if(defined($err) && $err) { - Log3 $hash, 2, "IOhomecontrol $name returned error: $err"; - return undef; - } else { - if(defined($data) && $data) { - # strip junk from the beginning - $data =~ s/^\)\]\}\',//; - #Debug "IOhomecontrol $name: data $data"; - my $result = decode_json $data; - #Debug Dumper $result; - my $errorsref= $result->{errors}; - my @errors= @{$errorsref}; - $err= ""; - if(@errors) { - $err= join(" ", @errors); - Log3 $hash, 2, "IOhomecontrol $name: API $api, action $action returned errors ($err)."; - }; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "deviceStatus", $result->{deviceStatus}); - readingsBulkUpdate($hash, "errors", $err); - readingsEndUpdate($hash, 1); - return undef if(@errors); - return $result; # this is a hash reference - } else { - Log3 $hash, 2, "IOhomecontrol $name returned no data."; - return undef; - } - } + $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(PWFILE, $pwfile)) { - my @contents= ; - close(PWFILE); - return undef unless @contents; - my $password = $contents[0]; - chomp $password; - return $password; - } else { + 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 IOhomecontrol + my ( $hash, $argref, undef ) = @_; + + my @def = @{$argref}; + if ( $#def != 4 ) { + my $msg = + "wrong syntax: define IOhomecontrol "; + 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_Login($) { - my $hash = shift; - my $name = $hash->{NAME}; - - Log3 $hash, 4, "IOhomecontrol $name: Logging in..."; - my $password = IOhomecontrol_getPassword($hash); - if(!defined($password)) { - Log3 $hash, 2, "IOhomecontrol $name: No password."; - return 0; - } - my $params = { "password" => $password }; - my $result= IOhomecontrol_Exec($hash, "auth", "login", $params); - if(defined($result)) { - my $token= $result->{token}; - Log3 $hash, 4, "IOhomecontrol $name got token: $token"; - $hash->{fhem}{".token"}= $token; - return 1; - } - return 0; -} - -sub IOhomecontrol_Logout($) { - my $hash = shift; - my $name = $hash->{NAME}; - - Log3 $hash, 4, "IOhomecontrol $name: Logging out..."; - my $params = { }; - my $result= IOhomecontrol_Exec($hash, "auth", "logout", $params); - if(defined($result)) { - Log3 $hash, 4, "IOhomecontrol $name logged out."; - $hash->{fhem}{".token"}= undef; - } - return undef; -} - -sub IOhomecontrol_Action($$$$) { - my ($hash, $api, $action, $params) = @_; - my $name = $hash->{NAME}; - - Log3 $hash, 4, "IOhomecontrol $name: API $api, action $action"; - if(IOhomecontrol_Login($hash)) { - my $result= IOhomecontrol_Exec($hash, $api, $action, $params); - IOhomecontrol_Logout($hash); - return $result; - } - return undef; -} - - ##################################### -sub IOhomecontrol_makeScenes($) { - my $hash= shift; - - my $scenes= $hash->{fhem}{".scenes"}; - if(!defined($scenes)) { - $scenes= IOhomecontrol_getScenes($hash); - $hash->{fhem}{".scenes"} = $scenes; - } - my $sc= {}; - if(defined($scenes)) { - my $data= $scenes->{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; +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)."; } - my $sns= ""; - foreach my $id (sort keys %{$sc}) { - $sns.="," if($sns); - $sns.= sprintf("%d: %s", $id, $sc->{$id}); + else { + $err = ""; } - readingsSingleUpdate($hash, "scenes", $sns, 1); - } - return $sc; # a hash id => name + 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; - my $scenes= IOhomecontrol_Action($hash, "scenes", "get", {}); - #Debug Dumper $scenes; - return $scenes; -} + my $hash = shift; + IOhomecontrol_enqueue( $hash, "scenes", "get", {}, + \&IOhomecontrol_getScenesCallback ); + IOhomecontrol_runQueue($hash); +} -sub IOhomecontrol_runSceneById($$$) { - my ($hash, $id, $name)= @_; - IOhomecontrol_Action($hash, "scenes", "run", { id => $id }); - readingsSingleUpdate($hash, "lastScene", $name, 1); -} +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 ( $hash, $argsref, undef ) = @_; - my @a= @{$argsref}; - return "get needs at least one parameter" if(@a < 2); + 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 $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 "scenes") { - $hash->{fhem}{".scenes"}= undef; # forget scenes forces get from device - my $sc= IOhomecontrol_makeScenes($hash); - foreach my $id (sort keys %{$sc}) { - $answer.="\n" if($answer); - $answer.= sprintf("%2d: %s", $id, $sc->{$id}); + 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} ); + } } - } else { - return "Unknown argument $cmd, choose one of scenes:noArg"; - } + } + 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; + 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; + 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 ( $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 @a = @{$argsref}; + return "set needs at least one parameter" if ( @a < 2 ); - 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, 4, "IOhomecontrol $name: substitute set command $cmd by $subst"; - ($argsref, undef)= parseParams($subst); - @a= @{$argsref}; - $cmd= shift @a; - } - - if($cmd eq "scene") { - if($#a) { - Debug Dumper @a; - return "Command scene needs exactly one argument."; - } else { - my $sc= IOhomecontrol_makeScenes($hash); - my $id= $a[0]; - if($id !~ /^\d+$/) { - #Debug "IOhomecontrol $name: looking up scene $id by name..."; - my %cs= reverse %{$sc}; - $id= $cs{$id}; - } - my $sn= $sc->{$id}; - if(defined($sn)) { - Log3 $hash, 4, "IOhomecontrol $name: running scene id $id, name $sn"; - IOhomecontrol_runSceneById($hash, $id, $sn); - } else { - return "No such scene $id"; - } + 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; } - } else { - return $usage - } - - return undef; - + + 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; + } ##################################### @@ -387,11 +1019,18 @@ sub IOhomecontrol_Set($$$) {
    define <name> IOhomecontrol <model> <host> <pwfile>

    - Defines a IOhomecontrol device. <model> is a placeholder for future amendments. <host> is the IP address or hostname of the IOhomecontrol device. <pwfile> is a file that contains the password to log into the device.

    + Defines an IOhomecontrol interface device (gateway) to communicate with + IOhomecontrol devices. + <model> is a placeholder for future amendments. + Currently only the Velux Integra KLF200 Interface model KLF200 is supported + as a gateway. + <host> is the IP address or hostname of the IOhomecontrol + interface device (gateway). + <pwfile> is a file that contains the password to log into the device.

    Example:
      - define velux IOhomecontrol KLF200 192.168.0.91 /opt/fhem/etc/veluxpw.txt
      + define myKLF200 IOhomecontrol KLF200 192.168.0.91 /opt/fhem/etc/veluxpw.txt


@@ -419,23 +1058,50 @@ sub IOhomecontrol_Set($$$) {
    get <name> scenes

    - Retrieves the ids and names of the scenes from the device. + 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.

    - Example: + Example:
      - get velux scenes
      + get myKLF200 scenes
    +

    + get <name> sceneList +

    + Displays the scenes. +

    + get <name> log +

    + Retrieves the event log from the device. +

    + get <name> showLog +

    + Displays the event log. +

    + get <name> password +

    + 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.


- - + + Attributes +

  • setCmds: a comma-separated list of set command definitions. - Every definition is of the form <shorthand>=<command>. This defines a new single-word command <shorthand> as a substitute for <command>.
    - Example: attr velux setCmds up=scene "3.dz.roll2 100%",down=scene "3.dz.roll2 0%"
    - Substituted commands (and only these) are shown in the state reading. This is useful in conjunction with the devStateIcon attribute, e.g. attr velux devStateIcon down:shutter_closed up:shutter_open.
  • + Every definition is of the form <shorthand>=<command>. + This defines a new single-word command <shorthand> as a substitute for <command>.
    + Example: attr velux setCmds evening=scene "close all",morning=scene "open all"
    +
    +
  • logTraffic: if set to a nonzero value, request and reply JSON strings + are logged with log level 5 and stored in the callRequest and + callReply readings. Use with caution because the password is + transmitted in plain text in the authentication request. +

  • readingFnAttributes


diff --git a/fhem/FHEM/84_IOhomecontrolDevice.pm b/fhem/FHEM/84_IOhomecontrolDevice.pm new file mode 100644 index 000000000..36ec54652 --- /dev/null +++ b/fhem/FHEM/84_IOhomecontrolDevice.pm @@ -0,0 +1,210 @@ +# $Id$ + +############################################################################## +# +# 84_IOhomecontrolDevice.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 . +# +############################################################################## + +package main; + +use strict; +use warnings; + +sub IOhomecontrolDevice_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = "IOhomecontrolDevice_Define"; + $hash->{SetFn} = "IOhomecontrolDevice_Set"; + $hash->{parseParams} = 1; + $hash->{AttrList} = "setCmds " . $readingFnAttributes; +} + +sub IOhomecontrolDevice_Define($$) { + + # define IOhomecontrolDevice + my ( $hash, $argref, undef ) = @_; + + my @def = @{$argref}; + if ( $#def != 2 ) { + my $msg = "wrong syntax: define IOhomecontrolDevice "; + Log 2, $msg; + return $msg; + } + + my $name = $def[0]; + my $master = $def[2]; + + my $interface = $defs{$master}; + $hash->{"INTERFACE"} = $interface; + if ( !defined($interface) || $interface->{TYPE} ne "IOhomecontrol" ) { + return "No such IOhomecontrol interface: $master"; + } + else { return; } +} + +sub IOhomecontrolDevice_getSetCmds($) { + my $hash = shift; + my $name = $hash->{NAME}; + + my $attr = AttrVal( $name, "setCmds", "" ); + my ( undef, $setCmds ) = parseParams( $attr, "," ); + return $setCmds; +} + +sub IOhomecontrolDevice_runSceneByIdCallback($$$$) { + my ( $hash, $httpParams, $err, $result ) = @_; + my $name = $hash->{NAME}; + my $interface = $hash->{INTERFACE}; + my $id = $httpParams->{params}{id}; + my $sn = $interface->{fhem}{".scenes"}->{$id}; + if ( defined($err) ) { + Log3 $hash, 2, +"IOhomecontrolDevice $name: running scene id $id, name $sn, failed ($err)"; + } + else { + Log3 $hash, 5, +"IOhomecontrolDevice $name: running scene id $id, name $sn, completed"; + readingsSingleUpdate( $hash, "state", $sn, 1 ); + } +} + +sub IOhomecontrolDevice_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 = IOhomecontrolDevice_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, + "IOhomecontrolDevice $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]; + my $interface = $hash->{INTERFACE}; + return IOhomecontrol_setScene( $interface, $id, + \&IOhomecontrolDevice_runSceneByIdCallback ); + } + } + else { + return $usage; + } + + return undef; + +} + +##################################### + +1; + +=pod +=item device +=item summary control IOhomecontrol devices via IOhomecontrol interface +=item summary_DE IOhomecontrol-Geräte mittels IOhomecontrol-Interface steuern +=begin html + + +

IOhomecontrolDevice

+
    + + + Define

    +
      + define <name> IOhomecontrolDevice <interface>

      + + Defines an IOhomecontrol device. <interface> is the + name of the IOhomecontrol interface device (gateway) that is used to + communicate with the IOhomecontrol devices. +

      + + Example: +
        + define shutter1 IOhomecontrolDevice myKLF200
        +
      +

      +
    + + + Set

    +
      + set <name> scene <id> +

      + Runs the scene identified by <id> which can be either + the numeric id of the scene or the scene's name. +

      + Examples: +
        + set shutter1 scene 1
        + set shutter1 scene "3.dz.roll2 100%"
        +
      +
      + Scene names with blanks must be enclosed in double quotes. +

      +
    + + + Attributes +
    +
    +
      +
    • setCmds: a comma-separated list of set command definitions. + Every definition is of the form <shorthand>=<command>. This defines a new single-word command <shorthand> as a substitute for <command>.
      + Example: attr shutter1 setCmds up=scene "3.dz.roll2 100%",down=scene "3.dz.roll2 0%"
      + Substituted commands (and only these) are shown in the state reading. + This is useful in conjunction with the devStateIcon attribute, + e.g. attr shutter1 devStateIcon down:shutter_closed up:shutter_open.
    • +
      +
    • readingFnAttributes
    • +
    +

    + Full example +
      + define myKLF200 IOhomecontrol KLF200 velux.local /opt/fhem/etc/veluxpw.txt
      + attr myKLF200 verbose 5
      + attr myKLF200 logTraffic 1
      +
      + define shutter1 IOhomecontrolDevice myKLF200
      + attr shutter1 setCmds up=scene "3.dz.roll2 0%",down=scene "3.dz.roll2 100%"
      + attr shutter1 webCmd up:down
      + attr shutter1 devStateIcon down:shutter_closed up:shutter_open
      +
    +

    + +
+ +=end html +=cut