diff --git a/fhem/CHANGED b/fhem/CHANGED index 85e33bc6d..7c013d1ea 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - new: 50_MOBILEALERTSGW/51_MOBILEALERTS: New moduls for MobileAlerts - change: 93_DbRep: V6.4.2, changelist: - prepare for usage of datetime picker widget - new attr "sqlResultFieldSep" (field separator) diff --git a/fhem/FHEM/50_MOBILEALERTSGW.pm b/fhem/FHEM/50_MOBILEALERTSGW.pm new file mode 100644 index 000000000..0fe19d608 --- /dev/null +++ b/fhem/FHEM/50_MOBILEALERTSGW.pm @@ -0,0 +1,833 @@ +############################################## +# $Id$ +# Written by Markus Feist, 2017 +package main; + +use strict; +use warnings; +use TcpServerUtils; +use HttpUtils; +use IO::Socket; + +use constant MA_PACKAGE_LENGTH => 64; + +my $MA_wname; +my $MA_chash; +my $MA_cname; +my @MA_httpheader; +my %MA_httpheader; + +sub MOBILEALERTSGW_Initialize($) { + my ($hash) = @_; + + $hash->{ReadFn} = "MOBILEALERTSGW_Read"; + $hash->{GetFn} = "MOBILEALERTSGW_Get"; + $hash->{SetFn} = "MOBILEALERTSGW_Set"; + $hash->{AttrFn} = "MOBILEALERTSGW_Attr"; + $hash->{DefFn} = "MOBILEALERTSGW_Define"; + $hash->{UndefFn} = "MOBILEALERTSGW_Undef"; + $hash->{Clients} = "MOBILEALERTS"; + $hash->{MatchList} = { "1:MOBILEALERTS" => "^.*" }; + $hash->{Write} = "MOBILEALERTSGW_Write"; + $hash->{FingerprintFn} = "MOBILEALERTSGW_Fingerprint"; + + #$hash->{NotifyFn}= ($init_done ? "FW_Notify" : "FW_SecurityCheck"); + #$hash->{AsyncOutputFn} = "MOBILEALERTSGW_AsyncOutput"; + #$hash->{ActivateInformFn} = "MOBILEALERTSGW_ActivateInform"; + $hash->{AttrList} = "forward:0,1 " . $readingFnAttributes; + Log3 "MOBILEALERTSGW", 5, "MOBILEALERTSGW_Initialize finished."; +} + +sub MOBILEALERTSGW_Define($$) { + my ( $hash, $def ) = @_; + my ( $name, $type, $port ) = split( "[ \t]+", $def ); + return "Usage: define MOBILEALERTSGW " + if ( $port !~ m/^\d+$/ ); + + my $ret = TcpServer_Open( $hash, $port, "global" ); + + return $ret; +} + +sub MOBILEALERTSGW_GetUDPSocket($$) { + my ( $hash, $name ) = @_; + my $socket; + if ( defined( $hash->{UDPHASH} ) ) { + $socket = $hash->{UDPHASH}->{UDPSOCKET}; + } + else { + #IO::Socket::INET geht leider nicht + Log3 $name, 3, "$name MOBILEALERTSGW: Create UDP Socket."; + unless ( socket( $socket, AF_INET, SOCK_DGRAM, getprotobyname('udp') ) ) + { + Log3 $name, 1, "$name MOBILEALERTSGW: Could not create socket: $!"; + return undef; + } + unless ( setsockopt( $socket, SOL_SOCKET, SO_BROADCAST, 1 ) ) { + Log3 $name, 1, "$name MOBILEALERTSGW: Could not setsockopt: $!"; + return undef; + } + my $cname = "${name}_UDPPORT"; + my %nhash; + $nhash{NR} = $devcount++; + $nhash{NAME} = $cname; + $nhash{FD} = fileno($socket); + $nhash{UDPSOCKET} = $socket; + $nhash{TYPE} = $hash->{TYPE}; + $nhash{STATE} = "Connected"; + $nhash{SNAME} = $name; + $nhash{TEMPORARY} = 1; # Don't want to save it + $nhash{HASH} = $hash; + $attr{$cname}{room} = "hidden"; + $defs{$cname} = \%nhash; + $selectlist{ $nhash{NAME} } = \%nhash; + $hash->{UDPHASH} = \%nhash; + } + return $socket; +} + +sub MOBILEALERTSGW_Get ($$@) { + my ( $hash, $name, $cmd, @args ) = @_; + + return "\"get $name\" needs at least one argument" unless ( defined($cmd) ); + + if ( $cmd eq "config" ) { + my @gateways = split( ",", ReadingsVal( $name, "Gateways", "" ) ); + my $gateway = $args[0]; + my $destpaddr; + my $command; + + if ( $gateway =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/ ) { + $destpaddr = sockaddr_in( 8003, inet_aton($gateway) ); + $gateway = "000000000000"; + $command = 1; + } + elsif ( $gateway =~ /([0-9A-F]{12})/ ) { + if ( @gateways == 0 ) { + $destpaddr = sockaddr_in( 8003, INADDR_BROADCAST ); + $command = 2; + } + elsif ( !grep( /^$gateway$/, @gateways ) ) { + $destpaddr = sockaddr_in( 8003, INADDR_BROADCAST ); + $command = 2; + } + else { + my $ip = ReadingsVal( $name, "GW_" . $gateway . "_ip", "" ); + if ( length($ip) == 0 ) { + $destpaddr = sockaddr_in( 8003, INADDR_BROADCAST ); + } + else { + $destpaddr = sockaddr_in( 8003, inet_aton($ip) ); + } + $command = 2; + } + } + else { + $gateway = "000000000000"; + $destpaddr = sockaddr_in( 8003, INADDR_BROADCAST ); + $command = 1; + } + my $socket = MOBILEALERTSGW_GetUDPSocket( $hash, $name ); + if ( !defined($socket) ) { + return "Could not create socket."; + } + my $data = pack( "nH[12]n", $command, $gateway, 10 ); + Log3 $name, 5, + "$name MOBILEALERTSGW: Send GetConfig " . unpack( "H*", $data ); + send( $socket, $data, 0, $destpaddr ); + return undef; + } + else { + return "Unknown argument $cmd, choose one of config"; + } +} + +sub MOBILEALERTSGW_Set ($$@) { + my ( $hash, $name, $cmd, @args ) = @_; + + return "\"set $name\" needs at least one argument" unless ( defined($cmd) ); + + if ( $cmd eq "clear" ) { + if ( $args[0] eq "readings" ) { + for ( keys %{ $hash->{READINGS} } ) { + delete $hash->{READINGS}->{$_} if ( $_ ne 'state' ); + } + return undef; + } + else { + return "Unknown value $args[0] for $cmd, choose one of readings"; + } + } + elsif ( $cmd eq "initgateway" ) { + my @gateways = split( ",", ReadingsVal( $name, "Gateways", "" ) ); + my $gateway = $args[0]; + + if ( @gateways == 0 ) { + return + "No gateway known. Find with 'get $name findgateways' first."; + } + if ( !grep( /^$gateway$/, @gateways ) ) { + return "Unknown $gateway for $cmd, choose one of " + . join( ",", @gateways ); + } + my $ip = ReadingsVal( $name, "GW_" . $gateway . "_ip", "" ); + my $config = ReadingsVal( $name, "GW_" . $gateway . "_config", "" ); + if ( length($ip) == 0 ) { + return +"IP of gateway unknown. Find with 'get $name findgateways' first."; + } + if ( length($config) == 0 ) { + return +"Config of gateway unknown. Find with 'get $name findgateways' first."; + } + my $sock = IO::Socket::INET->new( + Proto => 'udp', + PeerPort => 8003, + PeerAddr => $ip + ) or return "Could not create socket: $!\n"; + my $myip = $sock->sockhost; + my $myport = $hash->{PORT}; + + Log3 $name, 4, +"$name MOBILEALERTSGW: Config gateway $gateway $ip Proxy auf $myip:$myport"; + $config = pack( "H*", $config ); + $config = + "\0\x04" + . substr( $config, 2, 6 ) + . "\0\xB5" + . substr( $config, 15, 1 + 4 + 4 + 4 + 21 + 65 ) . "\x01" + . pack( "a65n", $myip, $myport ) + . substr( $config, 182, 4 ); + Log3 $name, 5, "$name MOBILEALERTSGW: Send " . unpack( "H*", $config ); + $sock->send($config) or return "Could not send $!"; + $sock->close(); + return undef; + } + elsif ( $cmd eq "rebootgateway" ) { + my @gateways = split( ",", ReadingsVal( $name, "Gateways", "" ) ); + my $gateway = $args[0]; + + if ( @gateways == 0 ) { + return + "No gateway known. Find with 'get $name findgateways' first."; + } + if ( !grep( /^$gateway$/, @gateways ) ) { + return "Unknown $gateway for $cmd, choose one of " + . join( ",", @gateways ); + } + my $ip = ReadingsVal( $name, "GW_" . $gateway . "_ip", "" ); + if ( length($ip) == 0 ) { + return +"IP of gateway unknown. Find with 'get $name findgateways' first."; + } + my $sock = IO::Socket::INET->new( + Proto => 'udp', + PeerPort => 8003, + PeerAddr => $ip + ) or return "Could not create socket: $!\n"; + Log3 $name, 4, + "$name MOBILEALERTSGW: Reboot gateway $gateway auf $ip:8003"; + my $data = pack( "nH[12]n", 5, $gateway, 10 ); + Log3 $name, 5, "$name MOBILEALERTSGW: Send " . unpack( "H*", $data ); + $sock->send($data) or return "Could not send $!"; + $sock->close(); + return undef; + } + elsif ( $cmd eq "debuginsert" ) { + my $data = pack( "H*", $args[0] ); + my ( $packageHeader, $timeStamp, $packageLength, $deviceID ) = + unpack( "CNCH12", $data ); + Log3 $name, 4, + "$name MOBILEALERTSGW: Debuginsert PackageHeader: " + . $packageHeader + . " Timestamp: " + . scalar( FmtDateTimeRFC1123($timeStamp) ) + . " PackageLength: " + . $packageLength + . " DeviceID: " + . $deviceID; + Log3 $name, 5, "$name MOBILEALERTSGW: Debuginsert for $deviceID: " + . unpack( "H*", $data ); + Dispatch( $hash, $data, undef ); + return undef; + } + else { + my $gateways = ReadingsVal( $name, "Gateways", "" ); + return + "Unknown argument $cmd, choose one of clear:readings rebootgateway:" + . $gateways + . " initgateway:" + . $gateways; + } +} + +sub MOBILEALERTSGW_Undef($$) { + my ( $hash, $name ) = @_; + + if ( defined( $hash->{UDPHASH} ) ) { + my $cname = "${name}_UDPPORT"; + delete( $selectlist{$cname} ); + delete $attr{$cname}; + delete $defs{$cname}; + close( $hash->{UDPHASH}->{UDPSOCKET} ); + delete $hash->{UDPHASH}; + } + + my $ret = TcpServer_Close($hash); + return $ret; +} + +sub MOBILEALERTSGW_Attr($$$$) { + my ( $cmd, $name, $attrName, $attrValue ) = @_; + + if ( $cmd eq "set" ) { + if ( $attrName eq "forward" ) { + if ( $attrValue !~ /^[01]$/ ) { + Log3 $name, 3, +"$name MOBILEALERTSGW: Invalid parameter attr $name $attrName $attrValue"; + return "Invalid value $attrValue allowed 0,1"; + } + } + } + return undef; +} + +sub MOBILEALERTSGW_Fingerprint($$$) { + my ( $io_name, $message ) = @_; + +#PackageHeader + UTC Timestamp + Package Length + Device ID + tx counter (3 bytes) + my $fingerprint = unpack( "H30", $message ); + return ( $io_name, $fingerprint ); +} + +sub MOBILEALERTSGW_Write ($$) { + + #Dummy, because it is not possible to send to device. + my ( $hash, @arguments ) = @_; + return undef; +} + +sub MOBILEALERTSGW_Read($$); + +sub MOBILEALERTSGW_Read($$) { + my ( $hash, $reread ) = @_; + my $name = $hash->{NAME}; + my $verbose = GetVerbose($name); + + if ( exists $hash->{UDPSOCKET} ) { + my $phash = $hash->{HASH}; + $name = $phash->{NAME}; + Log3 $name, 5, "$name MOBILEALERTSGW: Data from UDP received"; + my $srcpaddr = recv( $hash->{UDPSOCKET}, my $udpdata, 186, 0 ); + MOBILEALERTSGW_DecodeUDP( $phash, $udpdata, $srcpaddr ); + return; + } + + if ( $hash->{SERVERSOCKET} ) { # Accept and create a child + my $nhash = TcpServer_Accept( $hash, "MOBILEALERTSGW" ); + return if ( !$nhash ); + my $wt = AttrVal( $name, "alarmTimeout", undef ); + $nhash->{ALARMTIMEOUT} = $wt if ($wt); + $nhash->{CD}->blocking(0); + return; + } + + $MA_chash = $hash; + $MA_wname = $hash->{SNAME}; + $MA_cname = $name; + $verbose = GetVerbose($MA_wname); + + #$FW_subdir = ""; + + my $c = $hash->{CD}; + + if ( !$reread ) { + + # Data from HTTP Client + my $buf; + my $ret = sysread( $c, $buf, 1024 ); + + if ( !defined($ret) && $! == EWOULDBLOCK ) { + $hash->{wantWrite} = 1 + if ( TcpServer_WantWrite($hash) ); + return; + } + elsif ( !$ret ) { # 0==EOF, undef=error + CommandDelete( undef, $name ); + Log3 $MA_wname, 4, + "$MA_wname MOBILEALERTSGW: Connection closed for $name: " + . ( defined($ret) ? 'EOF' : $! ); + return; + } + $hash->{BUF} .= $buf; + } + + if ( !$hash->{HDR} ) { + return if ( $hash->{BUF} !~ m/^(.*?)(\n\n|\r\n\r\n)(.*)$/s ); + $hash->{HDR} = $1; + $hash->{BUF} = $3; + if ( $hash->{HDR} =~ m/Content-Length:\s*([^\r\n]*)/si ) { + $hash->{CONTENT_LENGTH} = $1; + } + } + + @MA_httpheader = split( /[\r\n]+/, $hash->{HDR} ); + %MA_httpheader = map { + my ( $k, $v ) = split( /: */, $_, 2 ); + $k =~ s/(\w+)/\u$1/g; # Forum #39203 + $k => ( defined($v) ? $v : 1 ); + } @MA_httpheader; + + my $POSTdata = ""; + if ( $hash->{CONTENT_LENGTH} ) { + return if ( length( $hash->{BUF} ) < $hash->{CONTENT_LENGTH} ); + $POSTdata = substr( $hash->{BUF}, 0, $hash->{CONTENT_LENGTH} ); + $hash->{BUF} = substr( $hash->{BUF}, $hash->{CONTENT_LENGTH} ); + } + delete( $hash->{HDR} ); + if ( $verbose >= 5 ) { + Log3 $MA_wname, 5, + "$MA_wname MOBILEALERTSGW: Headers: " . join( ", ", @MA_httpheader ); + Log3 $MA_wname, 5, "$MA_wname MOBILEALERTSGW: Receivebuffer: " + . unpack( "H*", $POSTdata ); + } + + my ( $method, $url, $httpvers ) = split( " ", $MA_httpheader[0], 3 ) + if ( $MA_httpheader[0] ); + $method = "" if ( !$method ); + + #if($method !~ m/^(GET|POST)$/i){ + if ( $method !~ m/^(PUT|POST)$/i ) { + TcpServer_WriteBlocking( $MA_chash, + "HTTP/1.1 405 Method Not Allowed\r\n" + . "Content-Length: 0\r\n\r\n" ); + delete $hash->{CONTENT_LENGTH}; + MOBILEALERTSGW_Read( $hash, 1 ) if ( $hash->{BUF} ); + Log3 $MA_wname, 3, +"$MA_wname MOBILEALERTSGW: $MA_cname: unsupported HTTP method $method, rejecting it."; + MOBILEALERTSGW_closeConn($hash); + return; + } + + if ( $url !~ m/.*\/gateway\/put$/i ) { + TcpServer_WriteBlocking( $MA_chash, + "HTTP/1.1 400 Bad Request\r\n" . "Content-Length: 0\r\n\r\n" ); + delete $hash->{CONTENT_LENGTH}; + MOBILEALERTSGW_Read( $hash, 1 ) if ( $hash->{BUF} ); + Log3 $MA_wname, 3, +"$MA_wname MOBILEALERTSGW: $MA_cname: unsupported URL $url, rejecting it."; + MOBILEALERTSGW_closeConn($hash); + return; + } + if ( !exists $MA_httpheader{"HTTP_IDENTIFY"} ) { + TcpServer_WriteBlocking( $MA_chash, + "HTTP/1.1 400 Bad Request\r\n" . "Content-Length: 0\r\n\r\n" ); + delete $hash->{CONTENT_LENGTH}; + MOBILEALERTSGW_Read( $hash, 1 ) if ( $hash->{BUF} ); + Log3 $MA_wname, 3, +"$MA_wname MOBILEALERTSGW: $MA_cname: not Header http_identify, rejecting it."; + MOBILEALERTSGW_closeConn($hash); + return; + } + Log3 $MA_wname, 5, "Header HTTP_IDENTIFY" . $MA_httpheader{"HTTP_IDENTIFY"}; + my ( $gwserial, $gwmac, $actioncode ) = + split( /:/, $MA_httpheader{"HTTP_IDENTIFY"} ); + my @gateways = split( ",", ReadingsVal( $MA_wname, "Gateways", "" ) ); + readingsBeginUpdate( $defs{$MA_wname} ); + if ( !grep( /^$gwmac$/, @gateways ) ) { + push( @gateways, $gwmac ); + readingsBulkUpdate( $defs{$MA_wname}, "Gateways", + join( ",", @gateways ) ); + } + readingsBulkUpdate( $defs{$MA_wname}, "GW_" . $gwmac . "_lastSeen", + TimeNow() ); + readingsBulkUpdateIfChanged( $defs{$MA_wname}, "GW_" . $gwmac . "_ip", + $hash->{PEER} ); + readingsBulkUpdateIfChanged( $defs{$MA_wname}, "GW_" . $gwmac . "_serial", + $gwserial ); + readingsEndUpdate( $defs{$MA_wname}, 1 ); + if ( $actioncode eq "00" ) { + Log3 $MA_wname, 4, +"$MA_wname MOBILEALERTSGW: $MA_cname: Initrequest from $gwserial $gwmac"; + MOBILEALERTSGW_DecodeInit( $hash, $POSTdata ); + MOBILEALERTSGW_DefaultAnswer($hash); + } + elsif ( $actioncode eq "C0" ) { + Log3 $MA_wname, 4, + "$MA_wname MOBILEALERTSGW: $MA_cname: Data from $gwserial $gwmac"; + MOBILEALERTSGW_DecodeData( $hash, $POSTdata ); + MOBILEALERTSGW_DefaultAnswer($hash); + } + else { + TcpServer_WriteBlocking( $MA_chash, + "HTTP/1.1 400 Bad Request\r\n" . "Content-Length: 0\r\n\r\n" ); + delete $hash->{CONTENT_LENGTH}; + MOBILEALERTSGW_Read( $hash, 1 ) if ( $hash->{BUF} ); + Log3 $MA_wname, 3, + "$MA_wname MOBILEALERTSGW: $MA_cname: unknown Actioncode $actioncode"; + Log3 $MA_wname, 4, +"$MA_wname MOBILEALERTSGW: $MA_cname: unknown Actioncode $actioncode Postdata: " + . unpack( "H*", $POSTdata ); + MOBILEALERTSGW_closeConn($hash); + return; + } + MOBILEALERTSGW_closeConn($hash); #No Keep-Alive + + #Send to Server + if ( AttrVal( $MA_wname, "forward", 0 ) == 1 ) { + my $httpparam = { + url => "http://www.data199.com/gateway/put", + timeout => 20, + httpversion => "1.1", + hash => $hash, + method => "PUT", + header => "HTTP_IDENTIFY: " + . $MA_httpheader{"HTTP_IDENTIFY"} + . "\r\nContent-Type: application/octet-stream", + data => $POSTdata, + callback => \&MOBILEALERTSGW_NonblockingGet_Callback + }; + HttpUtils_NonblockingGet($httpparam); + } + return; +} + +sub MOBILEALERTSGW_NonblockingGet_Callback($$$) { + my ( $param, $err, $data ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $code = $param->{code}; + Log3 $name, 4, "$name MOBILEALERTSGW: Callback"; + if ( $err ne "" ) { + Log3 $name, 3, + "$name MOBILEALERTSGW: error while forward request to " + . $param->{url} + . " - $err"; + } + elsif ( $code != 200 ) { + Log3 $name, 3, + "$name MOBILEALERTSGW: http-error while forward request to " + . $param->{url} . " - " + . $param->{code}; + Log3 $name, 5, + "$name MOBILEALERTSGW: http-header: " . $param->{httpheader}; + Log3 $name, 5, "$name MOBILEALERTSGW: http-data: " . $data; + } + else { + Log3 $name, 5, "$name MOBILEALERTSGW: forward successfull"; + Log3 $name, 5, + "$name MOBILEALERTSGW: http-header: " . $param->{httpheader}; + Log3 $name, 5, + "$name MOBILEALERTSGW: http-data: " . unpack( "H*", $data ); + } + HttpUtils_Close($param); +} + +sub MOBILEALERTSGW_closeConn($) { + my ($hash) = @_; + + # Kein Keep-Alive noetig + TcpServer_Close( $hash, 1 ); +} + +sub MOBILEALERTSGW_DefaultAnswer($) { + my ($hash) = @_; + my $buf; + + $buf = pack( "NxxxxNxxxxNN", 420, time, 0x1761D480, 15 ); + + TcpServer_WriteBlocking( $MA_chash, + "HTTP/1.1 200 OK\r\n" + . "Content-Type: application/octet-stream\r\n" + . "Content-Length: 24\r\n\r\n" + . $buf ); +} + +sub MOBILEALERTSGW_DecodeInit($$) { + my ( $hash, $POSTdata ) = @_; + my ( $packageLength, $upTime, $ID, $unknown1, $unknown50 ) = + unpack( "CNH12nn", $POSTdata ); + + Log3 $MA_wname, 4, + "$MA_wname MOBILEALERTSGW: Uptime (s): " . $upTime . " ID: " . $ID; +} + +sub MOBILEALERTSGW_DecodeData($$) { + my ( $hash, $POSTdata ) = @_; + my $verbose = GetVerbose($MA_wname); + + for ( my $pos = 0 ; $pos < length($POSTdata) ; $pos += MA_PACKAGE_LENGTH ) { + my $data = substr $POSTdata, $pos, MA_PACKAGE_LENGTH; + my ( $packageHeader, $timeStamp, $packageLength, $deviceID ) = + unpack( "CNCH12", $data ); + Log3 $MA_wname, 4, + "$MA_wname MOBILEALERTSGW: PackageHeader: " + . $packageHeader + . " Timestamp: " + . scalar( FmtDateTimeRFC1123($timeStamp) ) + . " PackageLength: " + . $packageLength + . " DeviceID: " + . $deviceID + if ( $verbose >= 4 ); + Log3 $MA_wname, 5, + "$MA_wname MOBILEALERTSGW: Data for $deviceID: " + . unpack( "H*", $data ) + if ( $verbose >= 5 ); + my $found = Dispatch( $defs{$MA_wname}, $data, undef ); + } +} + +sub MOBILEALERTSGW_DecodeUDP($$$) { + my ( $hash, $udpdata, $srcpaddr ) = @_; + my ( $port, $ipaddr ) = sockaddr_in($srcpaddr); + my $name = $hash->{NAME}; + Log3 $name, 4, + "$name MOBILEALERTSGW: Data from " . inet_ntoa($ipaddr) . ":" . $port; + Log3 $name, 5, "$name MOBILEALERTSGW: Data: " . unpack( "H*", $udpdata ); + + if ( length $udpdata == 186 ) { + my @ip; + my @fip; + my @netmask; + my @gateway; + my @dnsip; + ( + my $command, + my $gatewayid, + my $length, + @ip[ 0 .. 3 ], + my $dhcp, + @fip[ 0 .. 3 ], + @netmask[ 0 .. 3 ], + @gateway[ 0 .. 3 ], + my $devicename, + my $dataserver, + my $proxy, + my $proxyname, + my $proxyport, + @dnsip[ 0 .. 3 ] + ) = unpack( "nH12nxCCCCCCCCCCCCCCCCCa21a65Ca65nCCCC", $udpdata ); + + if ( $command != 3 ) { + Log3 $name, 3, + "$name MOBILEALERTSGW: Unknown Command $command: " + . unpack( "H*", $udpdata ); + return; + } + Log3 $name, 4, + "$name MOBILEALERTSGW: Command: " + . $command + . " Gatewayid: " + . $gatewayid + . " length: " + . $length . " IP: " + . join( ".", @ip ) + . " DHCP: " + . $dhcp + . " fixedIP: " + . join( ".", @fip ) + . " netmask: " + . join( ".", @netmask ) + . " gateway: " + . join( ".", @gateway ) + . " devicename: " + . $devicename + . " dataserver: " + . $dataserver + . " proxy: " + . $proxy + . " proxyname: " + . $proxyname + . " proxyport: " + . $proxyport + . " dnsip: " + . join( ".", @dnsip ); + $gatewayid = uc $gatewayid; + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "GW_" . $gatewayid . "_lastSeen", + TimeNow() ); + readingsBulkUpdateIfChanged( $hash, "GW_" . $gatewayid . "_ip", + inet_ntoa($ipaddr) ); + readingsBulkUpdateIfChanged( + $hash, + "GW_" . $gatewayid . "_config", + unpack( "H*", $udpdata ) + ); + readingsBulkUpdateIfChanged( + $hash, + "GW_" . $gatewayid . "_proxy", + $proxy == 1 ? "on" : "off" + ); + readingsBulkUpdateIfChanged( $hash, "GW_" . $gatewayid . "_proxyname", + $proxyname ); + readingsBulkUpdateIfChanged( $hash, "GW_" . $gatewayid . "_proxyport", + $proxyport ); + my @gateways = split( ",", ReadingsVal( $name, "Gateways", "" ) ); + + if ( !grep( /^$gatewayid$/, @gateways ) ) { + push( @gateways, $gatewayid ); + readingsBulkUpdate( $hash, "Gateways", join( ",", @gateways ) ); + } + readingsEndUpdate( $hash, 1 ); + } + elsif ( length $udpdata == 118 ) { + Log3 $name, 5, + "$name MOBILEALERTSGW: Package was defect, this seems to be normal."; + } + else { + Log3 $name, 3, + "$name MOBILEALERTSGW: Unknown Data: " . unpack( "H*", $udpdata ); + } + return; +} + +# Eval-Rückgabewert für erfolgreiches +# Laden des Moduls +1; + +=pod +=item device +=item summary IO device for german MobileAlerts +=item summary_DE IO device für deutsche MobileAlets +=begin html + + +

MOBILEALERTSGW

+ + +=end html + +=begin html_DE + + +

MOBILEALERTSGW

+ + +=end html_DE +=cut diff --git a/fhem/FHEM/51_MOBILEALERTS.pm b/fhem/FHEM/51_MOBILEALERTS.pm new file mode 100644 index 000000000..6f70b0b26 --- /dev/null +++ b/fhem/FHEM/51_MOBILEALERTS.pm @@ -0,0 +1,1125 @@ +############################################## +# $Id$ +# Written by Markus Feist, 2017 +package main; + +use strict; +use warnings; +use constant MA_RAIN_FACTOR => 0.258; + +sub MOBILEALERTS_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = "MOBILEALERTS_Define"; + $hash->{UndefFn} = "MOBILEALERTS_Undef"; + $hash->{SetFn} = "MOBILEALERTS_Set"; + $hash->{AttrFn} = "MOBILEALERTS_Attr"; + $hash->{ParseFn} = "MOBILEALERTS_Parse"; + $hash->{Match} = "^.*"; + $hash->{AttrList} = + "actCycle " + . "lastMsg:0,1 " + . "expert:0,1,4 " + . "stateFormat " + . "ignore:0,1 " + . $readingFnAttributes; + $hash->{AutoCreate} = { + "MA_.*" => { + ATTR => "event-on-change-reading:.* timestamp-on-change-reading:.*", + FILTER => "%NAME" + } + }; + InternalTimer( gettimeofday() + 60, "MOBILEALERTS_ActionDetector", $hash ); + Log3 "MOBILEALERTS", 5, "MOBILEALERTS_Initialize finished."; +} + +sub MOBILEALERTS_Define($$) { + my ( $hash, $def ) = @_; + my ( $name, $type, $deviceID ) = split( "[ \t]+", $def ); + Log3 $name, 3, "$name MOBILEALERTS: DeviceID $deviceID"; + return "Usage: define MOBILEALERTS " + if ( $deviceID !~ m/^[0-9a-f]{12}$/ ); + $modules{MOBILEALERTS}{defptr}{$deviceID} = $hash; + $hash->{DeviceID} = $deviceID; + if ( ( exists $modules{MOBILEALERTS}{AutoCreateMessages} ) + && ( exists $modules{MOBILEALERTS}{AutoCreateMessages}{$deviceID} ) ) + { + MOBILEALERTS_Parse( + $modules{MOBILEALERTS}{AutoCreateMessages}{$deviceID}[0], + $modules{MOBILEALERTS}{AutoCreateMessages}{$deviceID}[1] + ); + delete $modules{MOBILEALERTS}{AutoCreateMessages}{$deviceID}; + } + if ( substr( $deviceID, 0, 2 ) eq "08" ) { + Log3 $name, 5, "$name MOBILEALERTS: is rainSensor, start Timer"; + InternalTimer( gettimeofday() + 60, + "MOBILEALERTS_CheckRainSensorTimed", $hash ); + } + return undef; +} + +sub MOBILEALERTS_Undef($$) { + my ( $hash, $name ) = @_; + delete $modules{MOBILEALERTS}{defptr}{ $hash->{DeviceID} }; + RemoveInternalTimer( $hash, "MOBILEALERTS_CheckRainSensorTimed" ); + return undef; +} + +sub MOBILEALERTS_Attr($$$$) { + my ( $cmd, $name, $attrName, $attrValue ) = @_; + + if ( $cmd eq "set" ) { + if ( $attrName eq "lastMsg" ) { + if ( $attrValue !~ /^[01]$/ ) { + Log3 $name, 3, +"$name MOBILELAERTS: Invalid parameter attr $name $attrName $attrValue"; + return "Invalid value $attrValue allowed 0,1"; + } + } + elsif ( $attrName eq "expert" ) { + if ( $attrValue !~ /^[014]$/ ) { + Log3 $name, 3, +"$name MOBILELAERTS: Invalid parameter attr $name $attrName $attrValue"; + return "Invalid value $attrValue allowed 0,1,4"; + } + } + elsif ( $attrName eq "actCycle" ) { + unless ( $attrValue eq "off" ) { + ( $_[3], my $sec ) = MOBILEALERTS_time2sec($attrValue); + if ( $sec > 0 ) { + my $hash = $modules{MOBILEALERTS}; + if ($init_done) { + RemoveInternalTimer($hash); + InternalTimer( gettimeofday() + 60, + "MOBILEALERTS_ActionDetector", $hash ); + } + } + } + } + } + return undef; +} + +sub MOBILEALERTS_Notify($$) { + my ( $hash, $dev ) = @_; + my $name = $hash->{NAME}; + my $devName = $dev->{NAME}; +} + +sub MOBILEALERTS_Set ($$@) { + my ( $hash, $name, $cmd, @args ) = @_; + return "\"set $name\" needs at least one argument" unless ( defined($cmd) ); + + if ( $cmd eq "clear" ) { + if ( $args[0] eq "readings" ) { + for ( keys %{ $hash->{READINGS} } ) { + delete $hash->{READINGS}->{$_} if ( $_ ne 'state' ); + } + return undef; + } + elsif ( $args[0] eq "counters" ) { + my $test = ReadingsVal( $hash->{NAME}, "mmRain", undef ); + readingsSingleUpdate( $hash, "mmRain", 0, 1 ) if ( defined $test ); + return undef; + } + else { + return +"Unknown value $args[0] for $cmd, choose one of readings,counters"; + } + } + else { + return "Unknown argument $cmd, choose one of clear:readings,counters"; + } +} + +sub MOBILEALERTS_Parse ($$) { + my ( $io_hash, $message ) = @_; + my ( $packageHeader, $timeStamp, $packageLength, $deviceID ) = + unpack( "H2NCH12", $message ); + my $name = $io_hash->{NAME}; + + Log3 $name, 5, "$name MOBILELAERTS: Search for Device ID: $deviceID"; + if ( my $hash = $modules{MOBILEALERTS}{defptr}{$deviceID} ) { + my $verbose = GetVerbose( $hash->{NAME} ); + Log3 $name, 5, "$name MOBILELAERTS: Found Device: " . $hash->{NAME}; + Log3 $name, 5, + "$hash->{NAME} MOBILELAERTS: Message: " . unpack( "H*", $message ) + if ( $verbose >= 5 ); + + # Nachricht für $hash verarbeiten + $timeStamp = FmtDateTime($timeStamp); + readingsBeginUpdate($hash); + $hash->{".updateTimestamp"} = $timeStamp; + $hash->{".expertMode"} = AttrVal( $hash->{NAME}, "expert", 0 ); + my $sub = + "MOBILEALERTS_Parse_" + . substr( $deviceID, 0, 2 ) . "_" + . $packageHeader; + if ( defined &$sub ) { + + #no strict "refs"; + &{ \&$sub }( $hash, substr $message, 12, $packageLength - 12 ); + + #use strict "refs"; + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "lastMsg", + unpack( "H*", $message ) ) + if ( AttrVal( $hash->{NAME}, "lastMsg", 0 ) == 1 ); + } + else { + Log3 $name, 2, + "$name MOBILELAERTS: For id " + . substr( $deviceID, 0, 2 ) + . " and packageHeader $packageHeader is no decoding defined."; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "Unknown - " + . substr( $deviceID, 0, 2 ) . " " + . $packageHeader ); + $sub = "MOBILEALERTS_Parse_" . $packageHeader; + if ( defined &$sub ) { + + #no strict "refs"; + &{ \&$sub }( $hash, substr $message, 12, $packageLength - 12 ); + + #use strict "refs"; + } + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "lastMsg", + unpack( "H*", $message ) ); + } + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "lastRcv", $timeStamp ); + + my $actCycle = AttrVal( $hash->{NAME}, "actCycle", undef ); + if ($actCycle) { + ( undef, my $sec ) = MOBILEALERTS_time2sec($actCycle); + if ( $sec > 0 ) { + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "actStatus", + "alive" ); + } + } + readingsEndUpdate( $hash, 1 ); + + # Rückgabe des Gerätenamens, für welches die Nachricht bestimmt ist. + return $hash->{NAME}; + } + $modules{MOBILEALERTS}{AutoCreateMessages}{$deviceID} = + [ $io_hash, $message ]; + my $res = "UNDEFINED MA_" . $deviceID . " MOBILEALERTS $deviceID"; + Log3 $name, 5, "$name MOBILELAERTS: Parse return: " . $res; + return $res; +} + +sub MOBILEALERTS_Parse_02_ce ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10100" ); + MOBILEALERTS_Parse_ce( $hash, $message ); +} + +sub MOBILEALERTS_Parse_ce ($$) { + my ( $hash, $message ) = @_; + my ( $txCounter, $temperature, $prevTemperature ) = + unpack( "nnn", $message ); + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperature = MOBILEALERTS_decodeTemperature($temperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperature", $temperature ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureString", + MOBILEALERTS_temperatureToString($temperature) ); + $prevTemperature = MOBILEALERTS_decodeTemperature($prevTemperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperature", + $prevTemperature ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", "T: " . $temperature ); +} + +sub MOBILEALERTS_Parse_0f_d2 ($$) { + my ( $hash, $message ) = @_; + my ( $txCounter, $temperatureIn, $temperatureOut, $prevTemperatureIn, + $prevTemperatureOut ) + = unpack( "nnnnn", $message ); + + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10450" ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperatureIn = MOBILEALERTS_decodeTemperature($temperatureIn); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureIn", + $temperatureIn ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureInString", + MOBILEALERTS_temperatureToString($temperatureIn) ); + $temperatureOut = MOBILEALERTS_decodeTemperature($temperatureOut); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureOut", + $temperatureOut ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureOutString", + MOBILEALERTS_temperatureToString($temperatureOut) ); + $prevTemperatureIn = MOBILEALERTS_decodeTemperature($prevTemperatureIn); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperatureIn", + $prevTemperatureIn ); + $prevTemperatureOut = MOBILEALERTS_decodeTemperature($prevTemperatureOut); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperatureOut", + $prevTemperatureOut ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "In T: " . $temperatureIn . " Out T: " . $temperatureOut ); +} + +sub MOBILEALERTS_Parse_03_d2 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10200" ); + MOBILEALERTS_Parse_d2( $hash, $message ); +} + +sub MOBILEALERTS_Parse_d2 ($$) { + my ( $hash, $message ) = @_; + my ( $txCounter, $temperature, $humidity, $prevTemperature, $prevHumidity ) + = unpack( "nnnnn", $message ); + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperature = MOBILEALERTS_decodeTemperature($temperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperature", $temperature ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureString", + MOBILEALERTS_temperatureToString($temperature) ); + $humidity = MOBILEALERTS_decodeHumidity($humidity); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity", $humidity ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityString", + MOBILEALERTS_humidityToString($humidity) ); + $prevTemperature = MOBILEALERTS_decodeTemperature($prevTemperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperature", + $prevTemperature ); + $prevHumidity = MOBILEALERTS_decodeHumidity($prevHumidity); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevHumidity", $prevHumidity ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "T: " . $temperature . " H: " . $humidity ); +} + +sub MOBILEALERTS_Parse_04_d4 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10350" ); + MOBILEALERTS_Parse_d4( $hash, $message ); +} + +sub MOBILEALERTS_Parse_d4 ($$) { + my ( $hash, $message ) = @_; + my ( $txCounter, $temperature, $humidity, $wetness, + $prevTemperature, $prevHumidity, $prevWetness ) + = unpack( "nnnCnnC", $message ); + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperature = MOBILEALERTS_decodeTemperature($temperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperature", $temperature ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureString", + MOBILEALERTS_temperatureToString($temperature) ); + $humidity = MOBILEALERTS_decodeHumidity($humidity); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity", $humidity ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityString", + MOBILEALERTS_humidityToString($humidity) ); + $wetness = MOBILEALERTS_decodeWetness($wetness); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "wetness", $wetness ); + $prevTemperature = MOBILEALERTS_decodeTemperature($prevTemperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperature", + $prevTemperature ); + $prevHumidity = MOBILEALERTS_decodeHumidity($prevHumidity); + $prevWetness = MOBILEALERTS_decodeWetness($prevWetness); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevWetness", $prevWetness ); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevHumidity", $prevHumidity ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "T: " . $temperature . " H: " . $humidity . " " . $wetness ); +} + +sub MOBILEALERTS_Parse_07_da ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10410" ); + MOBILEALERTS_Parse_da( $hash, $message ); +} + +sub MOBILEALERTS_Parse_da ($$) { + my ( $hash, $message ) = @_; + my ( + $txCounter, $temperatureIn, $humidityIn, + $temperatureOut, $humidityOut, $prevTemperatureIn, + $prevHumidityIn, $prevTemperatureOut, $prevHumidityOut + ) = unpack( "nnnnnnnnn", $message ); + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperatureIn = MOBILEALERTS_decodeTemperature($temperatureIn); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureIn", + $temperatureIn ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureInString", + MOBILEALERTS_temperatureToString($temperatureIn) ); + $humidityIn = MOBILEALERTS_decodeHumidity($humidityIn); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityIn", $humidityIn ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityInString", + MOBILEALERTS_humidityToString($humidityIn) ); + $temperatureOut = MOBILEALERTS_decodeTemperature($temperatureOut); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureOut", + $temperatureOut ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureOutString", + MOBILEALERTS_temperatureToString($temperatureOut) ); + $humidityOut = MOBILEALERTS_decodeHumidity($humidityOut); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityOut", $humidityOut ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityOutString", + MOBILEALERTS_humidityToString($humidityOut) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "In T: " + . $temperatureIn . " H: " + . $humidityIn + . " Out T: " + . $temperatureOut . " H: " + . $humidityOut ); +} + +sub MOBILEALERTS_Parse_08_e1 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10650" ); + MOBILEALERTS_Parse_e1( $hash, $message ); +} + +sub MOBILEALERTS_Parse_e1 ($$) { + my ( $hash, $message ) = @_; + my @eventTime; + ( my ( $txCounter, $temperature, $eventCounter ), @eventTime[ 0 .. 8 ] ) = + unpack( "nnnnnnnnnnnn", $message ); + my $lastEventCounter = ReadingsVal( $hash->{NAME}, "eventCounter", undef ); + my $mmRain = 0; + + if ( !defined($lastEventCounter) ) { + + # First Data + $mmRain = $eventCounter * MA_RAIN_FACTOR; + } + elsif ( $lastEventCounter > $eventCounter ) { + + # Overflow EventCounter or fresh Batterie + $mmRain = $eventCounter * MA_RAIN_FACTOR; + } + elsif ( $lastEventCounter < $eventCounter ) { + $mmRain = ( $eventCounter - $lastEventCounter ) * MA_RAIN_FACTOR; + } + else { + $mmRain = 0; + } + MOBILEALERTS_CheckRainSensor( $hash, $mmRain ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperature = MOBILEALERTS_decodeTemperature($temperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperature", $temperature ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureString", + MOBILEALERTS_temperatureToString($temperature) ); + + for ( my $z = 0 ; $z < 9 ; $z++ ) { + my $eventTimeString = + MOBILEALERTS_convertEventTimeString( $eventTime[$z], 14 ); + $eventTime[$z] = MOBILEALERTS_convertEventTime( $eventTime[$z], 14 ); + if ( $z == 0 ) { + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "lastEvent", + $eventTime[$z] ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "lastEventString", + $eventTimeString ); + } + else { + MOBILEALERTS_readingsBulkUpdate( $hash, 4, "lastEvent" . $z, + $eventTime[$z] ); + MOBILEALERTS_readingsBulkUpdate( $hash, 4, + "lastEvent" . $z . "String", + $eventTimeString ); + } + } + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "eventCounter", $eventCounter ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "T: " . $temperature . " C: " . $eventCounter ); +} + +sub MOBILEALERTS_Parse_0b_e2 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10660" ); + MOBILEALERTS_Parse_e2( $hash, $message ); +} + +sub MOBILEALERTS_Parse_e2 ($$) { + my ( $hash, $message ) = @_; + my @dirTable = ( + "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", + "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW" + ); + my ( $txCounter, $data0, $data1, $data2, $data3 ) = + unpack( "NCCCC", "\0" . $message ); + + my $dir = $data0 >> 4; + my $overFlowBits = $data0 & 3; + my $windSpeed = ( ( ( $overFlowBits & 2 ) >> 1 ) << 8 ) + $data1 * 0.1; + my $gustSpeed = ( ( ( $overFlowBits & 1 ) >> 1 ) << 8 ) + $data2 * 0.1; + my $lastTransmit = $data3 * 2; + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "direction", $dirTable[$dir] ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "windSpeed", $windSpeed ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "gustSpeed", $gustSpeed ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "D: " . $dirTable[$dir] . " W: " . $windSpeed . " G: " . $gustSpeed ); +} + +sub MOBILEALERTS_Parse_0e_d8 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "TFA30.3312.02" ); + MOBILEALERTS_Parse_d8( $hash, $message ); +} + +sub MOBILEALERTS_Parse_d8 ($$) { + my ( $hash, $message ) = @_; + my ( + $txCounter, $temperature, $humidity, $prevTemperature, + $prevHumidity, $prevTemperature2, $prevHumidity2 + ) = unpack( "nnnxnnxnn", $message ); + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperature = MOBILEALERTS_decodeTemperature($temperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperature", $temperature ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureString", + MOBILEALERTS_temperatureToString($temperature) ); + $humidity = MOBILEALERTS_decodeHumidityDecimal($humidity); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity", $humidity ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityString", + MOBILEALERTS_humidityToString($humidity) ); + $prevTemperature = MOBILEALERTS_decodeTemperature($prevTemperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperature", + $prevTemperature ); + $prevHumidity = MOBILEALERTS_decodeHumidityDecimal($prevHumidity); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevHumidity", $prevHumidity ); + $prevTemperature2 = MOBILEALERTS_decodeTemperature($prevTemperature2); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperature2", + $prevTemperature2 ); + $prevHumidity2 = MOBILEALERTS_decodeHumidityDecimal($prevHumidity2); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevHumidity2", + $prevHumidity2 ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "T: " . $temperature . " H: " . $humidity ); +} + +sub MOBILEALERTS_Parse_10_d3 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10800" ); + MOBILEALERTS_Parse_d3( $hash, $message ); +} + +sub MOBILEALERTS_Parse_d3 ($$) { + my ( $hash, $message ) = @_; + my @data; + ( my ($txCounter), @data[ 0 .. 3 ] ) = unpack( "nnnnn", $message ); + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + for ( my $z = 0 ; $z < 4 ; $z++ ) { + my $eventTimeString = + MOBILEALERTS_convertEventTimeString( $data[$z], 13 ); + my $eventTime = MOBILEALERTS_convertEventTime( $data[$z], 13 ); + $data[$z] = MOBILEALERTS_convertOpenState( $data[$z] ); + + if ( $z == 0 ) { + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", $data[$z] ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "lastEvent", + $eventTime ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "lastEventString", + $eventTimeString ); + } + else { + MOBILEALERTS_readingsBulkUpdate( $hash, 4, "state" . $z, + $data[$z] ); + MOBILEALERTS_readingsBulkUpdate( $hash, 4, "lastEvent" . $z, + $eventTime ); + MOBILEALERTS_readingsBulkUpdate( $hash, 4, + "lastEvent" . $z . "String", + $eventTimeString ); + } + } +} + +sub MOBILEALERTS_Parse_12_d9 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10230" ); + MOBILEALERTS_Parse_d9( $hash, $message ); +} + +sub MOBILEALERTS_Parse_d9 ($$) { + my ( $hash, $message ) = @_; + my ( + $txCounter, $humidity3h, $humidity24h, $humidity7d, + $humidity30d, $temperature, $humidity + ) = unpack( "nCCCCnC", $message ); + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperature = MOBILEALERTS_decodeTemperature($temperature); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperature", $temperature ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureString", + MOBILEALERTS_temperatureToString($temperature) ); + $humidity = MOBILEALERTS_decodeHumidity($humidity); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity", $humidity ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityString", + MOBILEALERTS_humidityToString($humidity) ); + $humidity3h = MOBILEALERTS_decodeHumidity($humidity3h); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity3h", $humidity3h ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity3hString", + MOBILEALERTS_humidityToString($humidity3h) ); + $humidity24h = MOBILEALERTS_decodeHumidity($humidity24h); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity24h", $humidity3h ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity24hString", + MOBILEALERTS_humidityToString($humidity24h) ); + $humidity7d = MOBILEALERTS_decodeHumidity($humidity7d); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity7d", $humidity7d ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity7dString", + MOBILEALERTS_humidityToString($humidity7d) ); + $humidity30d = MOBILEALERTS_decodeHumidity($humidity30d); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity30d", $humidity30d ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity30dString", + MOBILEALERTS_humidityToString($humidity30d) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "T: " + . $temperature . " H: " + . $humidity . " " + . $humidity3h . "/" + . $humidity24h . "/" + . $humidity7d . "/" + . $humidity30d ); +} + +sub MOBILEALERTS_Parse_06_d6 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10300/MA10700" ); + MOBILEALERTS_Parse_d6( $hash, $message ); +} + +sub MOBILEALERTS_Parse_09_d6 ($$) { + my ( $hash, $message ) = @_; + MOBILEALERTS_readingsBulkUpdateIfChanged( $hash, 0, "deviceType", + "MA10320PRO" ); + MOBILEALERTS_Parse_d6( $hash, $message ); +} + +sub MOBILEALERTS_Parse_d6 ($$) { + my ( $hash, $message ) = @_; + my ( $txCounter, $temperatureIn, $temperatureOut, $humidityIn, + $prevTemperatureIn, $prevTemperatureOut, $prevHumidityIn ) + = unpack( "nnnnnnn", $message ); + + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "txCounter", + MOBILEALERTS_decodeTxCounter($txCounter) ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "triggered", + MOBILEALERTS_triggeredTxCounter($txCounter) ); + $temperatureIn = MOBILEALERTS_decodeTemperature($temperatureIn); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureIn", + $temperatureIn ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureStringIn", + MOBILEALERTS_temperatureToString($temperatureIn) ); + $temperatureOut = MOBILEALERTS_decodeTemperature($temperatureOut); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureOut", + $temperatureOut ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "temperatureStringOut", + MOBILEALERTS_temperatureToString($temperatureOut) ); + $humidityIn = MOBILEALERTS_decodeHumidity($humidityIn); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidity", $humidityIn ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "humidityString", + MOBILEALERTS_humidityToString($humidityIn) ); + $prevTemperatureIn = MOBILEALERTS_decodeTemperature($prevTemperatureIn); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperatureIn", + $prevTemperatureIn ); + $prevTemperatureOut = MOBILEALERTS_decodeTemperature($prevTemperatureOut); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevTemperatureOut", + $prevTemperatureOut ); + $prevHumidityIn = MOBILEALERTS_decodeHumidity($prevHumidityIn); + MOBILEALERTS_readingsBulkUpdate( $hash, 1, "prevHumidityIn", + $prevHumidityIn ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "state", + "In T: " + . $temperatureIn . " H: " + . $humidityIn + . " Out T: " + . $temperatureOut ); +} + +sub MOBILEALERTS_decodeTxCounter($) { + my ($txCounter) = @_; + return $txCounter & 0x3FFF; +} + +sub MOBILEALERTS_triggeredTxCounter($) { + my ($txCounter) = @_; + if ( ( $txCounter & 0x4000 ) == 0x4000 ) { + return 1; + } + return 0; +} + +sub MOBILEALERTS_decodeTemperature($) { + my ($temperature) = @_; + + #Overflow + return 9999 if ( ( $temperature & 0x2000 ) == 0x2000 ); + + #Illegal value + return -9999 if ( ( $temperature & 0x1000 ) == 0x1000 ); + + #Negativ Values + return ( 0x800 - ( $temperature & 0x7ff ) ) * -0.1 + if ( ( $temperature & 0x400 ) == 0x400 ); + + #Positiv Values + return ( $temperature & 0x7ff ) * 0.1; +} + +sub MOBILEALERTS_temperatureToString($) { + my ($temperature) = @_; + return "---" if ( $temperature < -1000 ); + return "OLF" if ( $temperature > 1000 ); + return $temperature . "°C"; +} + +sub MOBILEALERTS_decodeHumidity($) { + my ($humidity) = @_; + return 9999 if ( ( $humidity & 0x80 ) == 0x80 ); + return $humidity & 0x7F; +} + +sub MOBILEALERTS_decodeHumidityDecimal($) { + my ($humidity) = @_; + return ( $humidity & 0x3FF ) * 0.1; +} + +sub MOBILEALERTS_humidityToString($) { + my ($humidity) = @_; + return "---" if ( $humidity > 1000 ); + return $humidity . "%"; +} + +sub MOBILEALERTS_decodeWetness($) { + my ($wetness) = @_; + + return "dry" if ( $wetness & 0x01 ); + return "wet"; +} + +sub MOBILEALERTS_convertOpenState($) { + my ($value) = @_; + return "open" if ( $value & 0x8000 ); + return "closed"; +} + +sub MOBILEALERTS_convertEventTime($$) { + my ( $value, $timeScaleBitOffset ) = @_; + my $timeScaleFactor = ( $value >> $timeScaleBitOffset ) & 3; + $value = $value & ( ( 1 << $timeScaleBitOffset ) - 1 ); + if ( $timeScaleFactor == 0 ) { # days + return $value * 60 * 60 * 24; + } + elsif ( $timeScaleFactor == 1 ) { # hours + return $value * 60 * 60; + } + elsif ( $timeScaleFactor == 2 ) { # minutes + return $value * 60; + } + elsif ( $timeScaleFactor == 3 ) { # seconds + return $value; + } +} + +sub MOBILEALERTS_convertEventTimeString($$) { + my ( $value, $timeScaleBitOffset ) = @_; + my $timeScaleFactor = ( $value >> $timeScaleBitOffset ) & 3; + $value = $value & ( ( 1 << $timeScaleBitOffset ) - 1 ); + if ( $timeScaleFactor == 0 ) { # days + return $value . " d"; + } + elsif ( $timeScaleFactor == 1 ) { # hours + return $value . " h"; + } + elsif ( $timeScaleFactor == 2 ) { # minutes + return $value . " m"; + } + elsif ( $timeScaleFactor == 3 ) { # seconds + return $value . " s"; + } +} + +sub MOBILEALERTS_readingsBulkUpdate($$$$@) { + my ( $hash, $expert, $reading, $value, $changed ) = @_; + if ( $expert > $hash->{".expertMode"} ) { + delete $hash->{READINGS}{$reading}; + return undef; + } + my $i = $#{ $hash->{CHANGED} }; + my $res = readingsBulkUpdate( $hash, $reading, $value, $changed ); + $hash->{CHANGETIME}->[ $#{ $hash->{CHANGED} } ] = + $hash->{".updateTimestamp"} + if ( $#{ $hash->{CHANGED} } != $i ); # only add ts if there is a event to + return $res; +} + +sub MOBILEALERTS_readingsBulkUpdateIfChanged($$$$@) { + my ( $hash, $expert, $reading, $value, $changed ) = @_; + if ( $expert > $hash->{".expertMode"} ) { + delete $hash->{READINGS}{$reading}; + return undef; + } + my $i = $#{ $hash->{CHANGED} }; + my $res = readingsBulkUpdateIfChanged( $hash, $reading, $value, $changed ); + $hash->{CHANGETIME}->[ $#{ $hash->{CHANGED} } ] = + $hash->{".updateTimestamp"} + if ( $#{ $hash->{CHANGED} } != $i ); # only add ts if there is a event to + return $res; +} + +sub MOBILEALERTS_time2sec($) { + my ($timeout) = @_; + return ( "off", 0 ) unless ($timeout); + return ( "off", 0 ) if ( $timeout eq "off" ); + + my ( $h, $m ) = split( ":", $timeout ); + no warnings 'numeric'; + $h = int($h); + $m = int($m); + use warnings 'numeric'; + return ( + ( sprintf( "%03s:%02d", $h, $m ) ), + ( ( int($h) * 60 + int($m) ) * 60 ) + ); +} + +sub MOBILEALERTS_CheckRainSensorTimed($) { + my ($hash) = @_; + readingsBeginUpdate($hash); + MOBILEALERTS_CheckRainSensor( $hash, 0 ); + readingsEndUpdate( $hash, 1 ); + InternalTimer( + time_str2num( + substr( FmtDateTime( gettimeofday() + 3600 ), 0, 13 ) . ":00:00" + ), + "MOBILEALERTS_CheckRainSensorTimed", + $hash + ); +} + +sub MOBILEALERTS_CheckRainSensor($$) { + my ( $hash, $mmRain ) = @_; + + #Event + push @{ $hash->{CHANGED} }, "rain" if ( $mmRain > 0 ); + + #lastHour + my $actTime = $hash->{".updateTimestamp"}; + my $actH = ReadingsTimestamp( $hash->{NAME}, "mmRainActHour", $actTime ); + if ( substr( $actTime, 0, 13 ) eq substr( $actH, 0, 13 ) ) { + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainActHour", + $mmRain + ReadingsVal( $hash->{NAME}, "mmRainActHour", "0" ) ) + if ( $mmRain > 0 ); + } + else { + if ( + ( + time_str2num( substr( $actTime, 0, 13 ) . ":00:00" ) - + time_str2num( substr( $actH, 0, 13 ) . ":00:00" ) + ) > 3600 + ) + { + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainLastHour", 0 ); + } + else { + $hash->{".updateTimestamp"} = $actH; + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainLastHour", + ReadingsVal( $hash->{NAME}, "mmRainActHour", "0" ) ); + $hash->{".updateTimestamp"} = $actTime; + } + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainActHour", 0 ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainActHour", $mmRain ) + if ( $mmRain > 0 ); + } + + #Yesterday + my $actD = ReadingsTimestamp( $hash->{NAME}, "mmRainActDay", $actTime ); + Log3 "SOFORT", 1, "A" . substr( $actTime, 0, 10 ) . "A"; + if ( substr( $actTime, 0, 10 ) eq substr( $actD, 0, 10 ) ) { + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainActDay", + $mmRain + ReadingsVal( $hash->{NAME}, "mmRainActDay", "0" ) ) + if ( $mmRain > 0 ); + } + else { + if ( + ( + time_str2num( substr( $actTime, 0, 13 ) . " 00:00:00" ) - + time_str2num( substr( $actD, 0, 13 ) . " 00:00:00" ) + ) > 86400 + ) + { + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainYesterday", 0 ); + } + else { + $hash->{".updateTimestamp"} = + ReadingsTimestamp( $hash->{NAME}, "mmRainActDay", $actD ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainYesterday", + ReadingsVal( $hash->{NAME}, "mmRainActDay", "0" ) ); + $hash->{".updateTimestamp"} = $actTime; + } + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainActDay", 0 ); + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRainActDay", $mmRain ) + if ( $mmRain > 0 ); + } + MOBILEALERTS_readingsBulkUpdate( $hash, 0, "mmRain", + $mmRain + ReadingsVal( $hash->{NAME}, "mmRain", "0" ) ) + if ( $mmRain > 0 ); +} + +sub MOBILEALERTS_ActionDetector($) { + my ($hash) = @_; + my $name = "ActionDetector"; + unless ($init_done) { + Log3 $name, 5, + "$name MOBILELAERTS: ActionDetector run - fhem not intialized"; + InternalTimer( gettimeofday() + 60, + "MOBILEALERTS_ActionDetector", $hash ); + return; + } + Log3 $name, 5, "$name MOBILELAERTS: ActionDetector run"; + my $now = gettimeofday(); + my $nextTimer = $now + 60 * 60; # Check at least Hourly + for my $chash ( values %{ $modules{MOBILEALERTS}{defptr} } ) { + Log3 $name, 5, "$name MOBILELAERTS: ActionDetector " . $chash->{NAME}; + my $actCycle = AttrVal( $chash->{NAME}, "actCycle", undef ); + ( undef, my $sec ) = MOBILEALERTS_time2sec($actCycle); + if ( $sec == 0 ) { + readingsBeginUpdate($chash); + readingsBulkUpdateIfChanged( $chash, "actStatus", "switchedOff" ); + readingsEndUpdate( $chash, 1 ); + next; + } + my $lastRcv = ReadingsTimestamp( $chash->{NAME}, "lastRcv", undef ); + my $deadTime = undef; + readingsBeginUpdate($chash); + if ( defined($lastRcv) ) { + Log3 $name, 5, + "$name MOBILELAERTS: ActionDetector " + . $chash->{NAME} + . " lastRcv " + . $lastRcv; + $lastRcv = time_str2num($lastRcv); + $deadTime = $lastRcv + $sec; + if ( $deadTime < $now ) { + readingsBulkUpdateIfChanged( $chash, "actStatus", "dead" ); + $deadTime = $now + $sec; + } + else { + readingsBulkUpdate( $chash, "actStatus", "alive" ); + } + } + else { + readingsBulkUpdateIfChanged( $chash, "actStatus", "unknown" ); + $deadTime = $now + $sec; + } + readingsEndUpdate( $chash, 1 ); + if ( ( defined($deadTime) ) && ( $deadTime < $nextTimer ) ) { + $nextTimer = $deadTime; + Log3 $name, 5, + "$name MOBILELAERTS: ActionDetector " + . $chash->{NAME} + . " nextTime Set to " + . FmtDateTime($nextTimer); + } + } + Log3 $name, 5, + "$name MOBILELAERTS: MOBILEALERTS_ActionDetector nextRun " + . FmtDateTime($nextTimer); + InternalTimer( $nextTimer, "MOBILEALERTS_ActionDetector", $hash ); +} + +# Eval-Rückgabewert für erfolgreiches +# Laden des Moduls +1; + +=pod +=item device +=item summary virtual device for MOBILEALERTSGW +=item summary_DE virtuelles device für MOBILEALERTSGW +=begin html + + +

MOBILEALERTS

+
    + The MOBILEALERTS is a fhem module for the german MobileAlerts devices and TFA WEATHERHUB devices. +

    + The fhem module represents a MobileAlerts device. The connection is provided by the MOBILELAERTSGW module. + Currently supported: MA10100, MA10200, MA10230, MA10300, MA10410.
    + Supported but untested: MA10350, MA10650, MA10660, MA10700, MA10800
    +
    + + + Define +
      + define <name> MOBILEALERTS <deviceID>
      +
      + deviceID is the sensorcode on the sensor. +
    +
    + + + Readings +
      +
    • lastMsg
      The last message received (always for unknown devices, for known devices only if attr lastMsg is set).
    • +
    • deviceType
      The devicetype.
    • +
    • lastRcv
      Timestamp of last message.
    • +
    • actStatus
      Shows 'unknown', 'alive', 'dead', 'switchedOff' depending on attribut actCycle
    • +
    • txCounter
      Counter of last message.
    • +
    • triggered
      1=last message was triggered by a event.
    • +
    • tempertature, prevTemperature, temperatureIn, temperatureOut, prevTemperatureIn, prevTemperatureOut
      Temperature (depending on device and attribut expert).
    • +
    • tempertatureString, prevTemperatureString, temperatureInString, temperatureOutString, prevTemperatureInString, prevTemperatureOutString
      Temperature as string (depending on device and attribut expert).
    • +
    • state
      State of device (short actual reading)
    • +
    • humidity, prevHumidity, humidityIn, humidityOut, prevHumidityIn, prevHumidityOut
      Humidity (depending on device and attribut expert).
    • +
    • humidityString, prevHumidityString, humidityInString, humidityOutString, prevHumidityInString, prevHumidityOutString
      Humidity as string (depending on device and attribut expert).
    • +
    • wetness
      Shows if sensor detects water.
    • +
    • lastEvent, lastEvent<X> ,lastEventString, lastEvent<X>String
      Time when last event (rain) happend (MA10650 only).
    • +
    • mmRain, mmRainActHour, mmRainLastHour, mmRainActDay, mmRainYesterday
      Rain since reset of counter, current hour, last hour, current day, yesterday.
    • +
    • direction
      Direction of wind.
    • +
    • windSpeed, gustSpeed
      Windspeed.
    • +
    +
    + + + Set +
      +
    • set <name> clear <readings|counters>
      + Clears the readings (all) or counters (like mmRain).
    • +
    +
    + + + Get +
      + N/A +
    +
    +
    + + + Attributes +
      +
    • ignore
    • +
    • readingFnAttributes
    • +
    • lastMsg
      + If value 1 is set, the last received message will be logged as reading even if device is known. +
    • +
    • actCycle <[hhh:mm]|off>
      + This value triggers a 'alive' and 'not alive' detection. [hhh:mm] is the maximum silent time for the device. + The reading actStatus will show the states: 'unknown', 'alive', 'dead'. +
    • +
    • expert
      + Defines how many readings are show (0=only current, 1=previous, 4=all). +
    • +
    +
+ +=end html +=begin html_DE + + +

MOBILEALERTS

+
    + MOBILEALERTS ist ein FHEM-Modul fü die deutschen MobileAlerts Gerä und TFA WEATHERHUB. +

    + Dieses FHEM Modul stellt jeweils ein MobileAlerts Gerät dar. Die Verbindung wird durch das + MOBILELAERTSGW Modul bereitgestellt.
    + Aktuell werden unterstüzt: MA10100, MA10200, MA10230, MA10300, MA10410, MA10450, MA10320PRO, TFA 30.3312.02.
    + Unterstüzt aber ungetestet: MA10350, MA10650, MA10660, MA10700, MA10800
    +
    + + + Define +
      + define <name> MOBILEALERTS <deviceID>
      +
      + deviceID ist der Sensorcode auf dem Sensor. +
    +
    + + + Readings +
      +
    • lastMsg
      Die letzte empfangene Nachricht (immer für unbekannte Geräte, für bekannte nur wenn das Attribut lastMsg gesetzt ist).
    • +
    • deviceType
      Der Gerätetyü.
    • +
    • lastRcv
      Timestamp der letzten Nachricht.
    • +
    • actStatus
      Zeigt 'unknown', 'alive', 'dead', 'switchedOff' abhängig vom Attribut actCycle
    • +
    • txCounter
      Counter des letzten Nachricht (wird 0 nach Batteriewechsel).
    • +
    • triggered
      1=letzte Nachricht wurde von einem Ereignis ausgelöst.
    • +
    • tempertature, prevTemperature, temperatureIn, temperatureOut, prevTemperatureIn, prevTemperatureOut
      Temperatur (abhänging vom Gerät und dem Attribut expert).
    • +
    • tempertatureString, prevTemperatureString, temperatureInString, temperatureOutString, prevTemperatureInString, prevTemperatureOutString
      Temperatur als Zeichkette.
    • +
    • state
      State of device (short actual reading)
    • +
    • humidity, prevHumidity, humidityIn, humidityOut, prevHumidityIn, prevHumidityOut
      Luftfeuchte (abhänging vom Gerät und dem Attribut expert).
    • +
    • humidityString, prevHumidityString, humidityInString, humidityOutString, prevHumidityInString, prevHumidityOutString
      Luftfeuchte als Zeichenkette
    • +
    • wetness
      Zeigt ob der Sensors Wasser entdeckt.
    • +
    • lastEvent, lastEvent<X> ,lastEventString, lastEvent<X>String
      Zeitpunkt wann das letzte Event (Regen) stattgefunden hat (nur MA10650).
    • +
    • mmRain, mmRainActHour, mmRainLastHour, mmRainActDay, mmRainYesterday
      Regen seit dem letzten Reset des Counters, in der aktuellen Stunde, seit der letzten Stunden, am aktuellen Tagn, gestern.
    • +
    • direction
      Richtung des Winds.
    • +
    • windSpeed, gustSpeed
      Windgeschwindigkeit.
    • +
    +
    + + + Set +
      +
    • set <name> clear <readings|counters>
      + Löscht die Readings (alle) oder Counter (wie mmRain).
    • +
    +
    + + + Get +
      + N/A +
    +
    +
    + + + Attributes +
      +
    • ignore
    • +
    • readingFnAttributes
    • +
    • lastMsg
      + Wenn dieser Wert auf 1 gesetzt ist, wird die letzte erhaltene Nachricht als Reading gelogt auch wenn das Gerä bekannt ist. +
    • +
    • actCycle <[hhh:mm]|off>
      + Dieses Attribut ermölicht eine 'nicht erreichbarkeit' Erkennung. + [hhh:mm] ist die maximale Zeit, innerhalb der keine Nachrichten empfrangen wird. + Das Reading actStatus zeigt den Status 'unknown', 'alive', 'dead' an. +
    • +
    • expert
      + Gibt an wie detailiert die Readings angezeigt werden (0=nur aktuelle, 1=mit vorhergehenden, 4=alle). +
    • +
    +
+ +=end html_DE +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index ed148c05f..578c4c9be 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -238,10 +238,12 @@ FHEM/49_IPCAM.pm mfr69bs Sonstiges FHEM/49_SSCAM.pm DS_Starter Sonstiges FHEM/49_TBot_List.pm viegener Unterstuetzende Dienste FHEM/50_HP1000.pm loredo Heizungssteuerung/Raumklima +FHEM/50_MOBILEALERTSGW.pm MarkusF Sonstige Systeme FHEM/50_WS300.pm Dirk SlowRF FHEM/50_TelegramBot.pm viegener Unterstuetzende Dienste FHEM/51_I2C_BH1750.pm arnoaugustin Einplatinencomputer (bitte auch PM) FHEM/51_I2C_BMP180.pm Dirk Einplatinencomputer +FHEM/51_MOBILEALERTS.pm MarkusF Sonstige Systeme FHEM/51_Netzer.pm klausw Sonstige Systeme FHEM/51_RPI_GPIO.pm klausw Einplatinencomputer FHEM/52_I2C_DS1307 ntruchsess Sonstige Systeme