Hallo
Ich habe heute aus der Lust raus mal was versucht.
Verzeichnisse alle ausgehend von FHEM root (/opt/fhem)
anlegen von Verzeichnisstruktur lib/Devices/Sensors/BTLE
Mein 74_XiaomiBTLESens.pm Modul zerlegt in
74_XiaomiBTLESens.pm unter FHEM/ mit Inhalt
package main;
use strict;
use warnings;
use POSIX;
use Devices::Sensors::BTLE::XiaomiBTLESens qw(:ALL);
use FHEM::Meta;
sub XiaomiBTLESens_Initialize {
my $hash = shift;
$hash->{SetFn} = \&Devices::Sensors::BTLE::XiaomiBTLESens::Set;
$hash->{GetFn} = \&Devices::Sensors::BTLE::XiaomiBTLESens::Get;
$hash->{DefFn} = \&Devices::Sensors::BTLE::XiaomiBTLESens::Define;
$hash->{NotifyFn} = \&Devices::Sensors::BTLE::XiaomiBTLESens::Notify;
$hash->{UndefFn} = \&Devices::Sensors::BTLE::XiaomiBTLESens::Undef;
$hash->{AttrFn} = \&Devices::Sensors::BTLE::XiaomiBTLESens::Attr;
$hash->{AttrList} =
'interval '
. 'disable:1 '
. 'disabledForIntervals '
. 'hciDevice:hci0,hci1,hci2 '
. 'batteryFirmwareAge:8h,16h,24h,32h,40h,48h '
. 'minFertility '
. 'maxFertility '
. 'minTemp '
. 'maxTemp '
. 'minMoisture '
. 'maxMoisture '
. 'minLux '
. 'maxLux '
. 'sshHost '
. 'psCommand '
. 'model:flowerSens,thermoHygroSens,clearGrassSens,mijiaLYWSD03MMC '
. 'blockingCallLoglevel:2,3,4,5 '
. $readingFnAttributes;
$hash->{parseParams} = 1;
return FHEM::Meta::InitMod( __FILE__, $hash );
}
1;
Ein weiteres Modul XiaomiBTLESens.pm unter lib/Devices/Sensors/BTLE/
package Devices::Sensors::BTLE::XiaomiBTLESens;
use strict;
use warnings;
use POSIX;
use FHEM::Meta;
use GPUtils qw(GP_Import);
use Blocking;
# 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;
# 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'
unless ( defined( $ENV{PERL_JSON_BACKEND} ) );
require JSON;
import JSON qw( decode_json encode_json );
1;
};
if ($@) {
$@ = undef;
# In rare cases, Cpanel::JSON::XS may
# be installed but JSON|JSON::MaybeXS not ...
eval {
require Cpanel::JSON::XS;
import Cpanel::JSON::XS qw(decode_json encode_json);
1;
};
if ($@) {
$@ = undef;
# In rare cases, JSON::XS may
# be installed but JSON not ...
eval {
require JSON::XS;
import JSON::XS qw(decode_json encode_json);
1;
};
if ($@) {
$@ = undef;
# Fallback to built-in JSON which SHOULD
# be available since 5.014 ...
eval {
require JSON::PP;
import JSON::PP qw(decode_json encode_json);
1;
};
if ($@) {
$@ = undef;
# Fallback to JSON::backportPP in really rare cases
require JSON::backportPP;
import JSON::backportPP qw(decode_json encode_json);
1;
}
}
}
}
}
## Import der FHEM Funktionen
#-- Run before package compilation
BEGIN {
# Import from main context
GP_Import(
qw(readingsSingleUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsBeginUpdate
readingsEndUpdate
defs
modules
Log3
CommandAttr
AttrVal
ReadingsVal
IsDisabled
deviceEvents
init_done
gettimeofday
InternalTimer
RemoveInternalTimer
DoTrigger
BlockingKill
BlockingCall
FmtDateTime
readingFnAttributes
makeDeviceName)
);
}
my %XiaomiModels = (
flowerSens => {
'rdata' => '0x35',
'wdata' => '0x33',
'wdataValue' => 'A01F',
'wdatalisten' => 0,
'battery' => '0x38',
'firmware' => '0x38'
},
thermoHygroSens => {
'wdata' => '0x10',
'wdataValue' => '0100',
'wdatalisten' => 1,
'battery' => '0x18',
'firmware' => '0x24',
'devicename' => '0x3'
},
clearGrassSens => {
'rdata' => '0x1e',
'wdata' => '0x10',
'wdataValue' => '0100',
'wdatalisten' => 2,
'battery' => '0x3b',
'firmware' => '0x2a',
'devicename' => '0x3'
},
mijiaLYWSD03MMC => {
'wdata' => '0x38',
'wdataValue' => '0100',
'wdatalisten' => 3,
'battery' => '0x1b',
'firmware' => '0x12',
'devicename' => '0x3'
},
);
my %CallBatteryAge = (
'8h' => 28800,
'16h' => 57600,
'24h' => 86400,
'32h' => 115200,
'40h' => 144000,
'48h' => 172800
);
sub Define {
my $hash = shift;
my $arg_ref = shift;
return $@ if ( !FHEM::Meta::SetInternals($hash) );
use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' );
return 'too few parameters: define <name> XiaomiBTLESens <BTMAC>'
if ( scalar( @{$arg_ref} ) != 3 );
my $name = $arg_ref->[0];
my $mac = $arg_ref->[2];
$hash->{BTMAC} = $mac;
$hash->{VERSION} = version->parse($VERSION)->normal;
$hash->{INTERVAL} = 300;
$hash->{helper}{CallSensDataCounter} = 0;
$hash->{helper}{CallBattery} = 0;
$hash->{NOTIFYDEV} = 'global,' . $name;
$hash->{loglevel} = 4;
readingsSingleUpdate( $hash, 'state', 'initialized', 0 );
CommandAttr( undef, $name . ' room XiaomiBTLESens' )
if ( AttrVal( $name, 'room', 'none' ) eq 'none' );
Log3( $name, 3,
"XiaomiBTLESens ($name) - defined with BTMAC $hash->{BTMAC}" );
$modules{XiaomiBTLESens}{defptr}{ $hash->{BTMAC} } = $hash;
return;
}
sub Undef {
my $hash = shift;
my $arg = shift;
my $mac = $hash->{BTMAC};
my $name = $hash->{NAME};
RemoveInternalTimer($hash);
BlockingKill( $hash->{helper}{RUNNING_PID} )
if ( defined( $hash->{helper}{RUNNING_PID} ) );
delete( $modules{XiaomiBTLESens}{defptr}{$mac} );
Log3( $name, 3, "Sub XiaomiBTLESens_Undef ($name) - delete device $name" );
return;
}
sub Attr {
my ( $cmd, $name, $attrName, $attrVal ) = @_;
my $hash = $defs{$name};
if ( $attrName eq 'disable' ) {
if ( $cmd eq 'set' && $attrVal == 1 ) {
RemoveInternalTimer($hash);
readingsSingleUpdate( $hash, 'state', 'disabled', 1 );
Log3( $name, 3, "XiaomiBTLESens ($name) - disabled" );
}
elsif ( $cmd eq 'del' ) {
Log3( $name, 3, "XiaomiBTLESens ($name) - enabled" );
}
}
elsif ( $attrName eq 'disabledForIntervals' ) {
if ( $cmd eq 'set' ) {
return
'check disabledForIntervals Syntax HH:MM-HH:MM or HH:MM-HH:MM HH:MM-HH:MM ...'
if ( $attrVal !~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/ );
Log3( $name, 3, "XiaomiBTLESens ($name) - disabledForIntervals" );
stateRequest($hash);
}
elsif ( $cmd eq 'del' ) {
Log3( $name, 3, "XiaomiBTLESens ($name) - enabled" );
readingsSingleUpdate( $hash, 'state', 'active', 1 );
}
}
elsif ( $attrName eq 'interval' ) {
RemoveInternalTimer($hash);
if ( $cmd eq 'set' ) {
if ( $attrVal < 120 ) {
Log3( $name, 3,
"XiaomiBTLESens ($name) - interval too small, please use something >= 120 (sec), default is 300 (sec)"
);
return
'interval too small, please use something >= 120 (sec), default is 300 (sec)';
}
else {
$hash->{INTERVAL} = $attrVal;
Log3( $name, 3,
"XiaomiBTLESens ($name) - set interval to $attrVal" );
}
}
elsif ( $cmd eq 'del' ) {
$hash->{INTERVAL} = 300;
Log3( $name, 3,
"XiaomiBTLESens ($name) - set interval to default" );
}
}
elsif ( $attrName eq 'blockingCallLoglevel' ) {
if ( $cmd eq 'set' ) {
$hash->{loglevel} = $attrVal;
Log3( $name, 3,
"XiaomiBTLESens ($name) - set blockingCallLoglevel to $attrVal"
);
}
elsif ( $cmd eq 'del' ) {
$hash->{loglevel} = 4;
Log3( $name, 3,
"XiaomiBTLESens ($name) - set blockingCallLoglevel to default"
);
}
}
return;
}
sub Notify {
my $hash = shift;
my $dev = shift;
my $name = $hash->{NAME};
return stateRequestTimer($hash) if ( IsDisabled($name) );
my $devname = $dev->{NAME};
my $devtype = $dev->{TYPE};
my $events = deviceEvents( $dev, 1 );
return if ( !$events );
stateRequestTimer($hash)
if (
(
(
(
grep /^DEFINED.$name$/,
@{$events}
or grep /^DELETEATTR.$name.disable$/,
@{$events}
or grep /^ATTR.$name.disable.0$/,
@{$events}
or grep /^DELETEATTR.$name.interval$/,
@{$events}
or grep /^DELETEATTR.$name.model$/,
@{$events}
or grep /^ATTR.$name.model.+/,
@{$events}
or grep /^ATTR.$name.interval.[0-9]+/,
@{$events}
)
&& $devname eq 'global'
)
or grep /^resetBatteryTimestamp$/,
@{$events}
)
&& $init_done
|| (
(
grep /^INITIALIZED$/,
@{$events}
or grep /^REREADCFG$/,
@{$events}
or grep /^MODIFIED.$name$/,
@{$events}
)
&& $devname eq 'global'
)
);
CreateParamGatttool( $hash, 'read',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{devicename} )
if (
(
AttrVal( $name, 'model', 'thermoHygroSens' ) eq 'thermoHygroSens'
|| AttrVal( $name, 'model', 'mijiaLYWSD03MMC' ) eq 'mijiaLYWSD03MMC'
)
&& $devname eq $name
&& grep /^$name.firmware.+/,
@{$events}
);
return;
}
sub stateRequest {
my $hash = shift;
my $name = $hash->{NAME};
my %readings;
if ( AttrVal( $name, 'model', 'none' ) eq 'none' ) {
readingsSingleUpdate( $hash, 'state', 'set attribute model first', 1 );
}
elsif ( !IsDisabled($name) ) {
if ( ReadingsVal( $name, 'firmware', 'none' ) ne 'none' ) {
return CreateParamGatttool( $hash, 'read',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{battery} )
if (
CallBattery_IsUpdateTimeAgeToOld(
$hash,
$CallBatteryAge{ AttrVal( $name, 'BatteryFirmwareAge',
'24h' ) }
)
);
if ( $hash->{helper}{CallSensDataCounter} < 1
&& AttrVal( $name, 'model', '' ) ne 'clearGrassSens' )
{
CreateParamGatttool(
$hash,
'write',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{wdata},
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{wdataValue}
);
$hash->{helper}{CallSensDataCounter} =
$hash->{helper}{CallSensDataCounter} + 1;
}
elsif ( $hash->{helper}{CallSensDataCounter} < 1
&& AttrVal( $name, 'model', '' ) eq 'clearGrassSens' )
{
CreateParamGatttool( $hash, 'read',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{rdata},
);
$hash->{helper}{CallSensDataCounter} =
$hash->{helper}{CallSensDataCounter} + 1;
}
else {
$readings{'lastGattError'} = 'charWrite faild';
WriteReadings( $hash, \%readings );
$hash->{helper}{CallSensDataCounter} = 0;
return;
}
}
else {
CreateParamGatttool( $hash, 'read',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{firmware} );
}
}
else {
readingsSingleUpdate( $hash, 'state', 'disabled', 1 );
}
return;
}
sub stateRequestTimer {
my $hash = shift;
my $name = $hash->{NAME};
RemoveInternalTimer($hash);
stateRequest($hash);
InternalTimer( gettimeofday() + $hash->{INTERVAL} + int( rand(300) ),
\&Devices::Sensors::BTLE::XiaomiBTLESens::stateRequestTimer, $hash );
Log3( $name, 4,
"XiaomiBTLESens ($name) - stateRequestTimer: Call Request Timer" );
return;
}
sub Set($$@) {
my $hash = shift;
my $arg_ref = shift;
my $name = shift @$arg_ref;
my $cmd = shift @$arg_ref
// return qq{"set $name" needs at least one argument};
my $mod;
my $handle;
my $value = 'write';
if ( $cmd eq 'devicename' ) {
return 'usage: devicename <name>' if ( scalar( @{$arg_ref} ) < 1 );
$mod = 'write';
$handle = $XiaomiModels{ AttrVal( $name, 'model', '' ) }{devicename};
$value = CreateDevicenameHEX( makeDeviceName( $arg_ref->[0] ) );
}
elsif ( $cmd eq 'resetBatteryTimestamp' ) {
return 'usage: resetBatteryTimestamp' if ( scalar( @{$arg_ref} ) != 0 );
$hash->{helper}{updateTimeCallBattery} = 0;
return;
}
else {
my $list = q{};
$list .= 'resetBatteryTimestamp:noArg'
if ( AttrVal( $name, 'model', 'none' ) ne 'none' );
$list .= ' devicename'
if (
(
AttrVal( $name, 'model', 'thermoHygroSens' ) eq
'thermoHygroSens'
|| AttrVal( $name, 'model', 'mijiaLYWSD03MMC' ) eq
'mijiaLYWSD03MMC'
)
&& AttrVal( $name, 'model', 'none' ) ne 'none'
);
return "Unknown argument $cmd, choose one of $list";
}
CreateParamGatttool( $hash, $mod, $handle, $value );
return;
}
sub Get {
my $hash = shift;
my $arg_ref = shift;
my $name = shift @$arg_ref;
my $cmd = shift @$arg_ref
// return qq{"set $name" needs at least one argument};
my $mod = 'read';
my $handle;
if ( $cmd eq 'sensorData' ) {
return 'usage: sensorData' if ( scalar( @{$arg_ref} ) != 0 );
stateRequest($hash);
}
elsif ( $cmd eq 'firmware' ) {
return 'usage: firmware' if ( scalar( @{$arg_ref} ) != 0 );
$mod = 'read';
$handle = $XiaomiModels{ AttrVal( $name, 'model', '' ) }{firmware};
}
elsif ( $cmd eq 'devicename' ) {
return "usage: devicename" if ( scalar( @{$arg_ref} ) != 0 );
$mod = 'read';
$handle = $XiaomiModels{ AttrVal( $name, 'model', '' ) }{devicename};
}
else {
my $list = q{};
$list .= 'sensorData:noArg firmware:noArg'
if ( AttrVal( $name, 'model', 'none' ) ne 'none' );
$list .= ' devicename:noArg'
if (
(
AttrVal( $name, 'model', 'thermoHygroSens' ) eq
'thermoHygroSens'
|| AttrVal( $name, 'model', 'mijiaLYWSD03MMC' ) eq
'mijiaLYWSD03MMC'
)
&& AttrVal( $name, 'model', 'none' ) ne 'none'
);
return "Unknown argument $cmd, choose one of $list";
}
CreateParamGatttool( $hash, $mod, $handle ) if ( $cmd ne 'sensorData' );
return;
}
sub CreateParamGatttool {
my ( $hash, $mod, $handle, $value ) = @_;
my $name = $hash->{NAME};
my $mac = $hash->{BTMAC};
Log3( $name, 4,
"XiaomiBTLESens ($name) - Run CreateParamGatttool with mod: $mod" );
if ( $mod eq 'read' ) {
$hash->{helper}{RUNNING_PID} = BlockingCall(
\&Devices::Sensors::BTLE::XiaomiBTLESens::ExecGatttool_Run,
$name . '|' . $mac . '|' . $mod . '|' . $handle,
\&Devices::Sensors::BTLE::XiaomiBTLESens::ExecGatttool_Done,
90,
\&Devices::Sensors::BTLE::XiaomiBTLESens::ExecGatttool_Aborted,
$hash
) if ( !exists( $hash->{helper}{RUNNING_PID} ) );
readingsSingleUpdate( $hash, 'state', 'read sensor data', 1 );
Log3( $name, 5,
"XiaomiBTLESens ($name) - Read XiaomiBTLESens_ExecGatttool_Run $name|$mac|$mod|$handle"
);
}
elsif ( $mod eq 'write' ) {
$hash->{helper}{RUNNING_PID} = BlockingCall(
\&Devices::Sensors::BTLE::XiaomiBTLESens::ExecGatttool_Run,
$name . '|'
. $mac . '|'
. $mod . '|'
. $handle . '|'
. $value . '|'
. $XiaomiModels{ AttrVal( $name, 'model', '' ) }{wdatalisten},
\&Devices::Sensors::BTLE::XiaomiBTLESens::ExecGatttool_Done,
90,
\&Devices::Sensors::BTLE::XiaomiBTLESens::ExecGatttool_Aborted,
$hash
) if ( !exists( $hash->{helper}{RUNNING_PID} ) );
readingsSingleUpdate( $hash, 'state', 'write sensor data', 1 );
Log3( $name, 5,
"XiaomiBTLESens ($name) - Write XiaomiBTLESens_ExecGatttool_Run $name|$mac|$mod|$handle|$value"
);
}
return;
}
sub Gatttool_executeCommand {
my $command = join q{ }, @_;
return ( $_ = qx{$command 2>&1}, $? >> 8 );
}
sub ExecGatttool_Run {
my $string = shift;
my ( $name, $mac, $gattCmd, $handle, $value, $listen ) =
split( '\|', $string );
my $sshHost = AttrVal( $name, 'sshHost', 'none' );
my $gatttool;
my $json_notification;
$gatttool = qx(which gatttool) if ( $sshHost eq 'none' );
$gatttool = qx(ssh $sshHost 'which gatttool') if ( $sshHost ne 'none' );
chomp $gatttool;
if ( defined($gatttool) && ($gatttool) ) {
my $cmd;
my $loop;
my @gtResult;
my $wait = 1;
my $sshHost = AttrVal( $name, 'sshHost', 'none' );
my $hci = AttrVal( $name, 'hciDevice', 'hci0' );
$cmd .= "ssh $sshHost '" if ( $sshHost ne 'none' );
$cmd .= "timeout 10 " if ($listen);
$cmd .= "gatttool -i $hci -b $mac ";
$cmd .= "--char-read -a $handle" if ( $gattCmd eq 'read' );
$cmd .= "--char-write-req -a $handle -n $value"
if ( $gattCmd eq 'write' );
$cmd .= " --listen" if ($listen);
# $cmd .= " 2>&1 /dev/null";
$cmd .= " 2>&1";
$cmd .= "'" if ( $sshHost ne 'none' );
$cmd =
"ssh $sshHost 'gatttool -i $hci -b $mac --char-write-req -a 0x33 -n A01F && gatttool -i $hci -b $mac --char-read -a 0x35 2>&1 '"
if ( $sshHost ne 'none'
&& $gattCmd eq 'write'
&& AttrVal( $name, 'model', 'none' ) eq 'flowerSens' );
while ($wait) {
my $grepGatttool;
my $gatttoolCmdlineStaticEscaped =
BTLE_CmdlinePreventGrepFalsePositive("gatttool -i $hci -b $mac");
my $psCommand = AttrVal( $name, 'psCommand', 'ps ax' );
Log3( $name, 5,
"XiaomiBTLESens ($name) - ExecGatttool_Run: Execute Command $psCommand | grep -E $gatttoolCmdlineStaticEscaped"
);
# $grepGatttool = qx(ps ax| grep -E \'$gatttoolCmdlineStaticEscaped\')
$grepGatttool =
qx($psCommand | grep -E \'$gatttoolCmdlineStaticEscaped\')
if ( $sshHost eq 'none' );
# $grepGatttool = qx(ssh $sshHost 'ps ax| grep -E "$gatttoolCmdlineStaticEscaped"')
$grepGatttool =
qx(ssh $sshHost '$psCommand | grep -E "$gatttoolCmdlineStaticEscaped"')
if ( $sshHost ne 'none' );
if ( not $grepGatttool =~ /^\s*$/ ) {
Log3( $name, 3,
"XiaomiBTLESens ($name) - ExecGatttool_Run: another gatttool process is running. waiting..."
);
sleep(1);
}
else {
$wait = 0;
}
}
$loop = 0;
my $returnString;
my $returnCode = 1;
do {
Log3( $name, 5,
"XiaomiBTLESens ($name) - ExecGatttool_Run: call gatttool with command: $cmd and loop $loop"
);
( $returnString, $returnCode ) = Gatttool_executeCommand($cmd);
@gtResult = split( ': ', $returnString );
Log3( $name, 5,
"XiaomiBTLESens ($name) - ExecGatttool_Run: gatttool loop result "
. join q{,}, @gtResult );
$returnCode = 2
if ( !defined( $gtResult[0] ) );
$loop++;
} while ( $loop < 5 && ( $returnCode != 0 && $returnCode != 124 ) );
Log3( $name, 3,
"XiaomiBTLESens ($name) - ExecGatttool_Run: errorcode: \"$returnCode\", ErrorString: \"$returnString\""
) if ( $returnCode != 0 && $returnCode != 124 );
Log3( $name, 4,
"XiaomiBTLESens ($name) - ExecGatttool_Run: gatttool result "
. join q{,}, @gtResult );
$handle = '0x35'
if ( $sshHost ne 'none'
&& $gattCmd eq 'write'
&& AttrVal( $name, 'model', 'none' ) eq 'flowerSens' );
$gattCmd = 'read'
if ( $sshHost ne 'none'
&& $gattCmd eq 'write'
&& AttrVal( $name, 'model', 'none' ) eq 'flowerSens' );
$gtResult[1] = 'no data response'
if ( !defined( $gtResult[1] ) );
if ( $gtResult[1] ne 'no data response' && $listen ) {
( $gtResult[1] ) = split( /\n/, $gtResult[1] );
$gtResult[1] =~ s/\\n//g;
}
$json_notification = encodeJSON( $gtResult[1] );
if ( $gtResult[1] =~ /^([0-9a-f]{2}(\s?))*$/ ) {
return "$name|$mac|ok|$gattCmd|$handle|$json_notification";
}
elsif ( $returnCode == 0 && $gattCmd eq 'write' ) {
if ( $sshHost ne 'none' ) {
ExecGatttool_Run( $name . "|" . $mac . "|read|0x35" );
}
else {
return "$name|$mac|ok|$gattCmd|$handle|$json_notification";
}
}
else {
return "$name|$mac|error|$gattCmd|$handle|$json_notification";
}
}
else {
$json_notification = encodeJSON(
'no gatttool binary found. Please check if bluez-package is properly installed'
);
return "$name|$mac|error|$gattCmd|$handle|$json_notification";
}
return;
}
sub ExecGatttool_Done {
my $string = shift;
my ( $name, $mac, $respstate, $gattCmd, $handle, $json_notification ) =
split( '\|', $string );
my $hash = $defs{$name};
delete( $hash->{helper}{RUNNING_PID} );
Log3( $name, 5,
"XiaomiBTLESens ($name) - ExecGatttool_Done: Helper is disabled. Stop processing"
) if ( $hash->{helper}{DISABLED} );
return if ( $hash->{helper}{DISABLED} );
Log3( $name, 5,
"XiaomiBTLESens ($name) - ExecGatttool_Done: gatttool return string: $string"
);
my $decode_json = eval { decode_json($json_notification) };
if ($@) {
Log3( $name, 4,
"XiaomiBTLESens ($name) - ExecGatttool_Done: JSON error while request: $@"
);
}
if ( $respstate eq 'ok'
&& $gattCmd eq 'write'
&& AttrVal( $name, 'model', 'none' ) eq 'flowerSens' )
{
CreateParamGatttool( $hash, 'read',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{rdata} );
}
elsif ( $respstate eq 'ok' ) {
ProcessingNotification( $hash, $gattCmd, $handle,
$decode_json->{gtResult} );
}
else {
ProcessingErrors( $hash, $decode_json->{gtResult} );
}
return;
}
sub ExecGatttool_Aborted {
my $hash = shift;
my $name = $hash->{NAME};
my %readings;
delete( $hash->{helper}{RUNNING_PID} );
readingsSingleUpdate( $hash, 'state', 'unreachable', 1 );
$readings{'lastGattError'} =
'The BlockingCall Process terminated unexpectedly. Timedout';
WriteReadings( $hash, \%readings );
Log3( $name, 4,
"XiaomiBTLESens ($name) - ExecGatttool_Aborted: The BlockingCall Process terminated unexpectedly. Timedout"
);
return;
}
sub ProcessingNotification {
my ( $hash, $gattCmd, $handle, $notification ) = @_;
my $name = $hash->{NAME};
my $readings;
Log3( $name, 4, "XiaomiBTLESens ($name) - ProcessingNotification" );
if ( AttrVal( $name, 'model', 'none' ) eq 'flowerSens' ) {
if ( $handle eq '0x38' ) {
### Flower Sens - Read Firmware and Battery Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x38"
);
$readings = FlowerSensHandle0x38( $hash, $notification );
}
elsif ( $handle eq '0x35' ) {
### Flower Sens - Read Sensor Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x35"
);
$readings = FlowerSensHandle0x35( $hash, $notification );
}
}
elsif ( AttrVal( $name, 'model', 'none' ) eq 'thermoHygroSens' ) {
if ( $handle eq '0x18' ) {
### Thermo/Hygro Sens - Read Battery Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x18"
);
$readings = ThermoHygroSensHandle0x18( $hash, $notification );
}
elsif ( $handle eq '0x10' ) {
### Thermo/Hygro Sens - Read Sensor Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x10"
);
$readings = ThermoHygroSensHandle0x10( $hash, $notification );
}
elsif ( $handle eq '0x24' ) {
### Thermo/Hygro Sens - Read Firmware Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x24"
);
$readings = ThermoHygroSensHandle0x24( $hash, $notification );
}
elsif ( $handle eq '0x3' ) {
### Thermo/Hygro Sens - Read and Write Devicename
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x3" );
return CreateParamGatttool( $hash, 'read',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{devicename} )
if ( $gattCmd ne 'read' );
$readings = ThermoHygroSensHandle0x3( $hash, $notification );
}
}
elsif ( AttrVal( $name, 'model', 'none' ) eq 'mijiaLYWSD03MMC' ) {
if ( $handle eq '0x1b' ) {
### mijiaLYWSD03MMC - Read Battery Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x1b"
);
$readings = mijiaLYWSD03MMC_Handle0x1b( $hash, $notification );
}
elsif ( $handle eq '0x38' ) {
### mijiaLYWSD03MMC - Read Sensor Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x38"
);
$readings = mijiaLYWSD03MMC_Handle0x38( $hash, $notification );
}
elsif ( $handle eq '0x12' ) {
### mijiaLYWSD03MMC - Read Firmware Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x12"
);
$readings = mijiaLYWSD03MMC_Handle0x12( $hash, $notification );
}
elsif ( $handle eq '0x3' ) {
### mijiaLYWSD03MMC - Read and Write Devicename
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x3" );
return CreateParamGatttool( $hash, 'read',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{devicename} )
unless ( $gattCmd eq 'read' );
$readings = mijiaLYWSD03MMC_Handle0x3( $hash, $notification );
}
}
elsif ( AttrVal( $name, 'model', 'none' ) eq 'clearGrassSens' ) {
if ( $handle eq '0x3b' ) {
### Clear Grass Sens - Read Battery Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x3b"
);
$readings = ClearGrassSensHandle0x3b( $hash, $notification );
}
elsif ( $handle eq '0x1e' ) {
### Clear Grass Sens - Read Sensor Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x1e"
);
$readings = ClearGrassSensHandle0x1e( $hash, $notification );
}
elsif ( $handle eq '0x2a' ) {
### Clear Grass Sens - Read Firmware Data
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x2a"
);
$readings = ClearGrassSensHandle0x2a( $hash, $notification );
}
elsif ( $handle eq '0x3' ) {
### Clear Grass Sens - Read and Write Devicename
Log3( $name, 4,
"XiaomiBTLESens ($name) - ProcessingNotification: handle 0x3" );
return CreateParamGatttool( $hash, 'read',
$XiaomiModels{ AttrVal( $name, 'model', '' ) }{devicename} )
if ( $gattCmd ne 'read' );
$readings = ClearGrassSensHandle0x3( $hash, $notification );
}
}
return WriteReadings( $hash, $readings );
}
sub FlowerSensHandle0x38 {
### FlowerSens - Read Firmware and Battery Data
my $hash = shift;
my $notification = shift;
my $name = $hash->{NAME};
my %readings;
Log3( $name, 4, "XiaomiBTLESens ($name) - FlowerSens Handle0x38" );
my @dataBatFw = split( /\s/, $notification );
### neue Vereinheitlichung für Batteriereadings Forum #800017
$readings{'batteryPercent'} = hex( "0x" . $dataBatFw[0] );
$readings{'batteryState'} =
( hex( "0x" . $dataBatFw[0] ) > 15 ? "ok" : "low" );
$readings{'firmware'} =
( $dataBatFw[2] - 30 ) . "."
. ( $dataBatFw[4] - 30 ) . "."
. ( $dataBatFw[6] - 30 );
$hash->{helper}{CallBattery} = 1;
CallBattery_Timestamp($hash);
return \%readings;
}
sub FlowerSensHandle0x35 {
### Flower Sens - Read Sensor Data
my $hash = shift;
my $notification = shift;
my $name = $hash->{NAME};
my %readings;
Log3( $name, 4, "XiaomiBTLESens ($name) - FlowerSens Handle0x35" );
my @dataSensor = split( /\s/, $notification );
return stateRequest($hash)
if ( $dataSensor[0] eq "aa"
&& $dataSensor[1] eq "bb"
&& $dataSensor[2] eq "cc"
&& $dataSensor[3] eq "dd"
&& $dataSensor[4] eq "ee"
&& $dataSensor[5] eq "ff" );
if ( $dataSensor[1] eq "ff" ) {
$readings{'temperature'} =
( hex( "0x" . $dataSensor[1] . $dataSensor[0] ) - hex("0xffff") ) /
10;
}
else {
$readings{'temperature'} =
hex( "0x" . $dataSensor[1] . $dataSensor[0] ) / 10;
}
$readings{'lux'} = hex( "0x" . $dataSensor[4] . $dataSensor[3] );
$readings{'moisture'} = hex( "0x" . $dataSensor[7] );
$readings{'fertility'} = hex( "0x" . $dataSensor[9] . $dataSensor[8] );
Log3( $name, 4,
"XiaomiBTLESens ($name) - FlowerSens Handle0x35 - lux: "
. $readings{lux}
. ", moisture: "
. $readings{moisture}
. ", fertility: "
. $readings{fertility} );
$hash->{helper}{CallBattery} = 0;
return \%readings;
}
sub ThermoHygroSensHandle0x18 {
### Thermo/Hygro Sens - Battery Data
my $hash = shift;
my $notification = shift;
my $name = $hash->{NAME};
my %readings;
Log3( $name, 4, "XiaomiBTLESens ($name) - Thermo/Hygro Sens Handle0x18" );
chomp($notification);
$notification =~ s/\s+//g;
### neue Vereinheitlichung für Batteriereadings Forum #800017
$readings{'batteryPercent'} = hex( "0x" . $notification );
$readings{'batteryState'} =
( hex( "0x" . $notification ) > 15 ? 'ok' : 'low' );
$hash->{helper}{CallBattery} = 1;
CallBattery_Timestamp($hash);
return \%readings;
}
sub ThermoHygroSensHandle0x10 {
### Thermo/Hygro Sens - Read Sensor Data
my $hash = shift;
my $notification = shift;
my $name = $hash->{NAME};
my %readings;
Log3( $name, 4, "XiaomiBTLESens ($name) - Thermo/Hygro Sens Handle0x10" );
return stateRequest($hash)
if ( $notification !~ /^([0-9a-f]{2}(\s?))*$/ );
my @numberOfHex = split( /\s/, $notification );
$notification =~ s/\s+//g;
$readings{'temperature'} = pack( 'H*', substr( $notification, 4, 8 ) );
$readings{'humidity'} = pack(
'H*',
substr(
$notification,
(
(
scalar(@numberOfHex) == 14 || ( scalar(@numberOfHex) == 13
&& $readings{'temperature'} > 9 )
) ? 18 : 16
),
8
)
);
$hash->{helper}{CallBattery} = 0;
return \%readings;
}
sub ThermoHygroSensHandle0x24 {
### Thermo/Hygro Sens - Read Firmware Data
my $hash = shift;
my $notification = shift;
my $name = $hash->{NAME};
my %readings;
Log3( $name, 4, "XiaomiBTLESens ($name) - Thermo/Hygro Sens Handle0x24" );
$notification =~ s/\s+//g;
$readings{'firmware'} = pack( 'H*', $notification );
$hash->{helper}{CallBattery} = 0;
return \%readings;
}
....
....
....
Also im Grunde der ganze Rest vom Modulcode mit Ausnahme der Initialize
Danach FHEM neustart und interessanter Weise keine Fehlermeldung.
Wenn ich das Modul starte klappt alles soweit bis es zur Rückgabe der Daten von BlockingCall kommt.
Dann erhalte ich folgende Fehlermeldung welche ich wenn ich das gesamte Modul nehme FHEM/74_Xiaom...
nicht habe. Obwohl es der selbe Code ist.
Zitat
2020.05.11 12:43:32.055 1: PERL WARNING: Hexadecimal number > 0xffffffff non-portable at (eval 124) line 1.
2020.05.11 12:43:32.055 3: eval: {CODE(0x55f522a95350)('myTestSensor|C4:7C:8D:63:8C:75|ok|read|0x38|{"gtResult":"63 23 33 2e 31 2e 34 "}')}
2020.05.11 12:43:32.055 1: stacktrace:
2020.05.11 12:43:32.055 1: main::__ANON__ called by (eval 124) (1)
2020.05.11 12:43:32.055 1: (eval)
Verdacht: in ein HEX() wird was gegeben was größer als INT ist -> https://perldoc.perl.org/functions/hex.html
Warning: das ist nicht portable ...
Welche function passt den auf die signatur? CODE(0x55f522a95350)('myTestSensor|C4:7C:8D:63:8C:75|ok|read|0x38|{"gtResult":"63 23 33 2e 31 2e 34 "}')
sub ExecGatttool_Run {
my $string = shift;
my ( $name, $mac, $gattCmd, $handle, $value, $listen ) =
split( '\|', $string );
my $sshHost = AttrVal( $name, 'sshHost', 'none' );
my $gatttool;
my $json_notification;
$gatttool = qx(which gatttool) if ( $sshHost eq 'none' );
$gatttool = qx(ssh $sshHost 'which gatttool') if ( $sshHost ne 'none' );
chomp $gatttool;
if ( defined($gatttool) && ($gatttool) ) {
my $cmd;
my $loop;
my @gtResult;
my $wait = 1;
my $sshHost = AttrVal( $name, 'sshHost', 'none' );
my $hci = AttrVal( $name, 'hciDevice', 'hci0' );
$cmd .= "ssh $sshHost '" if ( $sshHost ne 'none' );
$cmd .= "timeout 10 " if ($listen);
$cmd .= "gatttool -i $hci -b $mac ";
$cmd .= "--char-read -a $handle" if ( $gattCmd eq 'read' );
$cmd .= "--char-write-req -a $handle -n $value"
if ( $gattCmd eq 'write' );
$cmd .= " --listen" if ($listen);
# $cmd .= " 2>&1 /dev/null";
$cmd .= " 2>&1";
$cmd .= "'" if ( $sshHost ne 'none' );
$cmd =
"ssh $sshHost 'gatttool -i $hci -b $mac --char-write-req -a 0x33 -n A01F && gatttool -i $hci -b $mac --char-read -a 0x35 2>&1 '"
if ( $sshHost ne 'none'
&& $gattCmd eq 'write'
&& AttrVal( $name, 'model', 'none' ) eq 'flowerSens' );
while ($wait) {
my $grepGatttool;
my $gatttoolCmdlineStaticEscaped =
BTLE_CmdlinePreventGrepFalsePositive("gatttool -i $hci -b $mac");
my $psCommand = AttrVal( $name, 'psCommand', 'ps ax' );
Log3( $name, 5,
"XiaomiBTLESens ($name) - ExecGatttool_Run: Execute Command $psCommand | grep -E $gatttoolCmdlineStaticEscaped"
);
# $grepGatttool = qx(ps ax| grep -E \'$gatttoolCmdlineStaticEscaped\')
$grepGatttool =
qx($psCommand | grep -E \'$gatttoolCmdlineStaticEscaped\')
if ( $sshHost eq 'none' );
# $grepGatttool = qx(ssh $sshHost 'ps ax| grep -E "$gatttoolCmdlineStaticEscaped"')
$grepGatttool =
qx(ssh $sshHost '$psCommand | grep -E "$gatttoolCmdlineStaticEscaped"')
if ( $sshHost ne 'none' );
if ( not $grepGatttool =~ /^\s*$/ ) {
Log3( $name, 3,
"XiaomiBTLESens ($name) - ExecGatttool_Run: another gatttool process is running. waiting..."
);
sleep(1);
}
else {
$wait = 0;
}
}
$loop = 0;
my $returnString;
my $returnCode = 1;
do {
Log3( $name, 5,
"XiaomiBTLESens ($name) - ExecGatttool_Run: call gatttool with command: $cmd and loop $loop"
);
( $returnString, $returnCode ) = Gatttool_executeCommand($cmd);
@gtResult = split( ': ', $returnString );
Log3( $name, 5,
"XiaomiBTLESens ($name) - ExecGatttool_Run: gatttool loop result "
. join q{,}, @gtResult );
$returnCode = 2
if ( !defined( $gtResult[0] ) );
$loop++;
} while ( $loop < 5 && ( $returnCode != 0 && $returnCode != 124 ) );
Log3( $name, 3,
"XiaomiBTLESens ($name) - ExecGatttool_Run: errorcode: \"$returnCode\", ErrorString: \"$returnString\""
) if ( $returnCode != 0 && $returnCode != 124 );
Log3( $name, 4,
"XiaomiBTLESens ($name) - ExecGatttool_Run: gatttool result "
. join q{,}, @gtResult );
$handle = '0x35'
if ( $sshHost ne 'none'
&& $gattCmd eq 'write'
&& AttrVal( $name, 'model', 'none' ) eq 'flowerSens' );
$gattCmd = 'read'
if ( $sshHost ne 'none'
&& $gattCmd eq 'write'
&& AttrVal( $name, 'model', 'none' ) eq 'flowerSens' );
$gtResult[1] = 'no data response'
if ( !defined( $gtResult[1] ) );
if ( $gtResult[1] ne 'no data response' && $listen ) {
( $gtResult[1] ) = split( /\n/, $gtResult[1] );
$gtResult[1] =~ s/\\n//g;
}
$json_notification = encodeJSON( $gtResult[1] );
if ( $gtResult[1] =~ /^([0-9a-f]{2}(\s?))*$/ ) {
return "$name|$mac|ok|$gattCmd|$handle|$json_notification"; ## BEI DIESEM RETURN KOMMT DANN DER FEHLER
}
elsif ( $returnCode == 0 && $gattCmd eq 'write' ) {
if ( $sshHost ne 'none' ) {
ExecGatttool_Run( $name . "|" . $mac . "|read|0x35" );
}
else {
return "$name|$mac|ok|$gattCmd|$handle|$json_notification";
}
}
else {
return "$name|$mac|error|$gattCmd|$handle|$json_notification";
}
}
else {
$json_notification = encodeJSON(
'no gatttool binary found. Please check if bluez-package is properly installed'
);
return "$name|$mac|error|$gattCmd|$handle|$json_notification";
}
return;
}
Das hier ist die Funktion und ich habe die Stelle markiert wo er den return macht. Die komplette Funktion läuft im BlockingCall