Experimentiert mit packages und Verzeichnisstruktur

Begonnen von CoolTux, 11 Mai 2020, 12:56:55

Vorheriges Thema - Nächstes Thema

CoolTux

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)     
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

herrmannj

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 "}')


CoolTux


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
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net