dev #25
@ -1,6 +1,6 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Developed with Kate
|
||||
# Developed with VSCodium and richterger perl plugin
|
||||
#
|
||||
# (c) 2016-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
|
||||
# All rights reserved
|
||||
@ -43,6 +43,14 @@ use warnings;
|
||||
use FHEM::Meta;
|
||||
require FHEM::Devices::Nuki::Bridge;
|
||||
|
||||
use GPUtils qw(GP_Import);
|
||||
|
||||
BEGIN {
|
||||
|
||||
# Import from main context
|
||||
GP_Import(qw( readingFnAttributes ));
|
||||
}
|
||||
|
||||
sub ::NUKIBridge_Initialize { goto &Initialize }
|
||||
|
||||
sub Initialize {
|
||||
@ -68,7 +76,7 @@ sub Initialize {
|
||||
. 'webhookFWinstance:'
|
||||
. $webhookFWinstance . ' '
|
||||
. 'webhookHttpHostname '
|
||||
. $::readingFnAttributes;
|
||||
. $readingFnAttributes;
|
||||
|
||||
return FHEM::Meta::InitMod( __FILE__, $hash );
|
||||
}
|
||||
@ -85,13 +93,27 @@ sub Initialize {
|
||||
<a name="NUKIBridge"></a>
|
||||
<h3>NUKIBridge</h3>
|
||||
<ul>
|
||||
<u><b>NUKIBridge - controls the Nuki Smartlock over the Nuki Bridge</b></u>
|
||||
<u><b>NUKIBridge - controls Nuki Devices (Smartlock, Opener and so on) over the Nuki Bridge</b></u>
|
||||
<br>
|
||||
The Nuki Bridge module connects FHEM to the Nuki Bridge and then reads all the smartlocks available on the bridge. Furthermore, the detected Smartlocks are automatically created as independent devices.
|
||||
The Nuki Bridge module connects FHEM to the Nuki Bridge and then reads all the Nuki devices available on the bridge. Furthermore, the detected Nuki smart devices are automatically created as independent devices.
|
||||
<br><br>
|
||||
<a name="NUKIBridgedefine"></a>
|
||||
<b>Define</b>
|
||||
<b>Define</b><br>
|
||||
There a two ways to define the bridge for use in fhem.<br>
|
||||
<b>first:</b>
|
||||
<ul>
|
||||
<code>define <name> NUKIBridge</code>
|
||||
<br><br>
|
||||
Example:
|
||||
<ul><br>
|
||||
<code>define NBridge1 NUKIBridge</code><br>
|
||||
</ul>
|
||||
<br>
|
||||
This statement creates a NUKIBridge device and activated the Bridge discovery and API activation. Once a bridge has been discovered on the LAN the API done be activated and the API token retrieved. You has to confirm this request by pressing the button on the bridge.<br>
|
||||
After the bridge device is setting up, all available Smartlocks are automatically placed in FHEM.
|
||||
</ul><br>
|
||||
<b>second:</b>
|
||||
<ul>
|
||||
<code>define <name> NUKIBridge <HOST> <API-TOKEN></code>
|
||||
<br><br>
|
||||
Example:
|
||||
@ -106,16 +128,18 @@ sub Initialize {
|
||||
<a name="NUKIBridgereadings"></a>
|
||||
<b>Readings</b>
|
||||
<ul>
|
||||
<li>bridgeAPI - API Version of bridge</li>
|
||||
<li>bridgeType - Hardware bridge / Software bridge</li>
|
||||
<li>currentTime - Current timestamp</li>
|
||||
<li>configAuthSuccess - state of command activat/deactiviate bridge discovery</li>
|
||||
<li>currentGMTime - Current timestamp</li>
|
||||
<li>firmwareVersion - Version of the bridge firmware</li>
|
||||
<li>hardwareId - Hardware ID</li>
|
||||
<li>lastError - Last connected error</li>
|
||||
<li>serverConnected - Flag indicating whether or not the bridge is connected to the Nuki server</li>
|
||||
<li>serverId - Server ID</li>
|
||||
<li>state - state of the bridge device, mostly online</li>
|
||||
<li>uptime - Uptime of the bridge in seconds</li>
|
||||
<li>wifiFirmwareVersion- Version of the WiFi modules firmware</li>
|
||||
<li>wlanConnected - wifi connect?</li>
|
||||
<br>
|
||||
The preceding number is continuous, starts with 0 und returns the properties of <b>one</b> Smartlock.
|
||||
</ul>
|
||||
@ -178,16 +202,18 @@ sub Initialize {
|
||||
<a name="NUKIBridgereadings"></a>
|
||||
<b>Readings</b>
|
||||
<ul>
|
||||
<li>bridgeAPI - API Version der Bridge</li>
|
||||
<li>bridgeType - Hardware oder Software/App Bridge</li>
|
||||
<li>currentTime - aktuelle Zeit auf der Bridge zum zeitpunkt des Info holens</li>
|
||||
<li>configAuthSuccess - status des Kommandos zum aktivieren/deaktivieren des bridge discovery</li>
|
||||
<li>currentGMTime - aktuelle Zeit auf der Bridge zum zeitpunkt des Info holens</li>
|
||||
<li>firmwareVersion - aktuell auf der Bridge verwendete Firmwareversion</li>
|
||||
<li>hardwareId - ID der Hardware Bridge</li>
|
||||
<li>lastError - gibt die letzte HTTP Errormeldung wieder</li>
|
||||
<li>serverConnected - true/false gibt an ob die Hardwarebridge Verbindung zur Nuki-Cloude hat.</li>
|
||||
<li>serverId - gibt die ID des Cloudeservers wieder</li>
|
||||
<li>state - status der bridge zu fhem, zu meist online :-)</li>
|
||||
<li>uptime - Uptime der Bridge in Sekunden</li>
|
||||
<li>wifiFirmwareVersion- Firmwareversion des Wifi Modules der Bridge</li>
|
||||
<li>wlanConnected - Wlan verbunden?</li>
|
||||
<br>
|
||||
Die vorangestellte Zahl ist forlaufend und gibt beginnend bei 0 die Eigenschaften <b>Eines</b> Smartlocks wieder.
|
||||
</ul>
|
||||
@ -241,8 +267,8 @@ sub Initialize {
|
||||
],
|
||||
"release_status": "stable",
|
||||
"license": "GPL_2",
|
||||
"version": "v2.0.0",
|
||||
"x_apiversion": "1.12.3",
|
||||
"version": "v2.0.2",
|
||||
"x_apiversion": "1.13.0",
|
||||
"author": [
|
||||
"Marko Oldenburg <leongaultier@gmail.com>"
|
||||
],
|
||||
@ -256,7 +282,7 @@ sub Initialize {
|
||||
"runtime": {
|
||||
"requires": {
|
||||
"FHEM": 5.00918799,
|
||||
"perl": 5.016,
|
||||
"perl": 5.024,
|
||||
"Meta": 0,
|
||||
"JSON": 0,
|
||||
"Date::Parse": 0
|
||||
|
@ -1,6 +1,6 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Developed with Kate
|
||||
# Developed with VSCodium and richterger perl plugin
|
||||
#
|
||||
# (c) 2016-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
|
||||
# All rights reserved
|
||||
@ -32,11 +32,19 @@ use warnings;
|
||||
use FHEM::Meta;
|
||||
require FHEM::Devices::Nuki::Device;
|
||||
|
||||
use GPUtils qw(GP_Import);
|
||||
|
||||
BEGIN {
|
||||
|
||||
# Import from main context
|
||||
GP_Import(qw( readingFnAttributes ));
|
||||
}
|
||||
|
||||
main::LoadModule('NUKIBridge');
|
||||
|
||||
sub ::NUKIDevice_Initialize { goto &Initialize }
|
||||
|
||||
sub Initialize($) {
|
||||
sub Initialize {
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{Match} = '^{.*}$';
|
||||
@ -52,7 +60,7 @@ sub Initialize($) {
|
||||
'IODev '
|
||||
. 'model:smartlock,opener,smartdoor,smartlock3 '
|
||||
. 'disable:1 '
|
||||
. $::readingFnAttributes;
|
||||
. $readingFnAttributes;
|
||||
|
||||
return FHEM::Meta::InitMod( __FILE__, $hash );
|
||||
}
|
||||
@ -71,7 +79,7 @@ sub Initialize($) {
|
||||
<ul>
|
||||
<u><b>NUKIDevice - Controls the Nuki Smartlock</b></u>
|
||||
<br>
|
||||
The Nuki module connects FHEM over the Nuki Bridge with a Nuki Smartlock or Nuki Opener. After that, it´s possible to lock and unlock the Smartlock.<br>
|
||||
The Nuki module connects FHEM over the Nuki Bridge with a Nuki Smartlock or Nuki Opener. After that, it´s possible to control your Nuki devices<br>
|
||||
Normally the Nuki devices are automatically created by the bridge module.
|
||||
<br><br>
|
||||
<a name="NUKIDevicedefine"></a>
|
||||
@ -79,7 +87,7 @@ sub Initialize($) {
|
||||
<ul><br>
|
||||
<code>define <name> NUKIDevice <Nuki-Id> <IODev-Device> <Device-Type></code>
|
||||
<br><br>
|
||||
Device-Type is 0 for the Smartlock and 2 for the Opener.
|
||||
Device-Type is 0/4 for the Smartlock and 2 for the Opener.
|
||||
<br><br>
|
||||
Example:
|
||||
<ul><br>
|
||||
@ -92,19 +100,41 @@ sub Initialize($) {
|
||||
<br><br>
|
||||
<a name="NUKIDevicereadings"></a>
|
||||
<b>Readings</b>
|
||||
<br>Smartlock
|
||||
<ul>
|
||||
<li>state - Status of the Smartlock or error message if any error.</li>
|
||||
<li>lockState - current lock status uncalibrated, locked, unlocked, unlocked (lock ‘n’ go), unlatched, locking, unlocking, unlatching, motor blocked, undefined.</li>
|
||||
<li>batteryCharging - is the battery charging true/false.</li>
|
||||
<li>batteryPercent - current battry state in percent.</li>
|
||||
<li>batteryState - battery state ok/low</li>
|
||||
<li>deviceType - type name of nuki device smartlock/smartlock3/opener</li>
|
||||
<li>firmwareVersion - version of device firmware</li>
|
||||
<li>name - name of the device</li>
|
||||
<li>nukiid - id of the nuki device</li>
|
||||
<li>paired - paired information false/true</li>
|
||||
<li>rssi - value of rssi</li>
|
||||
<li>state - Status of the Smartlock or error message if any error.</li>
|
||||
<li>stateName - Status of the Smartlock or error message if any error.</li>
|
||||
<li>succes - true, false Returns the status of the last closing command. Ok or not Ok.</li>
|
||||
</ul>
|
||||
<br>Opener
|
||||
<ul>
|
||||
<li>batteryState - battery state ok/low</li>
|
||||
<li>deviceType - type name of nuki device smartlock/smartlock3/opener</li>
|
||||
<li>firmwareVersion - version of device firmware</li>
|
||||
<li>mode - Operation mode (door mode/continuous mode)</li>
|
||||
<li>name - name of the device</li>
|
||||
<li>nukiid - id of the nuki device</li>
|
||||
<li>paired - paired information false/true</li>
|
||||
<li>ringactionState - state of ring (0/1)</li>
|
||||
<li>ringactionTimestamp - timestamp of ring</li>
|
||||
<li>rssi - value of rssi</li>
|
||||
<li>state - Status of the Smartlock or error message if any error.</li>
|
||||
<li>stateName - Status of the Smartlock or error message if any error.</li>
|
||||
<li>succes - true, false Returns the status of the last closing command. Ok or not Ok.</li>
|
||||
<li>batteryCritical - Is the battery in a critical state? True, false</li>
|
||||
<li>batteryState - battery status, ok / low</li>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="NUKIDeviceset"></a>
|
||||
<b>Set</b>
|
||||
<br>Smartlock
|
||||
<ul>
|
||||
<li>statusRequest - retrieves the current state of the smartlock from the bridge.</li>
|
||||
<li>lock - lock</li>
|
||||
@ -113,7 +143,15 @@ sub Initialize($) {
|
||||
<li>unpair - Removes the pairing with a given Smart Lock</li>
|
||||
<li>locknGo - lock when gone</li>
|
||||
<li>locknGoWithUnlatch - lock after the door has been opened</li>
|
||||
<br>
|
||||
</ul>
|
||||
<br>Opener
|
||||
<ul>
|
||||
<li>statusRequest - retrieves the current state of the smartlock from the bridge.</li>
|
||||
<li>activateRto - activate ring to open mode / ringing the bell activates the electric strike actuation </li>
|
||||
<li>deactivateRto - deactivate ring to open mode</li>
|
||||
<li>electricStrikeActuation - electric strike actuation</li>
|
||||
<li>activateContinuousMode - activate Nuki Opener Mode with Ring to Open continuously</li>
|
||||
<li>deactivateContinuousMode - deactivate Ring to Open continuously</li>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="NUKIDeviceattribut"></a>
|
||||
@ -123,6 +161,7 @@ sub Initialize($) {
|
||||
<br>
|
||||
</ul>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
=begin html_DE
|
||||
@ -130,21 +169,21 @@ sub Initialize($) {
|
||||
<a name="NUKIDevice"></a>
|
||||
<h3>NUKIDevice</h3>
|
||||
<ul>
|
||||
<u><b>NUKIDevice - Steuert das Nuki Smartlock</b></u>
|
||||
<u><b>NUKIDevice - Zur Steuerung von Nuki Geräte</b></u>
|
||||
<br>
|
||||
Das Nuki Modul verbindet FHEM über die Nuki Bridge mit einem Nuki Smartlock oder Nuki Opener. Es ist dann möglich das Schloss zu ver- und entriegeln.<br>
|
||||
In der Regel werden die Nuki Devices automatisch durch das Bridgemodul angelegt.
|
||||
Das Nuki Modul verbindet FHEM über die Nuki Bridge mit einem Nuki Smartlock oder Opener. Nach der Einrichtung können diese Geräte gesteuert werden.<br>
|
||||
Die Nuki Geräte werden automatisch nach dem erstellen der Nuki Bridge in FHEM eingerichtet.
|
||||
<br><br>
|
||||
<a name="NUKIDevicedefine"></a>
|
||||
<b>Define</b>
|
||||
<ul><br>
|
||||
<code>define <name> NUKIDevice <Nuki-Id> <IODev-Device> <Device-Type></code>
|
||||
<br><br>
|
||||
Device-Type ist 0 für das Smartlock und 2 f&üuml;r den Opener.
|
||||
Der Device-Type kann 0/4 für ein Smartlock sein oder 2 für den Opener.
|
||||
<br><br>
|
||||
Beispiel:
|
||||
Example:
|
||||
<ul><br>
|
||||
<code>define Haustür NUKIDevice 1 NBridge1 0</code><br>
|
||||
<code>define Frontdoor NUKIDevice 1 NBridge1 0</code><br>
|
||||
</ul>
|
||||
<br>
|
||||
Diese Anweisung erstellt ein NUKIDevice mit Namen Haustür, der NukiId 1 sowie dem IODev Device NBridge1.<br>
|
||||
@ -153,19 +192,40 @@ sub Initialize($) {
|
||||
<br><br>
|
||||
<a name="NUKIDevicereadings"></a>
|
||||
<b>Readings</b>
|
||||
<br>Smartlock
|
||||
<ul>
|
||||
<li>batteryCharging - wird die Batterie geladen true/false.</li>
|
||||
<li>batteryPercent - aktueller Ladestand der Batterie.</li>
|
||||
<li>batteryState - Staus der Batterie ok/low</li>
|
||||
<li>deviceType - der Typenname des Nuki Gerätes smartlock/smartlock3/opener</li>
|
||||
<li>firmwareVersion - Version der Geräte Firmware</li>
|
||||
<li>name - Name des Nuki Gerätes</li>
|
||||
<li>nukiid - die Geräte Id</li>
|
||||
<li>paired - paired Informationen false/true</li>
|
||||
<li>rssi - Wert für die empfangene Signalstärke</li>
|
||||
<li>state - Status des Smartlock bzw . Fehlermeldung von Fehler vorhanden.</li>
|
||||
<li>lockState - aktueller Schließstatus uncalibrated, locked, unlocked, unlocked (lock ‘n’ go), unlatched, locking, unlocking, unlatching, motor blocked, undefined.</li>
|
||||
<li>name - Name des Smart Locks</li>
|
||||
<li>paired - pairing Status des Smart Locks</li>
|
||||
<li>rssi - rssi Wert des Smart Locks</li>
|
||||
<li>succes - true, false Gibt des Status des letzten Schließbefehles wieder. Geklappt oder nicht geklappt.</li>
|
||||
<li>batteryCritical - Ist die Batterie in einem kritischen Zustand? true, false</li>
|
||||
<li>batteryState - Status der Batterie, ok/low</li>
|
||||
<li>succes - true, false. Gibt den Status des letzen Befehls zurück.</li>
|
||||
</ul>
|
||||
<br>Opener
|
||||
<ul>
|
||||
<li>batteryCharging - wird die Batterie geladen true/false.</li>
|
||||
<li>batteryPercent - aktueller Ladestand der Batterie.</li>
|
||||
<li>batteryState - Staus der Batterie ok/low</li>
|
||||
<li>deviceType - der Typenname des Nuki Gerätes smartlock/smartlock3/opener</li>
|
||||
<li>firmwareVersion - Version der Geräte Firmware</li>
|
||||
<li>name - Name des Nuki Gerätes</li>
|
||||
<li>nukiid - die Geräte Id</li>
|
||||
<li>paired - paired Informationen false/true</li>
|
||||
<li>ringactionState - Status der Klingel. Wurde eben geklingelt (0/1)</li>
|
||||
<li>ringactionTimestamp - Zeitstempel des klingelns</li>
|
||||
<li>rssi - Wert f ür die empfangene Signalst ärke</li>
|
||||
<li>state - Status des Opener bzw . Fehlermeldung von Fehler vorhanden.</li>
|
||||
<li>succes - true, false. Gibt den Status des letzen Befehls zurück.</li>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="NUKIDeviceset"></a>
|
||||
<b>Set</b>
|
||||
<br>Smartlock
|
||||
<ul>
|
||||
<li>statusRequest - ruft den aktuellen Status des Smartlocks von der Bridge ab.</li>
|
||||
<li>lock - verschließen</li>
|
||||
@ -174,16 +234,25 @@ sub Initialize($) {
|
||||
<li>unpair - entfernt das pairing mit dem Smart Lock</li>
|
||||
<li>locknGo - verschließen wenn gegangen</li>
|
||||
<li>locknGoWithUnlatch - verschließen nach dem die Falle geöffnet wurde.</li>
|
||||
<br>
|
||||
</ul>
|
||||
<br>Opener
|
||||
<ul>
|
||||
<li>statusRequest - ruft den aktuellen Status des Opener von der Bridge ab.</li>
|
||||
<li>activateRto - aktiviert den ring to open Modus / ein klingeln aktiviert den Türöffner</li>
|
||||
<li>deactivateRto - deaktiviert den ring to open Modus</li>
|
||||
<li>electricStrikeActuation - aktiviert den Türöffner</li>
|
||||
<li>activateContinuousMode - aktiviert dauerhaft öffnen der Tür durch klingeln Modus</li>
|
||||
<li>deactivateContinuousMode - deaktiviert diesen Modus</li>
|
||||
</ul>
|
||||
<br><br>
|
||||
<a name="NUKIDeviceattribut"></a>
|
||||
<b>Attribute</b>
|
||||
<b>Attributes</b>
|
||||
<ul>
|
||||
<li>disable - deaktiviert das Nuki Device</li>
|
||||
<li>disable - disables the Nuki device</li>
|
||||
<br>
|
||||
</ul>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
=end html_DE
|
||||
|
||||
@ -204,7 +273,7 @@ sub Initialize($) {
|
||||
],
|
||||
"release_status": "stable",
|
||||
"license": "GPL_2",
|
||||
"version": "v2.0.0",
|
||||
"version": "v2.0.2",
|
||||
"author": [
|
||||
"Marko Oldenburg <leongaultier@gmail.com>"
|
||||
],
|
||||
@ -218,7 +287,7 @@ sub Initialize($) {
|
||||
"runtime": {
|
||||
"requires": {
|
||||
"FHEM": 5.00918799,
|
||||
"perl": 5.016,
|
||||
"perl": 5.024,
|
||||
"Meta": 0,
|
||||
"JSON": 0,
|
||||
"Date::Parse": 0
|
||||
|
@ -1,4 +1,4 @@
|
||||
UPD 2021-12-05_12:10:05 9217 FHEM/73_NUKIBridge.pm
|
||||
UPD 2021-12-05_12:10:05 7569 FHEM/74_NUKIDevice.pm
|
||||
UPD 2021-12-05_12:14:27 40875 lib/FHEM/Devices/Nuki/Bridge.pm
|
||||
UPD 2021-12-05_12:10:05 15802 lib/FHEM/Devices/Nuki/Device.pm
|
||||
UPD 2021-12-17_11:56:40 10358 FHEM/73_NUKIBridge.pm
|
||||
UPD 2021-12-17_11:56:38 11116 FHEM/74_NUKIDevice.pm
|
||||
UPD 2021-12-11_19:37:43 42761 lib/FHEM/Devices/Nuki/Bridge.pm
|
||||
UPD 2021-12-11_19:37:43 16338 lib/FHEM/Devices/Nuki/Device.pm
|
||||
|
@ -4,25 +4,13 @@ use File::Basename;
|
||||
use POSIX qw(strftime);
|
||||
use strict;
|
||||
|
||||
my @filenames = ( 'FHEM/73_AutoShuttersControl.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Dev.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Roommate.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Shutters.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Shading.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/EventProcessingFunctions.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Helper.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Window.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Dev/Attr.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Dev/Readings.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Shutters/Attr.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Shutters/Readings.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Window/Attr.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Window/Readings.pm',
|
||||
'lib/FHEM/Automation/ShuttersControl/Rainprotection.pm'
|
||||
my @filenames = ( 'FHEM/73_NUKIBridge.pm',
|
||||
'FHEM/74_NUKIDevice.pm',
|
||||
'lib/FHEM/Devices/Nuki/Bridge.pm',
|
||||
'lib/FHEM/Devices/Nuki/Device.pm'
|
||||
);
|
||||
|
||||
my $controlsfile = 'controls_AutoShuttersControl.txt';
|
||||
my $controlsfile = 'controls_NukiSmart.txt';
|
||||
|
||||
open(FH, ">$controlsfile") || return("Can't open $controlsfile: $!");
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Developed with Kate
|
||||
# Developed with VSCodium and richterger perl plugin
|
||||
#
|
||||
# (c) 2016-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
|
||||
# All rights reserved
|
||||
@ -39,25 +39,36 @@ package FHEM::Devices::Nuki::Bridge;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use experimental qw( switch );
|
||||
|
||||
use FHEM::Meta;
|
||||
use HttpUtils;
|
||||
|
||||
use GPUtils qw(GP_Import);
|
||||
|
||||
BEGIN {
|
||||
|
||||
# Import from main context
|
||||
GP_Import(
|
||||
qw( init_done
|
||||
defs
|
||||
modules
|
||||
data
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
# try to use JSON::MaybeXS wrapper
|
||||
# for chance of better performance + open code
|
||||
eval {
|
||||
require JSON::MaybeXS;
|
||||
import JSON::MaybeXS qw( decode_json encode_json );
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# try to use JSON wrapper
|
||||
# for chance of better performance
|
||||
eval {
|
||||
|
||||
# JSON preference order
|
||||
local $ENV{PERL_JSON_BACKEND} =
|
||||
'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP'
|
||||
@ -66,10 +77,7 @@ if ($@) {
|
||||
require JSON;
|
||||
import JSON qw( decode_json encode_json );
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# In rare cases, Cpanel::JSON::XS may
|
||||
# be installed but JSON|JSON::MaybeXS not ...
|
||||
@ -77,10 +85,7 @@ if ($@) {
|
||||
require Cpanel::JSON::XS;
|
||||
import Cpanel::JSON::XS qw(decode_json encode_json);
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# In rare cases, JSON::XS may
|
||||
# be installed but JSON not ...
|
||||
@ -88,10 +93,7 @@ if ($@) {
|
||||
require JSON::XS;
|
||||
import JSON::XS qw(decode_json encode_json);
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# Fallback to built-in JSON which SHOULD
|
||||
# be available since 5.014 ...
|
||||
@ -99,20 +101,17 @@ if ($@) {
|
||||
require JSON::PP;
|
||||
import JSON::PP qw(decode_json encode_json);
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# Fallback to JSON::backportPP in really rare cases
|
||||
require JSON::backportPP;
|
||||
import JSON::backportPP qw(decode_json encode_json);
|
||||
1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
######## Begin Bridge
|
||||
|
||||
@ -138,11 +137,16 @@ my %lockActionsOpener = (
|
||||
);
|
||||
|
||||
sub Define {
|
||||
use version 0.60;
|
||||
|
||||
my $hash = shift;
|
||||
my $def = shift // return;
|
||||
my $version;
|
||||
|
||||
return $@ unless ( FHEM::Meta::SetInternals($hash) );
|
||||
use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' );
|
||||
|
||||
$version = FHEM::Meta::Get( $hash, 'version' );
|
||||
our $VERSION = $version;
|
||||
|
||||
my ( $name, undef, $host, $token ) = split( m{\s+}xms, $def );
|
||||
|
||||
@ -185,7 +189,7 @@ sub Define {
|
||||
$hash->{fhem}{infix} = $infix;
|
||||
}
|
||||
|
||||
$::modules{NUKIBridge}{defptr}{ $hash->{HOST} } = $hash;
|
||||
$modules{NUKIBridge}{defptr}{ $hash->{HOST} } = $hash;
|
||||
|
||||
return;
|
||||
}
|
||||
@ -201,7 +205,7 @@ sub Undef {
|
||||
}
|
||||
|
||||
::RemoveInternalTimer($hash);
|
||||
delete $::modules{NUKIBridge}{defptr}{ $hash->{HOST} };
|
||||
delete $modules{NUKIBridge}{defptr}{ $hash->{HOST} };
|
||||
|
||||
return;
|
||||
}
|
||||
@ -211,8 +215,9 @@ sub Attr {
|
||||
my $name = shift;
|
||||
my $attrName = shift;
|
||||
my $attrVal = shift;
|
||||
my $attrWebhookCheck;
|
||||
|
||||
my $hash = $::defs{$name};
|
||||
my $hash = $defs{$name};
|
||||
my $orig = $attrVal;
|
||||
|
||||
if ( $attrName eq 'disable' ) {
|
||||
@ -251,15 +256,30 @@ sub Attr {
|
||||
}
|
||||
}
|
||||
|
||||
######################
|
||||
#### webhook #########
|
||||
$attrWebhookCheck = AttrWebhookCheck( $cmd, $name, $attrName, $attrVal );
|
||||
|
||||
return (
|
||||
"Invalid value for attribute $attrName: can only by FQDN or IPv4 or IPv6 address"
|
||||
)
|
||||
if ( $attrVal
|
||||
&& $attrName eq 'webhookHttpHostname'
|
||||
&& $attrVal !~ /^([A-Za-z_.0-9]+\.[A-Za-z_.0-9]+)|[0-9:]+$/ );
|
||||
return $attrWebhookCheck
|
||||
if ( defined($attrWebhookCheck) );
|
||||
|
||||
return AttrWebhook( $hash, $cmd, $attrName, $attrVal );
|
||||
}
|
||||
|
||||
sub AttrWebhookCheck {
|
||||
my $cmd = shift;
|
||||
my $name = shift;
|
||||
my $attrName = shift;
|
||||
my $attrVal = shift // return 'No given attribut value';
|
||||
|
||||
######################
|
||||
#### webhook check #########
|
||||
|
||||
### Test ist ohne Funktion. Noch mal drüber schauen
|
||||
# return (
|
||||
# "Invalid value for attribute $attrName: can only by FQDN or IPv4 or IPv6 address"
|
||||
# )
|
||||
# if ( $attrVal
|
||||
# && $attrName eq 'webhookHttpHostname'
|
||||
# && $attrVal !~ m{\A([A-Za-z_.0-9]+\.[A-Za-z_.0-9]+)|[0-9:]+\z}xms );
|
||||
|
||||
return (
|
||||
"Invalid value for attribute $attrName: FHEMWEB instance $attrVal not existing"
|
||||
@ -267,17 +287,21 @@ sub Attr {
|
||||
if (
|
||||
$attrVal
|
||||
&& $attrName eq 'webhookFWinstance'
|
||||
&& ( !defined( $::defs{$attrVal} )
|
||||
|| $::defs{$attrVal}{TYPE} ne 'FHEMWEB' )
|
||||
&& ( !defined( $defs{$attrVal} )
|
||||
|| $defs{$attrVal}{TYPE} ne 'FHEMWEB' )
|
||||
);
|
||||
|
||||
return (
|
||||
"Invalid value for attribute $attrName: needs to be an integer value")
|
||||
if ( $attrVal
|
||||
&& $attrName eq 'webhookPort'
|
||||
&& $attrVal !~ /^\d+$/ );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $attrName =~ /^webhook.*/ ) {
|
||||
sub AttrWebhook {
|
||||
my $hash = shift;
|
||||
my $cmd = shift;
|
||||
my $attrName = shift;
|
||||
my $attrVal = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if ( $attrName =~ m{\Awebhook.*}xms ) {
|
||||
|
||||
my $webhookHttpHostname = (
|
||||
$attrName eq 'webhookHttpHostname' && defined($attrVal)
|
||||
@ -295,13 +319,7 @@ sub Attr {
|
||||
. ::AttrVal( $webhookFWinstance, 'webname', 'fhem' )
|
||||
. '/NUKIBridge' . '-'
|
||||
. $hash->{HOST};
|
||||
$hash->{WEBHOOK_PORT} = (
|
||||
$attrName eq 'webhookPort' ? $attrVal : ::AttrVal(
|
||||
$name, 'webhookPort',
|
||||
::InternalVal( $webhookFWinstance, 'PORT', '' )
|
||||
)
|
||||
);
|
||||
|
||||
$hash->{WEBHOOK_PORT} = ::InternalVal( $webhookFWinstance, 'PORT', '' );
|
||||
$hash->{WEBHOOK_URL} = '';
|
||||
$hash->{WEBHOOK_COUNTER} = 0;
|
||||
|
||||
@ -322,7 +340,7 @@ sub Attr {
|
||||
|
||||
# Write( $hash, 'callback/add', $url, undef, undef )
|
||||
Write( $hash, 'callback/add', '{"param":"' . $url . '"}' )
|
||||
if ($::init_done);
|
||||
if ($init_done);
|
||||
$hash->{WEBHOOK_REGISTER} = 'sent';
|
||||
}
|
||||
else {
|
||||
@ -334,7 +352,6 @@ sub Attr {
|
||||
}
|
||||
|
||||
sub Notify {
|
||||
|
||||
my $hash = shift;
|
||||
my $dev = shift // return;
|
||||
my $name = $hash->{NAME};
|
||||
@ -349,19 +366,15 @@ sub Notify {
|
||||
FirstRun($hash)
|
||||
if (
|
||||
(
|
||||
grep /^INITIALIZED$/,
|
||||
@{$events}
|
||||
or grep /^REREADCFG$/,
|
||||
@{$events}
|
||||
or grep /^MODIFIED.$name$/,
|
||||
@{$events}
|
||||
or grep /^DEFINED.$name$/,
|
||||
@{$events}
|
||||
grep { /^INITIALIZED$/x } @{$events}
|
||||
or grep { /^REREADCFG$/x } @{$events}
|
||||
or grep { /^MODIFIED.$name$/x } @{$events}
|
||||
or grep { /^DEFINED.$name$/x } @{$events}
|
||||
)
|
||||
&& $hash->{HOST} ne 'discover'
|
||||
&& $hash->{TOKEN} ne 'discover'
|
||||
&& $devname eq 'global'
|
||||
&& $::init_done
|
||||
&& $init_done
|
||||
);
|
||||
|
||||
return;
|
||||
@ -378,9 +391,9 @@ sub addExtension {
|
||||
"NUKIBridge ($name) - Registering NUKIBridge for webhook URI $url ..."
|
||||
);
|
||||
|
||||
$::data{FWEXT}{$url}{deviceName} = $name;
|
||||
$::data{FWEXT}{$url}{FUNC} = $func;
|
||||
$::data{FWEXT}{$url}{LINK} = $link;
|
||||
$data{FWEXT}{$url}{deviceName} = $name;
|
||||
$data{FWEXT}{$url}{FUNC} = $func;
|
||||
$data{FWEXT}{$url}{LINK} = $link;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -389,13 +402,13 @@ sub removeExtension {
|
||||
my $link = shift;
|
||||
|
||||
my $url = '/' . $link;
|
||||
my $name = $::data{FWEXT}{$url}{deviceName};
|
||||
my $name = $data{FWEXT}{$url}{deviceName};
|
||||
|
||||
::Log3( $name, 2,
|
||||
"NUKIBridge ($name) - Unregistering NUKIBridge for webhook URL $url..."
|
||||
) if ( defined($name) );
|
||||
|
||||
delete $::data{FWEXT}{$url};
|
||||
delete $data{FWEXT}{$url};
|
||||
|
||||
return;
|
||||
}
|
||||
@ -409,53 +422,60 @@ sub Set {
|
||||
my $endpoint;
|
||||
my $param;
|
||||
|
||||
if ( lc($cmd) eq 'getdevicelist' ) {
|
||||
$cmd = lc($cmd);
|
||||
|
||||
given ($cmd) {
|
||||
when ('getdevicelist') {
|
||||
return 'usage: getDeviceList' if ($arg);
|
||||
$endpoint = 'list';
|
||||
}
|
||||
elsif ( $cmd eq 'info' ) {
|
||||
when ('info') {
|
||||
return 'usage: info' if ($arg);
|
||||
$endpoint = 'info';
|
||||
}
|
||||
elsif ( lc($cmd) eq 'fwupdate' ) {
|
||||
when ('fwupdate') {
|
||||
return 'usage: fwUpdate' if ($arg);
|
||||
$endpoint = 'fwupdate';
|
||||
}
|
||||
elsif ( $cmd eq 'reboot' ) {
|
||||
return 'usage: reboot' if ( defined($arg) );
|
||||
|
||||
when ('reboot') {
|
||||
return 'usage: freboot' if ($arg);
|
||||
$endpoint = 'reboot';
|
||||
}
|
||||
elsif ( lc($cmd) eq 'clearlog' ) {
|
||||
return 'usage: clearLog' if ( defined($arg) );
|
||||
|
||||
when ('clearlog') {
|
||||
return 'usage: clearLog' if ($arg);
|
||||
$endpoint = 'clearlog';
|
||||
}
|
||||
elsif ( lc($cmd) eq 'factoryreset' ) {
|
||||
return 'usage: clearLog' if ( defined($arg) );
|
||||
|
||||
$endpoint = 'factoryReset';
|
||||
when ('factoryreset') {
|
||||
return 'usage: factoryReset' if ($arg);
|
||||
$endpoint = 'factoryreset';
|
||||
}
|
||||
elsif ( lc($cmd) eq 'callbackremove' ) {
|
||||
return 'usage: callbackRemove' if ( split( m{\s+}xms, $arg ) > 1 );
|
||||
|
||||
when ('callbackremove') {
|
||||
return 'usage: callbackRemove'
|
||||
if ( split( m{\s+}xms, $arg ) > 1 );
|
||||
my $id = ( defined($arg) ? $arg : 0 );
|
||||
$endpoint = 'callback/remove';
|
||||
$param = '{"param":"' . $id . '"}';
|
||||
}
|
||||
elsif ( lc($cmd) eq 'configauth' ) {
|
||||
return 'usage: configAuth' if ( split( m{\s+}xms, $arg ) > 1 );
|
||||
|
||||
when ('configauth') {
|
||||
return 'usage: configAuth' if ( split( m{\s+}xms, $arg ) > 1 );
|
||||
$endpoint = 'clearlog';
|
||||
my $configAuth = 'enable=' . ( $arg eq 'enable' ? 1 : 0 );
|
||||
$endpoint = 'configAuth';
|
||||
$param = '{"param":"' . $configAuth . '"}';
|
||||
}
|
||||
else {
|
||||
|
||||
default {
|
||||
my $list = '';
|
||||
$list .= 'info:noArg getDeviceList:noArg ';
|
||||
$list .=
|
||||
'clearLog:noArg fwUpdate:noArg reboot:noArg factoryReset:noArg configAuth:enable,disable'
|
||||
if ( ::ReadingsVal( $name, 'bridgeType', 'Software' ) eq 'Hardware' );
|
||||
if ( ::ReadingsVal( $name, 'bridgeType', 'Software' ) eq
|
||||
'Hardware' );
|
||||
return ( 'Unknown argument ' . $cmd . ', choose one of ' . $list );
|
||||
}
|
||||
}
|
||||
|
||||
Write( $hash, $endpoint, $param )
|
||||
if ( !::IsDisabled($name) );
|
||||
@ -471,24 +491,26 @@ sub Get {
|
||||
|
||||
my $endpoint;
|
||||
|
||||
if ( lc($cmd) eq 'logfile' ) {
|
||||
$cmd = lc($cmd);
|
||||
given ($cmd) {
|
||||
when ( $cmd eq 'logfile' ) {
|
||||
return 'usage: logFile' if ( defined($arg) );
|
||||
|
||||
$endpoint = 'log';
|
||||
}
|
||||
elsif ( lc($cmd) eq 'callbacklist' ) {
|
||||
when ( $cmd eq 'callbacklist' ) {
|
||||
return 'usage: callbackList' if ( defined($arg) );
|
||||
|
||||
$endpoint = 'callback/list';
|
||||
}
|
||||
else {
|
||||
default {
|
||||
my $list = '';
|
||||
$list .= 'callbackList:noArg ';
|
||||
$list .= 'logFile:noArg'
|
||||
if ( ::ReadingsVal( $name, 'bridgeType', 'Software' ) eq 'Hardware' );
|
||||
if ( ::ReadingsVal( $name, 'bridgeType', 'Software' ) eq
|
||||
'Hardware' );
|
||||
|
||||
return 'Unknown argument ' . $cmd . ', choose one of ' . $list;
|
||||
}
|
||||
}
|
||||
|
||||
return Write( $hash, $endpoint, undef );
|
||||
}
|
||||
@ -533,8 +555,9 @@ sub Write {
|
||||
my $hash = shift;
|
||||
my $endpoint = shift // return;
|
||||
my $json = shift;
|
||||
my $decode_json;
|
||||
|
||||
my $decode_json = eval { decode_json($json) }
|
||||
$decode_json = eval { decode_json($json) }
|
||||
if ( defined($json) );
|
||||
|
||||
my $nukiId = $decode_json->{nukiId} // undef;
|
||||
@ -658,34 +681,11 @@ sub BridgeCall {
|
||||
return;
|
||||
}
|
||||
|
||||
sub Distribution {
|
||||
sub DistributionErrHandle1 {
|
||||
my $hash = shift;
|
||||
my $param = shift;
|
||||
my $err = shift;
|
||||
my $json = shift;
|
||||
|
||||
my $hash = $param->{hash};
|
||||
|
||||
# my $doTrigger = $param->{doTrigger};
|
||||
my $name = $hash->{NAME};
|
||||
my $host = $hash->{HOST};
|
||||
|
||||
my $dhash = $hash;
|
||||
|
||||
$dhash = $::modules{NUKIDevice}{defptr}{ $param->{'nukiId'} }
|
||||
if ( defined( $param->{'nukiId'} ) );
|
||||
|
||||
my $dname = $dhash->{NAME};
|
||||
|
||||
::Log3( $name, 4, "NUKIBridge ($name) - Response JSON: $json" );
|
||||
::Log3( $name, 4, "NUKIBridge ($name) - Response ERROR: $err" );
|
||||
::Log3( $name, 4, "NUKIBridge ($name) - Response CODE: $param->{code}" )
|
||||
if ( defined( $param->{code} )
|
||||
&& $param->{code} );
|
||||
|
||||
$hash->{helper}->{iowrite} = 0
|
||||
if ( $hash->{helper}->{iowrite} == 1 );
|
||||
|
||||
::readingsBeginUpdate($hash);
|
||||
|
||||
if ( defined($err) ) {
|
||||
if ( $err ne '' ) {
|
||||
@ -709,6 +709,18 @@ sub Distribution {
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub DistributionErrHandle2 {
|
||||
my $hash = shift;
|
||||
my $err = shift;
|
||||
my $param = shift;
|
||||
my $json = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
# 2021.12.10 13:55:43 1: PERL WARNING: Use of uninitialized value $json in string eq at lib/FHEM/Devices/Nuki/Bridge.pm line 722.
|
||||
# Can't use string ("{"success": false}") as a HASH ref while "strict refs" in use at lib/FHEM/Devices/Nuki/Bridge.pm line 722.
|
||||
if ( ( $json eq '' || $json =~ /Unavailable/i )
|
||||
&& exists( $param->{code} )
|
||||
&& $param->{code} != 200 )
|
||||
@ -758,6 +770,14 @@ sub Distribution {
|
||||
. ' without any data after requesting' );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub DistributionErrHandle3 {
|
||||
my ( $hash, $err, $dhash, $param, $json ) = @_;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if ( ( $json =~ /Error/i )
|
||||
&& exists( $param->{code} ) )
|
||||
{
|
||||
@ -781,6 +801,49 @@ sub Distribution {
|
||||
|
||||
return $param->{code};
|
||||
}
|
||||
}
|
||||
|
||||
sub Distribution {
|
||||
my $param = shift;
|
||||
my $err = shift;
|
||||
my $json = shift;
|
||||
my $errHandle1;
|
||||
my $errHandle2;
|
||||
my $errHandle3;
|
||||
|
||||
my $hash = $param->{hash};
|
||||
|
||||
# my $doTrigger = $param->{doTrigger};
|
||||
my $name = $hash->{NAME};
|
||||
my $host = $hash->{HOST};
|
||||
|
||||
my $dhash = $hash;
|
||||
|
||||
$dhash = $modules{NUKIDevice}{defptr}{ $param->{'nukiId'} }
|
||||
if ( defined( $param->{'nukiId'} ) );
|
||||
|
||||
::Log3( $name, 4, "NUKIBridge ($name) - Response JSON: $json" );
|
||||
::Log3( $name, 4, "NUKIBridge ($name) - Response ERROR: $err" );
|
||||
::Log3( $name, 4, "NUKIBridge ($name) - Response CODE: $param->{code}" )
|
||||
if ( defined( $param->{code} )
|
||||
&& $param->{code} );
|
||||
|
||||
$hash->{helper}->{iowrite} = 0
|
||||
if ( $hash->{helper}->{iowrite} == 1 );
|
||||
|
||||
::readingsBeginUpdate($hash);
|
||||
|
||||
$errHandle1 = DistributionErrHandle1( $hash, $param, $err );
|
||||
return $errHandle1
|
||||
if ($errHandle1);
|
||||
|
||||
$errHandle2 = DistributionErrHandle2( $hash, $err, $param, $json );
|
||||
return $errHandle2
|
||||
if ($errHandle2);
|
||||
|
||||
$errHandle3 = DistributionErrHandle3( $hash, $err, $dhash, $param, $json );
|
||||
return $errHandle3
|
||||
if ($errHandle3);
|
||||
|
||||
delete $hash->{helper}->{lastDeviceAction}
|
||||
if ( defined( $hash->{helper}->{lastDeviceAction} )
|
||||
@ -825,36 +888,13 @@ sub Distribution {
|
||||
return;
|
||||
}
|
||||
|
||||
sub ResponseProcessing {
|
||||
sub HandlingListInfo {
|
||||
my $hash = shift;
|
||||
my $json = shift;
|
||||
my $decode_json = shift;
|
||||
my $endpoint = shift;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $decode_json;
|
||||
|
||||
if ( !$json ) {
|
||||
::Log3( $name, 3, "NUKIBridge ($name) - empty answer received" );
|
||||
return;
|
||||
}
|
||||
elsif ( $json =~ m'HTTP/1.1 200 OK' ) {
|
||||
::Log3( $name, 4, "NUKIBridge ($name) - empty answer received" );
|
||||
return;
|
||||
}
|
||||
elsif ( $json !~ m/^[\[{].*[}\]]$/ ) {
|
||||
::Log3( $name, 3, "NUKIBridge ($name) - invalid json detected: $json" );
|
||||
return ("NUKIBridge ($name) - invalid json detected: $json");
|
||||
}
|
||||
|
||||
$decode_json = eval { decode_json($json) };
|
||||
if ($@) {
|
||||
::Log3( $name, 3, "NUKIBridge ($name) - JSON error while request: $@" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $endpoint eq 'list'
|
||||
|| $endpoint eq 'info' )
|
||||
{
|
||||
if (
|
||||
(
|
||||
ref($decode_json) eq 'ARRAY'
|
||||
@ -873,34 +913,34 @@ sub ResponseProcessing {
|
||||
@buffer = split( '"scanResults": \[', $json )
|
||||
if ( $endpoint eq 'info' );
|
||||
|
||||
my ( $json, $tail ) = ParseJSON( $hash, $buffer[1] );
|
||||
my ( $sjson, $tail ) = ParseJSON( $hash, $buffer[1] );
|
||||
|
||||
while ($json) {
|
||||
while ($sjson) {
|
||||
::Log3( $name, 5,
|
||||
"NUKIBridge ($name) - Decoding JSON message. Length: "
|
||||
. length($json)
|
||||
. length($sjson)
|
||||
. " Content: "
|
||||
. $json );
|
||||
. $sjson );
|
||||
|
||||
::Log3( $name, 5,
|
||||
"NUKIBridge ($name) - Vor Sub: Laenge JSON: "
|
||||
. length($json)
|
||||
. length($sjson)
|
||||
. " Content: "
|
||||
. $json
|
||||
. $sjson
|
||||
. " Tail: "
|
||||
. $tail );
|
||||
|
||||
::Dispatch( $hash, $json, undef )
|
||||
::Dispatch( $hash, $sjson, undef )
|
||||
if ( defined($tail)
|
||||
&& $tail );
|
||||
|
||||
( $json, $tail ) = ParseJSON( $hash, $tail );
|
||||
( $sjson, $tail ) = ParseJSON( $hash, $tail );
|
||||
|
||||
::Log3( $name, 5,
|
||||
"NUKIBridge ($name) - Nach Sub: Laenge JSON: "
|
||||
. length($json)
|
||||
. length($sjson)
|
||||
. " Content: "
|
||||
. $json
|
||||
. $sjson
|
||||
. " Tail: "
|
||||
. $tail );
|
||||
}
|
||||
@ -911,14 +951,49 @@ sub ResponseProcessing {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub ResponseProcessing {
|
||||
my $hash = shift;
|
||||
my $json = shift;
|
||||
my $endpoint = shift;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $decode_json;
|
||||
|
||||
if ( !$json ) {
|
||||
::Log3( $name, 3, "NUKIBridge ($name) - empty answer received" );
|
||||
return;
|
||||
}
|
||||
elsif ( $json =~ m{'HTTP/1.1 200 OK'}xms ) {
|
||||
::Log3( $name, 4, "NUKIBridge ($name) - empty answer received" );
|
||||
return;
|
||||
}
|
||||
elsif ( $json !~ m{^[\[{].*[}\]]$}xms ) {
|
||||
::Log3( $name, 3, "NUKIBridge ($name) - invalid json detected: $json" );
|
||||
return ("NUKIBridge ($name) - invalid json detected: $json");
|
||||
}
|
||||
|
||||
$decode_json = eval { decode_json($json) };
|
||||
if ($@) {
|
||||
::Log3( $name, 3, "NUKIBridge ($name) - JSON error while request: $@" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $endpoint eq 'list'
|
||||
|| $endpoint eq 'info' )
|
||||
{
|
||||
HandlingListInfo( $hash, $json, $decode_json, $endpoint );
|
||||
}
|
||||
elsif ( $endpoint eq 'configAuth' ) {
|
||||
WriteReadings( $hash, $decode_json, $endpoint );
|
||||
}
|
||||
else {
|
||||
|
||||
return ::Log3( $name, 5,
|
||||
::Log3( $name, 5,
|
||||
"NUKIBridge ($name) - Rückgabe Path nicht korrekt: $json" );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub CGI() {
|
||||
@ -927,8 +1002,8 @@ sub CGI() {
|
||||
my $hash;
|
||||
my $name;
|
||||
|
||||
while ( my ( $key, $value ) = each %{ $::modules{NUKIBridge}{defptr} } ) {
|
||||
$hash = $::modules{NUKIBridge}{defptr}{$key};
|
||||
while ( my ( $key, $value ) = each %{ $modules{NUKIBridge}{defptr} } ) {
|
||||
$hash = $modules{NUKIBridge}{defptr}{$key};
|
||||
$name = $hash->{NAME};
|
||||
}
|
||||
|
||||
@ -943,12 +1018,12 @@ sub CGI() {
|
||||
"NUKIBridge WEBHOOK ($name) - empty message received" );
|
||||
return;
|
||||
}
|
||||
elsif ( $json =~ m'HTTP/1.1 200 OK' ) {
|
||||
elsif ( $json =~ m{'HTTP/1.1 200 OK'}xms ) {
|
||||
::Log3( $name, 4,
|
||||
"NUKIBridge WEBHOOK ($name) - empty answer received" );
|
||||
return;
|
||||
}
|
||||
elsif ( $json !~ m/^[\[{].*[}\]]$/ ) {
|
||||
elsif ( $json !~ m{\A[\[{].*[}\]]\z}xms ) {
|
||||
::Log3( $name, 3,
|
||||
"NUKIBridge WEBHOOK ($name) - invalid json detected: $json" );
|
||||
return ("NUKIBridge WEBHOOK ($name) - invalid json detected: $json");
|
||||
@ -957,12 +1032,12 @@ sub CGI() {
|
||||
::Log3( $name, 5,
|
||||
"NUKIBridge WEBHOOK ($name) - Webhook received with JSON: $json" );
|
||||
|
||||
if ( $json =~ m/^\{.*\}$/ ) {
|
||||
if ( $json =~ m{\A\{.*\}\z}xms ) {
|
||||
$hash->{WEBHOOK_COUNTER}++;
|
||||
$hash->{WEBHOOK_LAST} = ::TimeNow();
|
||||
|
||||
::Log3( $name, 3,
|
||||
"NUKIBridge WEBHOOK ($name) - Received webhook for matching NukiId at device $name"
|
||||
"NUKIBridge WEBHOOK ($name) - Received webhook for matching NukiId at IODev $name"
|
||||
);
|
||||
|
||||
::Dispatch( $hash, $json, undef );
|
||||
@ -976,7 +1051,7 @@ sub CGI() {
|
||||
"NUKIBridge WEBHOOK - received malformed request\n$request" );
|
||||
}
|
||||
|
||||
::return( 'text/plain; charset=utf-8', 'Call failure: ' . $request );
|
||||
return ( 'text/plain; charset=utf-8', 'Call failure: ' . $request );
|
||||
}
|
||||
|
||||
sub WriteReadings {
|
||||
@ -986,12 +1061,6 @@ sub WriteReadings {
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $nukiId;
|
||||
my $scanResults;
|
||||
my %response_hash;
|
||||
my $dname;
|
||||
my $dhash;
|
||||
|
||||
::readingsBeginUpdate($hash);
|
||||
|
||||
if ( $endpoint eq 'configAuth' ) {
|
||||
@ -1134,7 +1203,7 @@ sub getCallbackList {
|
||||
for my $cb ( @{ $decode_json->{callbacks} } ) {
|
||||
$aHref = "<a href=\""
|
||||
|
||||
# . $::FW_httpheader->{host}
|
||||
# . main::$FW_httpheader->{host}
|
||||
. "/fhem?cmd=set+"
|
||||
. $name
|
||||
. "+callbackRemove+"
|
||||
@ -1246,23 +1315,24 @@ sub ParseJSON {
|
||||
my $buffer = shift;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $open = 0;
|
||||
my $close = 0;
|
||||
my $jsonopen = 0;
|
||||
my $jsonclose = 0;
|
||||
my $msg = '';
|
||||
my $tail = '';
|
||||
|
||||
if ($buffer) {
|
||||
for my $c ( split //, $buffer ) {
|
||||
|
||||
if ( $open == $close
|
||||
&& $open > 0 )
|
||||
if ( $jsonopen == $jsonclose
|
||||
&& $jsonopen > 0 )
|
||||
{
|
||||
$tail .= $c;
|
||||
::Log3( $name, 5,
|
||||
"NUKIBridge ($name) - $open == $close and $open > 0" );
|
||||
"NUKIBridge ($name) - $jsonopen == $jsonclose and $jsonopen > 0"
|
||||
);
|
||||
|
||||
}
|
||||
elsif ($open == $close
|
||||
elsif ($jsonopen == $jsonclose
|
||||
&& $c ne '{' )
|
||||
{
|
||||
::Log3( $name, 5,
|
||||
@ -1271,17 +1341,17 @@ sub ParseJSON {
|
||||
}
|
||||
else {
|
||||
if ( $c eq '{' ) {
|
||||
$open++;
|
||||
$jsonopen++;
|
||||
}
|
||||
elsif ( $c eq '}' ) {
|
||||
$close++;
|
||||
$jsonclose++;
|
||||
}
|
||||
|
||||
$msg .= $c;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $open != $close ) {
|
||||
if ( $jsonopen != $jsonclose ) {
|
||||
|
||||
$tail = $msg;
|
||||
$msg = '';
|
||||
@ -1406,8 +1476,7 @@ sub BridgeDiscover_getAPIToken {
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $pullApiKeyMessage =
|
||||
'When issuing this API-call the bridge turns on its LED for 30 seconds.
|
||||
The button of the bridge has to be pressed within this timeframe. Otherwise the bridge returns a negative success and no token.';
|
||||
'When issuing this API-call the bridge turns on its LED for 30 seconds. The button of the bridge has to be pressed within this timeframe. Otherwise the bridge returns a negative success and no token.';
|
||||
|
||||
::readingsSingleUpdate( $hash, 'state', $pullApiKeyMessage, 1 );
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Developed with Kate
|
||||
# Developed with VSCodium and richterger perl plugin
|
||||
#
|
||||
# (c) 2016-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
|
||||
# All rights reserved
|
||||
@ -28,24 +28,34 @@ package FHEM::Devices::Nuki::Device;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use experimental qw( switch );
|
||||
|
||||
use FHEM::Meta;
|
||||
|
||||
use GPUtils qw(GP_Import);
|
||||
|
||||
BEGIN {
|
||||
|
||||
# Import from main context
|
||||
GP_Import(
|
||||
qw( init_done
|
||||
defs
|
||||
modules
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
# try to use JSON::MaybeXS wrapper
|
||||
# for chance of better performance + open code
|
||||
eval {
|
||||
require JSON::MaybeXS;
|
||||
import JSON::MaybeXS qw( decode_json encode_json );
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# try to use JSON wrapper
|
||||
# for chance of better performance
|
||||
eval {
|
||||
|
||||
# JSON preference order
|
||||
local $ENV{PERL_JSON_BACKEND} =
|
||||
'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP'
|
||||
@ -54,10 +64,7 @@ if ($@) {
|
||||
require JSON;
|
||||
import JSON qw( decode_json encode_json );
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# In rare cases, Cpanel::JSON::XS may
|
||||
# be installed but JSON|JSON::MaybeXS not ...
|
||||
@ -65,10 +72,7 @@ if ($@) {
|
||||
require Cpanel::JSON::XS;
|
||||
import Cpanel::JSON::XS qw(decode_json encode_json);
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# In rare cases, JSON::XS may
|
||||
# be installed but JSON not ...
|
||||
@ -76,10 +80,7 @@ if ($@) {
|
||||
require JSON::XS;
|
||||
import JSON::XS qw(decode_json encode_json);
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# Fallback to built-in JSON which SHOULD
|
||||
# be available since 5.014 ...
|
||||
@ -87,20 +88,17 @@ if ($@) {
|
||||
require JSON::PP;
|
||||
import JSON::PP qw(decode_json encode_json);
|
||||
1;
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$@ = undef;
|
||||
} or do {
|
||||
|
||||
# Fallback to JSON::backportPP in really rare cases
|
||||
require JSON::backportPP;
|
||||
import JSON::backportPP qw(decode_json encode_json);
|
||||
1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
######## Begin Device
|
||||
|
||||
@ -193,9 +191,12 @@ my %doorsensorStates = (
|
||||
sub Define {
|
||||
my $hash = shift;
|
||||
my $def = shift // return;
|
||||
my $version;
|
||||
|
||||
return $@ unless ( FHEM::Meta::SetInternals($hash) );
|
||||
use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' );
|
||||
|
||||
$version = FHEM::Meta::Get( $hash, 'version' );
|
||||
our $VERSION = $version;
|
||||
|
||||
my ( $name, undef, $nukiId, $deviceType ) = split( m{\s+}xms, $def );
|
||||
return 'too few parameters: define <name> NUKIDevice <nukiId> <deviceType>'
|
||||
@ -227,11 +228,11 @@ sub Define {
|
||||
|
||||
$iodev = $hash->{IODev}->{NAME};
|
||||
|
||||
$hash->{BRIDGEAPI} = $::defs{$iodev}->{BRIDGEAPI}
|
||||
$hash->{BRIDGEAPI} = $defs{$iodev}->{BRIDGEAPI}
|
||||
if ( defined($iodev)
|
||||
&& $iodev );
|
||||
|
||||
my $d = $::modules{NUKIDevice}{defptr}{$nukiId};
|
||||
my $d = $modules{NUKIDevice}{defptr}{$nukiId};
|
||||
|
||||
return
|
||||
'NUKIDevice device '
|
||||
@ -250,11 +251,11 @@ sub Define {
|
||||
::CommandAttr( undef, $name . ' model ' . $deviceTypes{$deviceType} )
|
||||
if ( ::AttrVal( $name, 'model', 'none' ) eq 'none' );
|
||||
|
||||
$::modules{NUKIDevice}{defptr}{$nukiId} = $hash;
|
||||
$modules{NUKIDevice}{defptr}{$nukiId} = $hash;
|
||||
|
||||
GetUpdate($hash)
|
||||
if ( ::ReadingsVal( $name, 'success', 'none' ) eq 'none'
|
||||
&& $::init_done );
|
||||
&& $init_done );
|
||||
|
||||
return;
|
||||
}
|
||||
@ -266,7 +267,7 @@ sub Undef {
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
::Log3( $name, 3, "NUKIDevice ($name) - undefined with NukiId: $nukiId" );
|
||||
delete( $::modules{NUKIDevice}{defptr}{$nukiId} );
|
||||
delete( $modules{NUKIDevice}{defptr}{$nukiId} );
|
||||
|
||||
return;
|
||||
}
|
||||
@ -277,7 +278,7 @@ sub Attr {
|
||||
my $attrName = shift;
|
||||
my $attrVal = shift;
|
||||
|
||||
my $hash = $::defs{$name};
|
||||
my $hash = $defs{$name};
|
||||
my $token = $hash->{IODev}->{TOKEN};
|
||||
|
||||
if ( $attrName eq 'disable' ) {
|
||||
@ -330,17 +331,13 @@ sub Notify {
|
||||
GetUpdate($hash)
|
||||
if (
|
||||
(
|
||||
grep /^INITIALIZED$/,
|
||||
@{$events}
|
||||
or grep /^REREADCFG$/,
|
||||
@{$events}
|
||||
or grep /^MODIFIED.$name$/,
|
||||
@{$events}
|
||||
or grep /^DEFINED.$name$/,
|
||||
@{$events}
|
||||
grep { /^INITIALIZED$/x } @{$events}
|
||||
or grep { /^REREADCFG$/x } @{$events}
|
||||
or grep { /^MODIFIED.$name$/x } @{$events}
|
||||
or grep { /^DEFINED.$name$/x } @{$events}
|
||||
)
|
||||
&& $devname eq 'global'
|
||||
&& $::init_done
|
||||
&& $init_done
|
||||
);
|
||||
|
||||
return;
|
||||
@ -427,7 +424,7 @@ sub Parse {
|
||||
#########################################
|
||||
####### Errorhandling #############
|
||||
|
||||
if ( $json !~ m/^[\[{].*[}\]]$/ ) {
|
||||
if ( $json !~ m{\A[\[{].*[}\]]\z}xms ) {
|
||||
::Log3( $name, 3, "NUKIDevice ($name) - invalid json detected: $json" );
|
||||
return "NUKIDevice ($name) - invalid json detected: $json";
|
||||
}
|
||||
@ -449,14 +446,14 @@ sub Parse {
|
||||
}
|
||||
|
||||
my $nukiId = $decode_json->{nukiId};
|
||||
if ( my $hash = $::modules{NUKIDevice}{defptr}{$nukiId} ) {
|
||||
my $name = $hash->{NAME};
|
||||
if ( my $dhash = $modules{NUKIDevice}{defptr}{$nukiId} ) {
|
||||
my $dname = $dhash->{NAME};
|
||||
|
||||
WriteReadings( $hash, $decode_json );
|
||||
::Log3( $name, 4,
|
||||
"NUKIDevice ($name) - find logical device: $hash->{NAME}" );
|
||||
WriteReadings( $dhash, $decode_json );
|
||||
::Log3( $dname, 4,
|
||||
"NUKIDevice ($dname) - find logical device: $dhash->{NAME}" );
|
||||
|
||||
return $hash->{NAME};
|
||||
return $dhash->{NAME};
|
||||
}
|
||||
else {
|
||||
::Log3( $name, 4,
|
||||
@ -472,16 +469,14 @@ sub Parse {
|
||||
|
||||
::Log3( $name, 5, "NUKIDevice ($name) - parse status message for $name" );
|
||||
|
||||
WriteReadings( $hash, $decode_json );
|
||||
return WriteReadings( $hash, $decode_json );
|
||||
}
|
||||
|
||||
sub WriteReadings {
|
||||
my $hash = shift;
|
||||
my $decode_json = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
sub SmartlockState {
|
||||
############################
|
||||
#### Status des Smartlock
|
||||
my $hash = shift;
|
||||
my $decode_json = shift;
|
||||
|
||||
if ( defined( $hash->{helper}{lockAction} ) ) {
|
||||
my $state;
|
||||
@ -527,6 +522,16 @@ sub WriteReadings {
|
||||
delete $hash->{helper}{lockAction};
|
||||
}
|
||||
|
||||
return $decode_json;
|
||||
}
|
||||
|
||||
sub WriteReadings {
|
||||
my $hash = shift;
|
||||
my $decode_json = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
$decode_json = SmartlockState( $hash, $decode_json );
|
||||
|
||||
::readingsBeginUpdate($hash);
|
||||
|
||||
my $t;
|
||||
@ -555,36 +560,43 @@ sub WriteReadings {
|
||||
&& $t ne 'doorsensorState'
|
||||
&& $t ne 'doorsensorStateName' );
|
||||
|
||||
given ($t) {
|
||||
when ('state') {
|
||||
::readingsBulkUpdate(
|
||||
$hash, $t,
|
||||
(
|
||||
$v =~ m/^[0-9]$/
|
||||
? $lockStates{$v}{ $hash->{DEVICETYPEID} }
|
||||
$v =~ m{\A[0-9]\z}xms
|
||||
? $lockStates{$v}->{ $hash->{DEVICETYPEID} }
|
||||
: $v
|
||||
)
|
||||
) if ( $t eq 'state' );
|
||||
|
||||
::readingsBulkUpdate( $hash, $t, $modes{$v}{ $hash->{DEVICETYPEID} } )
|
||||
if ( $t eq 'mode' );
|
||||
|
||||
::readingsBulkUpdate( $hash, $t, $deviceTypes{$v} )
|
||||
if ( $t eq 'deviceType' );
|
||||
|
||||
::readingsBulkUpdate( $hash, $t, $doorsensorStates{$v} )
|
||||
if ( $t eq 'doorsensorState' );
|
||||
|
||||
::readingsBulkUpdate( $hash, $t, ( $v == 1 ? 'true' : 'false' ) )
|
||||
if ( $t eq 'paired' );
|
||||
|
||||
::readingsBulkUpdate( $hash, $t, ( $v == 1 ? 'true' : 'false' ) )
|
||||
if ( $t eq 'batteryCharging' );
|
||||
|
||||
);
|
||||
}
|
||||
when ('mode') {
|
||||
::readingsBulkUpdate( $hash, $t,
|
||||
$modes{$v}{ $hash->{DEVICETYPEID} } );
|
||||
}
|
||||
when ('deviceType') {
|
||||
::readingsBulkUpdate( $hash, $t, $deviceTypes{$v} );
|
||||
}
|
||||
when ('doorsensorState') {
|
||||
::readingsBulkUpdate( $hash, $t, $doorsensorStates{$v} );
|
||||
}
|
||||
when ('paired') {
|
||||
::readingsBulkUpdate( $hash, $t,
|
||||
( $v == 1 ? 'true' : 'false' ) );
|
||||
}
|
||||
when ('batteryCharging') {
|
||||
::readingsBulkUpdate( $hash, $t,
|
||||
( $v == 1 ? 'true' : 'false' ) );
|
||||
}
|
||||
when ('batteryCritical') {
|
||||
::readingsBulkUpdate( $hash, 'batteryState',
|
||||
( $v == 1 ? 'low' : 'ok' ) )
|
||||
if ( $t eq 'batteryCritical' );
|
||||
|
||||
( $v == 1 ? 'low' : 'ok' ) );
|
||||
}
|
||||
when ('batteryChargeState') {
|
||||
::readingsBulkUpdate( $hash, 'batteryPercent', $v )
|
||||
if ( $t eq 'batteryChargeState' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::readingsEndUpdate( $hash, 1 );
|
||||
|
Loading…
Reference in New Issue
Block a user