mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-09 20:57:11 +00:00
70_CanOverEthernet: send and receive analog and digital values
git-svn-id: https://svn.fhem.de/fhem/trunk@20339 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
c803268e93
commit
50edec0970
@ -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
|
||||
|
@ -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('CCS<S<S<S<CCCC', $targetNode,5,22.7,0,0,0,1,0,0,0);
|
||||
|
||||
$socket->send($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('CCS<S<S<S<CCCC', $targetNode, $pageIndex, @pageVals, @pageTypes);
|
||||
|
||||
$socket->send($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.
|
||||
</ul>
|
||||
|
||||
<a name="CanOverEthernetset"><b>Set</b></a>
|
||||
<ul>
|
||||
<li><a href="#sendDataAnalog">sendDataAnalog</a><br>Sends analog values.<br>Example:
|
||||
<code>set <name> sendDataAnalog <Target-IP> <CAN-Channel> <Index>=<Value>;<Type><br>
|
||||
set coe sendDataAnalog 192.168.1.1 3 1=22.7;1 2=18.0;1
|
||||
</code>
|
||||
</li>
|
||||
<li><a href="#sendDataDigital">sendDataDigital</a><br>Sends digital values. This can be 0 or 1.<br>Example:
|
||||
<code>set <name> sendDataDigital <Target-IP> <CAN-Channel> <Index>=<Value><br>
|
||||
set coe sendDataDigital 192.168.1.1 3 1=1 2=0
|
||||
</code>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
=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.
|
||||
</ul>
|
||||
<a name="CanOverEthernetset"><b>Set</b></a>
|
||||
<ul>
|
||||
<li><a href="#sendDataAnalog">sendDataAnalog</a><br>Sendet analoge Werte.<br>Beispiel:
|
||||
<code>set <name> sendDataAnalog <Target-IP> <CAN-Channel> <Index>=<Value>;<Type><br>
|
||||
set coe sendDataAnalog 192.168.1.1 3 1=22.7;1 2=18.0;1
|
||||
</code>
|
||||
</li>
|
||||
<li><a href="#sendDataDigital">sendDataDigital</a><br>Sends digitale Werte. Also nur 0 oder 1.<br>Beispiel:
|
||||
<code>set <name> sendDataDigital <Target-IP> <CAN-Channel> <Index>=<Value><br>
|
||||
set coe sendDataDigital 192.168.1.1 3 1=1 2=0
|
||||
</code>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
=end html_DE
|
||||
|
||||
|
@ -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 {
|
||||
<b>Attributes</b>
|
||||
<br><br>
|
||||
<ul>
|
||||
<li><code>readingsConfig {index=reading-name}</code><br>This maps received values to readings. eg <code>1=Flowrate_Solar 2=T.Solar_Backflow</code></li>
|
||||
<li><a href="#readingsConfigAnalog">readingsConfigAnalog {index=reading-name}</a><br>This maps received analog values to readings. eg <code>1=Flowrate_Solar 2=T.Solar_Backflow</code></li>
|
||||
<li><a href="#readingsConfigDigital">readingsConfigDigital {index=reading-name}</a><br>This maps received digital values to readings. eg <code>1=Pump_Solar_Power 2=Pump_Water_Power</code></li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
@ -285,7 +308,8 @@ sub COE_Node_Set {
|
||||
<b>Attributes</b>
|
||||
<br><br>
|
||||
<ul>
|
||||
<li><code>readingsConfig {index=reading-name}</code><br>Ordnet Werte einem Reading zu. zB <code>1=Durchfluss_Solar 2=T.Solar_Rücklauf</code></li>
|
||||
<li><a href="#readingsConfigAnalog">readingsConfigAnalog {index=reading-name}</a><br>Ordnet analoge Werte einem Reading zu. zB <code>1=Durchfluss_Solar 2=T.Solar_Rücklauf</code></li>
|
||||
<li><a href="#readingsConfigDigital">readingsConfigDigital {index=reading-name}</a><br>Ordnet digitale Werte einem Reading zu. zB <code>1=Solarpumpe_Status 2=Wasserpumpe_Status</code></li>
|
||||
</ul>
|
||||
|
||||
=end html_DE
|
||||
|
Loading…
x
Reference in New Issue
Block a user