From 50edec0970ecd533f73d7f9a656adfee4b87fe32 Mon Sep 17 00:00:00 2001 From: delmar <> Date: Wed, 9 Oct 2019 19:02:50 +0000 Subject: [PATCH] 70_CanOverEthernet: send and receive analog and digital values git-svn-id: https://svn.fhem.de/fhem/trunk@20339 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/70_CanOverEthernet.pm | 226 +++++++++++++++++++++++++++----- fhem/FHEM/71_COE_Node.pm | 104 +++++++++------ 3 files changed, 256 insertions(+), 75 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 5125fb666..391a2d60f 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. + - feature: 70_CanOverEthernet: send and receive analog and digital values - bugfix: 93_DbRep: fix warnings - feature: 22_HOMEMODE: added attribs HomeCMDbattery & HomeCMDbatteryNormal added new reading lastBatteryNormal diff --git a/fhem/FHEM/70_CanOverEthernet.pm b/fhem/FHEM/70_CanOverEthernet.pm index 8668cafca..718d223a3 100644 --- a/fhem/FHEM/70_CanOverEthernet.pm +++ b/fhem/FHEM/70_CanOverEthernet.pm @@ -38,10 +38,7 @@ use DevIo; sub CanOverEthernet_Initialize($) { my ($hash) = @_; - -# require "$attr{global}{modpath}/FHEM/DevIo.pm"; - $hash->{GetFn} = "CanOverEthernet_Get"; $hash->{SetFn} = "CanOverEthernet_Set"; $hash->{DefFn} = "CanOverEthernet_Define"; $hash->{UndefFn} = "CanOverEthernet_Undef"; @@ -77,7 +74,7 @@ sub CanOverEthernet_Define($$) { my $conn = IO::Socket::INET->new(Proto=>"udp",LocalPort=>$portno); $hash->{FD} = $conn->fileno(); - $hash->{CD} = $conn; # sysread / close won't work on fileno + $hash->{CD} = $conn; $selectlist{$name} = $hash; Log3 $name, 3, "CanOverEthernet ($name) - Awaiting UDP connections on port $portno\n"; @@ -103,7 +100,7 @@ sub CanOverEthernet_Read($) { my $data; $hash->{STATE} = 'Last: '.gmtime(); - $hash->{CD}->recv($buf, 16); + $hash->{CD}->recv($buf, 14); $data = unpack('H*', $buf); Log3 $name, 5, "CanOverEthernet ($name) - Client said $data"; @@ -111,55 +108,187 @@ sub CanOverEthernet_Read($) { } -sub CanOverEthernet_Get ($@) { - my ( $hash, $param ) = @_; - - my $name = $hash->{NAME}; - Log3 $name, 5, "CanOverEthernet ($name) - Get done ..."; - return undef; -} - sub CanOverEthernet_Set ($@) { - my ( $hash, $name, $cmd, $args ) = @_; + my ( $hash, $name, $cmd, @args ) = @_; - if ( 'sendData' eq $cmd ) { - CanOverEthernet_parseSendDataCommand( $hash, $name, $args ); + if ( 'sendDataAnalog' eq $cmd ) { + + my ( $targetIp, $targetNode, $valuesRef, $typesRef ) = CanOverEthernet_parseAnalog( $hash, $name, @args ); + return CanOverEthernet_sendDataAnalog ( $hash, $targetIp, $targetNode, $valuesRef, $typesRef ); + + } elsif ( 'sendDataDigital' eq $cmd ) { + + my ( $targetIp, $targetNode, @values ) = CanOverEthernet_parseDigital( $hash, $name, @args ); + return CanOverEthernet_sendDataDigital ( $hash, $targetIp, $targetNode, @values ); } - Log3 $name, 5, "CanOverEthernet ($name) - Set done ..."; - return 'sendData'; + return 'sendDataAnalog sendDataDigital'; } -sub CanOverEthernet_parseSendDataCommand { +sub CanOverEthernet_parseDigital { my ( $hash, $name, @args ) = @_; - # args: Target-IP Target-Node Index=Value + my $targetIp = $args[0]; + my $targetNode = $args[1]; + my @values = @args[2..$#args]; + my $page; + + for ( my $i=0; $i <= $#values; $i++ ) { + my ( $index, $value ) = split /[=]/, $values[$i]; + + if ( $index < 0 || $index > 32 ) { + Log3 $name, 0, "CanOverEthernet ($name) - parsing sendDataDigital: index $index is out of bounds [1-32]. Value will not be sent."; + next; + } + + $values[$index-1] = $value; + } + + return ( $targetIp, $targetNode, @values ); +} + +sub CanOverEthernet_parseAnalog { + my ( $hash, $name, @args ) = @_; + + # args: Target-IP Target-Node Index=Value;Type my $targetIp = $args[0]; my $targetNode = $args[1]; - + my @valuesAndTypes = @args[2..$#args]; + my @values; + my @types; + my $page; + + for ( my $i=0; $i <= $#valuesAndTypes; $i++ ) { + my ( $index, $value, $type ) = split /[=;]/, $valuesAndTypes[$i]; + + if ( $index < 1 || $index > 32 ) { + Log3 $name, 0, "CanOverEthernet ($name) - parsing sendDataAnalog: index $index is out of bounds [1-32]. Value will not be sent."; + next; + } + + my $pIndex; #index inside of page (eg 18 is pIndex 2 on page 1) + + if ( $index < 5 ) { + $page = 1; + } elsif ( $index < 9 ) { + $page = 2; + } elsif ( $index < 13 ) { + $page = 3; + } elsif ( $index < 17 ) { + $page = 4; + } elsif ( $index < 21 ) { + $page = 5; + } elsif ( $index < 25 ) { + $page = 6; + } elsif ( $index < 29 ) { + $page = 7; + } elsif ( $index < 33 ) { + $page = 8; + } + + $pIndex = $index - (($page-1)*4) -1; + $types[$page][$pIndex] = $type; + $values[$page][$pIndex] = $value; + } + + return ( $targetIp, $targetNode, \@values, \@types ); +} + +sub CanOverEthernet_sendDataAnalog { + my ( $hash, $targetIp, $targetNode, $valuesRef, $typesRef ) = @_; + my $name = $hash->{NAME}; + + my @values = @{$valuesRef}; + my @types = @{$typesRef}; + my $socket = new IO::Socket::INET ( - PeerAddr=>$targetIp, #PeerAddr von $sock ist eingegebener Paramenter $ipaddr - PeerPort=>5441, #PeerPort von $sock ist eingegebener Paramenter $port - Proto=>'udp' #Transportprotokoll: UDP + PeerAddr=>$targetIp, + PeerPort=>5441, + Proto=>"udp" ); - if ($socket) { + if ( !$socket ) { + Log3 $name, 0, "CanOverEthernet ($name) - sendDataAnalog failed to create network socket"; - my $out = pack('CCSsend($out, 16); - $socket->close(); - Log3 $name, 4, "CanOverEthernet ($name) - sendData done."; - - } else { - Log3 $name, 0, "CanOverEthernet ($name) - sendData failed to create network socket"; return; - } - + + for ( my $pageIndex=1; $pageIndex <= 4; $pageIndex++ ) { + my $nrEntries = @{$values[$pageIndex] // []}; + Log3 $name, 5, "CanOverEthernet ($name) - page $pageIndex has $nrEntries entries."; + if ( $nrEntries == 0 ) { + next; + } + + my @pageVals; + my @pageTypes; + for ( my $valIndex=0; $valIndex < 4; $valIndex++ ) { + Log3 $name, 4, "CanOverEthernet ($name) - value $valIndex = $values[$pageIndex][$valIndex] type=$types[$pageIndex][$valIndex]"; + my $val = $values[$pageIndex][$valIndex]; + my $type = $types[$pageIndex][$valIndex]; + $pageVals[$valIndex] = CanOverEthernet_getValue( $name, $val ); + $pageTypes[$valIndex] = ( defined $type ? $type : 0); + } + my $out = pack('CCSsend($out); + } + + $socket->close(); +} + +sub CanOverEthernet_sendDataDigital { + my ( $hash, $targetIp, $targetNode, @values ) = @_; + my $name = $hash->{NAME}; + + my $socket = new IO::Socket::INET ( + PeerAddr=>$targetIp, + PeerPort=>5441, + Proto=>"udp" + ); + + if ( !$socket ) { + Log3 $name, 0, "CanOverEthernet ($name) - sendDataDigital failed to create network socket"; + + return; + } + + # prepare digital values (4 bytes, 32 bits for 32 values) + my $digiVals = ''; + for (my $idx=0; $idx < 32; $idx++) { + + if(defined($values[0][$idx])) { + $digiVals = $digiVals . ($values[0][$idx] == '1' ? "\001" : "\000"); + } else { + $digiVals = $digiVals . "\000"; + } + } + + # pad the rest of the 14 bytes with zeroes + for (my $idx=32; $idx < 96; $idx++) { + $digiVals = $digiVals."\000"; + } + + my $out = pack('CCb*', $targetNode, 0, $digiVals); + $socket->send($out); + $socket->close(); +} + +sub CanOverEthernet_getValue { + my ( $name, $input ) = @_; + if ( ! defined $input ) { + return 0; + } + + #type 1 needs to have 1 decimal place + #type 13 needs to have 2 decimal places + #but the value is submitted without the dot + + $input =~ s/\.//; + return $input; } 1; @@ -190,6 +319,20 @@ sub CanOverEthernet_parseSendDataCommand { are created on-the-fly. +Set + + =end html =begin html_DE @@ -211,6 +354,19 @@ sub CanOverEthernet_parseSendDataCommand { Die eingehenden Daten werden als readings in eigenen COE_Node devices gespeichert. Diese devices werden automatisch angelegt, sobald Daten dafür empfangen werden. +Set + =end html_DE diff --git a/fhem/FHEM/71_COE_Node.pm b/fhem/FHEM/71_COE_Node.pm index 21fa29c33..9f793cf62 100644 --- a/fhem/FHEM/71_COE_Node.pm +++ b/fhem/FHEM/71_COE_Node.pm @@ -44,7 +44,7 @@ sub COE_Node_Initialize { $hash->{GetFn} = "COE_Node_Get"; $hash->{SetFn} = "COE_Node_Set"; - $hash->{AttrList} = "readingsConfig " . $readingFnAttributes; + $hash->{AttrList} = "readingsConfigAnalog readingsConfigDigital " . $readingFnAttributes; $hash->{Match} = "^.*"; return undef; @@ -112,40 +112,11 @@ sub COE_Node_HandleData { my ( $hash, $canNodeId, $canNodePartId, $bytes ) = @_; my $name = $hash->{NAME}; - my $readings = AttrVal($name, 'readingsConfig', undef); - if (! defined $readings) { - Log3 $name, 0, "COE_Node ($name) - No config found. Please set readingsConfig accordingly."; - return undef; - } - - Log3 $name, 4, "COE_Node ($name) - Config found: $readings"; - - # incoming data: 05011700f3000000000001010000 - # extract readings from config, so we know, how to assign each value to a reading - # readings are separated by space - # format: index=name - # example - # 1=T.Solar 2=T.Solar_RL - $hash->{helper}{mapping} = (); - my @readingsArray = split / /, $readings; - foreach my $readingsEntry (@readingsArray) { - Log3 $name, 5, "COE_Node ($name) - $readingsEntry"; - - my @entry = split /=/, $readingsEntry; - $hash->{helper}{mapping}{$entry[0]} = makeReadingName($entry[1]); - } - - if ($canNodeId != $hash->{helper}{CAN_NODE_ID}) { - Log3 $name, 0, "COE_Node ($name) - defined nodeId $hash->{canNodeId} != message-nodeId $canNodeId. Skipping message."; - return undef; - } - readingsBeginUpdate($hash); if ( $canNodePartId > 0 ) { COE_Node_HandleAnalogValues($hash, $canNodePartId, $bytes); } else { COE_Node_HandleDigitalValues($hash, $canNodePartId, $bytes); -# Log3 $name, 3, "COE_Node ($name) - [$canNodeId][$canNodePartId][] digital value. skipping for now"; } readingsEndUpdate($hash, 1); @@ -161,11 +132,39 @@ sub COE_Node_HandleAnalogValues { my $canNodeId = $hash->{helper}{CAN_NODE_ID}; my $name = $hash->{NAME}; + my $readings = AttrVal($name, 'readingsConfigAnalog', undef); + if (! defined $readings) { + Log3 $name, 0, "COE_Node ($name) - No config found. Please set readingsConfigAnalog accordingly."; + return undef; + } + + Log3 $name, 4, "COE_Node ($name) - Config found: $readings"; + + # incoming data: 05011700f3000000000001010000 + # extract readings from config, so we know, how to assign each value to a reading + # readings are separated by space + # format: index=name + # example + # 1=T.Solar 2=T.Solar_RL + my @readingsArray = split / /, $readings; + my @readingsMapping; + foreach my $readingsEntry (@readingsArray) { + Log3 $name, 5, "COE_Node ($name) - $readingsEntry"; + + my @entry = split /=/, $readingsEntry; + $readingsMapping[$entry[0]] = makeReadingName($entry[1]); + } + + if ($canNodeId != $hash->{helper}{CAN_NODE_ID}) { + Log3 $name, 0, "COE_Node ($name) - defined nodeId $hash->{canNodeId} != message-nodeId $canNodeId. Skipping message."; + return undef; + } + #iterate through data entries. 4 entries max per incoming UDP packet for (my $i=0; $i < 4; $i++) { - my $outputId = ($i+($canNodePartId-1)*4 +1); + my $outputId = ($i + ($canNodePartId-1) * 4 +1); my $entryId = $outputId; - my $existingConfig = exists $hash->{helper}{mapping}{$entryId}; + my $existingConfig = exists $readingsMapping[$entryId]; my $value = $values[$i]; my $type = $types[$i]; @@ -181,12 +180,12 @@ sub COE_Node_HandleAnalogValues { $value = "0$value"; } - my $reading = $hash->{helper}{mapping}{$entryId}; + my $reading = $readingsMapping[$entryId]; readingsBulkUpdateIfChanged( $hash, $reading, $value ); - Log3 $name, 3, "COE_Node ($name) - [$canNodeId][$canNodePartId][$outputId][$entryId][type=$type][value=$value] configured: $reading"; + Log3 $name, 4, "COE_Node ($name) - [$canNodeId][$canNodePartId][$i][$entryId][type=$type][value=$value] configured: $reading"; } else { - Log3 $name, 3, "COE_Node ($name) - [$canNodeId][$canNodePartId][$outputId][$entryId][type=$type][value=$value] $entryId not configured. Skipping."; + Log3 $name, 0, "COE_Node ($name) - [$canNodeId][$canNodePartId][$i][$entryId][type=$type][value=$value] $entryId not configured. Skipping."; } } } @@ -199,10 +198,33 @@ sub COE_Node_HandleDigitalValues { my $values = unpack 'b*', $bytes; my @bits = split //, $values; - for (my $i=0; $i < 16; $i++) { - my $reading = $hash->{helper}{mapping}{$i+1}; + my $readings = AttrVal($name, 'readingsConfigDigital', undef); + if (! defined $readings) { + Log3 $name, 0, "COE_Node ($name) - No config found. Please set readingsConfigDigital accordingly."; + return undef; + } + + Log3 $name, 4, "COE_Node ($name) - Config found: $readings"; + + my @readingsArray = split / /, $readings; + my @readingsMapping; + foreach my $readingsEntry (@readingsArray) { + Log3 $name, 5, "COE_Node ($name) - $readingsEntry"; + + my @entry = split /=/, $readingsEntry; + $readingsMapping[$entry[0]] = makeReadingName($entry[1]); + } + + if ($canNodeId != $hash->{helper}{CAN_NODE_ID}) { + Log3 $name, 0, "COE_Node ($name) - defined nodeId $hash->{canNodeId} != message-nodeId $canNodeId. Skipping message."; + return undef; + } + + + for (my $i=0; $i < 32; $i++) { + my $reading = $readingsMapping[$i+1]; readingsBulkUpdateIfChanged( $hash, $reading, $bits[$i] ); - Log3 $name, 3, "COE_Node ($name) - [$canNodeId][$canNodePartId][".($i+1)."] = $bits[$i]"; + Log3 $name, 4, "COE_Node ($name) - [$canNodeId][$canNodePartId][".($i+1)."] = $bits[$i]"; } } @@ -256,7 +278,8 @@ sub COE_Node_Set { Attributes

=end html @@ -285,7 +308,8 @@ sub COE_Node_Set { Attributes

=end html_DE