############################################################################## # # 71_COE_Node.pm # # This file is part of Fhem. # # Fhem is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Fhem is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Fhem. If not, see . # ############################################################################## # # COE_Node (c) Martin Gutenbrunner / https://github.com/delmar43/FHEM # # This module is designed to work as a logical device in connection with # 70_CanOverEthernet as a physical device. # # Discussed in FHEM Forum: https://forum.fhem.de/index.php/topic,96170.0.html # # $Id$ # ############################################################################## package main; use strict; use warnings; sub COE_Node_Initialize { my ($hash) = @_; $hash->{DefFn} = "COE_Node_Define"; $hash->{ParseFn} = "COE_Node_Parse"; $hash->{UndefFn} = "COE_Node_Undef"; $hash->{GetFn} = "COE_Node_Get"; $hash->{SetFn} = "COE_Node_Set"; $hash->{AttrList} = "readingsConfigAnalog:textField-long readingsConfigDigital:textField-long " . $readingFnAttributes; $hash->{Match} = "^.*"; return undef; } sub COE_Node_Define { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); my $name = $a[0]; my $module = $a[1]; my $canNodeId = $a[2]; if(@a < 3 || @a > 3) { my $msg = "COE_Node ($name) - Wrong syntax: define COE_Node "; Log3 $name, 1, $msg; return $msg; } $hash->{NAME} = $name; AssignIoPort($hash); my $ioDevName = $hash->{IODev}{NAME}; my $logDevAddress = $ioDevName.'_'.$canNodeId; Log3 $name, 5, "COE_Node ($name) - Define: Logical device address: $logDevAddress"; $modules{COE_Node}{defptr}{$logDevAddress} = $hash; Log3 $name, 3, "COE_Node ($name) - Define done ... module=$module, canNodeId=$canNodeId"; $hash->{helper}{CAN_NODE_ID} = $canNodeId; readingsSingleUpdate($hash, 'state', 'defined', 1); return undef; } sub COE_Node_Parse { my ( $io_hash, $buf) = @_; my $ioDevName = $io_hash->{NAME}; my ( $canNodeId, $canNodePartId ) = unpack 'C C', $buf; my $bytes = substr $buf, 2; my $logDevAddress = $ioDevName.'_'.$canNodeId; # wenn bereits eine Gerätedefinition existiert (via Definition Pointer aus Define-Funktion) if(my $hash = $modules{COE_Node}{defptr}{$logDevAddress}) { COE_Node_HandleData($hash, $canNodeId, $canNodePartId, $bytes); return $hash->{NAME}; } else { # Keine Gerätedefinition verfügbar # Daher Vorschlag define-Befehl: Log3 $ioDevName, 5, "COE_Node-Parse ($ioDevName) - No definition for $logDevAddress. Suggesting autocreate for canNodeId=$canNodeId"; my $ioName = $io_hash->{NAME}; return "UNDEFINED COE_Node_".$ioDevName."_".$canNodeId." COE_Node $canNodeId"; } } sub COE_Node_HandleData { my ( $hash, $canNodeId, $canNodePartId, $bytes ) = @_; my $name = $hash->{NAME}; readingsBeginUpdate($hash); if ( $canNodePartId > 0 ) { COE_Node_HandleAnalogValues($hash, $canNodePartId, $bytes); } else { COE_Node_HandleDigitalValues($hash, $canNodePartId, $bytes); } readingsEndUpdate($hash, 1); } sub COE_Node_HandleAnalogValues { my ( $hash, $canNodePartId, $bytes ) = @_; my @valuesAndTypes = unpack 's s s s C C C C', $bytes; my @values = @valuesAndTypes[0..3]; my @types = @valuesAndTypes[4..7]; 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 $entryId = $outputId; my $existingConfig = exists $readingsMapping[$entryId]; my $value = $values[$i]; my $type = $types[$i]; my $vorz = (substr $value, 0,1); if ($existingConfig) { if ($type == 1 || $type == 10) { $value = (substr $value, 0, (length $value)-1) . "." . (substr $value, -1); } elsif ($type == 13) { $value = (substr $value, 0, (length $value)-2) . "." . (substr $value, -2); } if ( COE_Node_BeginsWith($value, '-.') ) { $value = "-0." . (substr $value, 2, (length $value)); } if ( COE_Node_BeginsWith($value, '.') ) { $value = "0$value"; } my $reading = $readingsMapping[$entryId]; readingsBulkUpdateIfChanged( $hash, $reading, $value ); Log3 $name, 4, "COE_Node ($name) - [$canNodeId][$canNodePartId][$i][$entryId][type=$type][value=$value] configured: $reading"; } else { Log3 $name, 0, "COE_Node ($name) - [$canNodeId][$canNodePartId][$i][$entryId][type=$type][value=$value] $entryId not configured. Skipping."; } } } sub COE_Node_HandleDigitalValues { my ( $hash, $canNodePartId, $bytes ) = @_; my $name = $hash->{NAME}; my $canNodeId = $hash->{helper}{CAN_NODE_ID}; my $values = unpack 'b*', $bytes; my @bits = split //, $values; 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, 4, "COE_Node ($name) - [$canNodeId][$canNodePartId][".($i+1)."] = $bits[$i]"; } } sub COE_Node_BeginsWith { return substr($_[0], 0, length($_[1])) eq $_[1]; } sub COE_Node_Undef { my ($hash, $arg) = @_; my $name = $hash->{NAME}; return undef; } sub COE_Node_Get { return undef; } sub COE_Node_Set { return undef; } 1; =pod =item device =item summary Single CanOverEthernet node which is created automatically by CanOverEthernet =item summary_DE Ein einzelner CanOverEthernet Node. Wird automatisch erstellt. =begin html

COE_Node

Define
    define <name> COE_Node <CAN-Node ID>

    Defines a CanOverEthernet node. FHEM will automatically create these.
    Example:
      define COE_Node_coe_2 COE_Node 2
    Assigment of readings to incoming values is done in the attribue 'readingsConfig'.


Attributes

=end html =begin html_DE

COE_Node

Define
    define <name> COE_Node <CAN-Node ID>

    Repräsentiert einen einzelnen CanOverEthernet Node. Wird normalerweise automatisch erstellt.
    Beispiel:
      define COE_Node_coe_2 COE_Node 2
    Die eintreffenden Werte müssen noch im Attribut 'readingsConfig' einem Reading zugewiesen werden.


Attributes

=end html_DE =cut