| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- # $Id: 44_S7_DWrite.pm 15539 2017-12-01 21:52:13Z charlie71 $
- ##############################################
- package main;
- use strict;
- use warnings;
- use Time::HiRes qw(gettimeofday usleep);
- my %sets = (
- "on" => "",
- "off" => "",
- "toggle" => ""
- );
- my %gets = (
- "reading" => "",
- "STATE" => ""
- );
- sub __isfloat {
- my $val = shift;
- # return $val =~ m/^\d+.\d+$/;
- return $val =~ m/^[-+]?\d*\.?\d*$/;
- #[-+]?[0-9]*\.?[0-9]*
- }
- #####################################
- sub S7_DWrite_Initialize($) {
- my $hash = shift @_;
- # Provider
- # Consumer
- $hash->{Match} = "^DW";
- $hash->{DefFn} = "S7_DWrite_Define";
- $hash->{UndefFn} = "S7_DWrite_Undef";
- $hash->{SetFn} = "S7_DWrite_Set";
- $hash->{ParseFn} = "S7_DWrite_Parse";
- $hash->{AttrFn} = "S7_DWrite_Attr";
- $hash->{AttrList} = "IODev trigger_length " . $readingFnAttributes;
- main::LoadModule("S7");
- }
- #####################################
- sub S7_DWrite_Undef($$) {
- my ( $hash, $name ) = @_;
- RemoveInternalTimer($hash);
- Log3 $name, 4,
- "S7_DWrite ("
- . $hash->{IODev}{NAME}
- . "): undef "
- . $hash->{NAME}
- . " Adress:"
- . $hash->{ADDRESS};
- delete( $modules{S7_DWrite}{defptr} );
- return undef;
- }
- #####################################
- sub S7_DWrite_Define($$) {
- my ( $hash, $def ) = @_;
- my @a = split( "[ \t][ \t]*", $def );
- my ( $name, $area, $DB, $position );
- my $byte;
- my $bit;
- $name = $a[0];
- Log3 $name, 5, "S7_DWrite_Define called";
- AssignIoPort($hash); # logisches modul an physikalisches binden !!!
- my $sname = $hash->{IODev}{NAME};
- if ( uc $a[2] =~ m/^[QIMN](\d*)/ ) {
- $area = "db";
- $DB = 0;
- my $startposition;
- my $Offset;
- if ( uc $a[2] =~ m/^Q(\d*)/ ) {
- $startposition = 1;
- if ( $hash->{IODev}{S7TYPE} eq "LOGO7" ) {
- $Offset = 942;
- }
- elsif ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
- $Offset = 1064;
- }
- else {
- my $msg =
- "wrong syntax : define <name> S7_DWrite {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DWrite {I|Q|M|NI|NQ}1..24";
- Log3 undef, 2, $msg;
- return $msg;
- }
- }
- elsif ( uc $a[2] =~ m/^I(\d*)/ ) {
- $startposition = 1;
- if ( $hash->{IODev}{S7TYPE} eq "LOGO7" ) {
- $Offset = 923;
- }
- elsif ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
- $Offset = 1024;
- }
- else {
- my $msg =
- "wrong syntax : define <name> S7_DWrite {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DWrite {I|Q|M|NI|NQ}1..24";
- Log3 undef, 2, $msg;
- return $msg;
- }
- }
- elsif ( uc $a[2] =~ m/^NI(\d*)/ ) {
- $startposition = 2;
- if ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
- $Offset = 1246;
- }
- else {
- my $msg =
- "wrong syntax : define <name> S7_DWrite {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DWrite {I|Q|M|NI|NQ}1..24";
- Log3 undef, 2, $msg;
- return $msg;
- }
- }
- elsif ( uc $a[2] =~ m/^NQ(\d*)/ ) {
- $startposition = 2;
- if ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
- $Offset = 1390;
- }
- else {
- my $msg =
- "wrong syntax : define <name> S7_DWrite {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DWrite {I|Q|M|NI|NQ}1..24";
- Log3 undef, 2, $msg;
- return $msg;
- }
- }
- elsif ( uc $a[2] =~ m/^M(\d*)/ ) {
- $startposition = 1;
- if ( $hash->{IODev}{S7TYPE} eq "LOGO7" ) {
- $Offset = 948;
- }
- elsif ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
- $Offset = 1104;
- }
- else {
- my $msg =
- "wrong syntax : define <name> S7_DWrite {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DWrite {I|Q|M|NI|NQ}1..24";
- Log3 undef, 2, $msg;
- return $msg;
- }
- }
- else {
- my $msg =
- "wrong syntax : define <name> S7_DWrite {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DWrite {I|Q|M|NI|NQ}1..24";
- Log3 undef, 2, $msg;
- return $msg;
- }
- $position =
- ( $Offset * 8 ) + int( substr( $a[2], $startposition ) ) - 1;
- $byte = int( $position / 8 );
- $bit = ( $position % 8 );
- }
- else {
- $area = lc $a[2];
- $DB = $a[3];
- $position = $a[4];
- if ( $area ne "inputs"
- && $area ne "outputs"
- && $area ne "flags"
- && $area ne "db" )
- {
- my $msg =
- "wrong syntax: define <name> S7_DWrite {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DWrite {I|Q|M}1..24";
- Log3 undef, 2, $msg;
- return $msg;
- }
- my @address = split( /\./, $position );
- if ( int(@address) == 2 ) {
- $byte = $address[0];
- $bit = $address[1];
- }
- else {
- $byte = int( $address[0] / 8 );
- $bit = ( $address[0] % 8 );
- }
- $position = ( $byte * 8 ) + $bit;
- }
- Log3 $name, 5, "S7_DWrite_Define called2";
- $hash->{ADDRESS} = "$byte.$bit";
- $hash->{AREA} = $area;
- $hash->{DB} = $DB;
- $hash->{LENGTH} = 1;
- $hash->{POSITION} = $position;
- my $ID = "$area $DB";
- if ( !defined( $modules{S7_DWrite}{defptr}{$ID} ) ) {
- my @b = ();
- push( @b, $hash );
- $modules{S7_DWrite}{defptr}{$ID} = \@b;
- }
- else {
- push( @{ $modules{S7_DWrite}{defptr}{$ID} }, $hash );
- }
- $hash->{IODev}{dirty} = 1;
- return undef;
- }
- #####################################
- sub S7_DWrite_setABit($$) {
- my ( $hash, $newValue ) = @_;
- my $name = $hash->{NAME};
- $newValue = lc $newValue;
- Log3 $name, 4, "S7_DWrite_setABit $newValue";
- if ( $newValue ne "on" && $newValue ne "off" && $newValue ne "trigger" ) {
- return "Unknown argument $newValue, choose one of ON OFF TRIGGER";
- }
- my $sname = $hash->{IODev}{NAME};
- my $position = $hash->{POSITION};
- my $area = $hash->{AREA};
- my $dbNR = $hash->{DB};
- my $shash = $defs{$sname};
- my $writeAreaIndex = S7_getAreaIndex4AreaName($area);
- return $writeAreaIndex if ( $writeAreaIndex ne int($writeAreaIndex) );
- my $b = 0;
- my $res;
- if ( $newValue eq "on" || $newValue eq "trigger" ) {
- $b = 1;
- }
-
- my $byte;
- my $bit;
- my $readbuffer;
- my @cbuffer;
- my $tbuffer;
-
- if ( $shash->{S7TYPE} eq "S5" ) {
- #S5
- #lesen wir das aktuelle byte
- $byte = int( $position / 8 );
- $bit = int( $position % 8 );
- ( $res, $readbuffer ) =
- S7_ReadBlockFromPLC( $shash, $writeAreaIndex, $dbNR, $byte, 1 );
- if ( $res == 0 && length($readbuffer) == 1 ) { #reading was OK
- #setzen/löschen wir das gewünsche bit
-
- $tbuffer = join( ", ", unpack( "H2 " x length($readbuffer), $readbuffer ) );
- Log3( undef, 5, "S5 Read old Value <-- " . $tbuffer ." now changing bitNr: ".$bit );
-
-
- @cbuffer = unpack( "C" x length($readbuffer), $readbuffer);
- if ($b == 1) {
- $cbuffer[0] |= (1 << $bit);
- } else {
- $cbuffer[0] &= (~(1 << $bit)) & 0xFF;
- }
-
- $readbuffer = pack( "C" x 1, @cbuffer);
-
- #schreiben wir das byte
- $tbuffer = join( ", ", unpack( "H2 " x length($readbuffer), $readbuffer ) );
- Log3( undef, 5, "S5 Write new Value <-- " . $tbuffer );
- $res = S7_WriteToPLC( $shash, $writeAreaIndex, $dbNR, $byte, &S7Client::S7WLByte , $readbuffer );
-
-
- if ( $res != 0 ) {
- my $error = $shash->{S7PLCClient}->getErrorStr($res);
- my $msg =
- "$name S7_DWrite_setABit -S5- S7_WriteToPLC error: $res=$error";
- Log3( $name, 3, $msg );
- }
-
-
- } else {
- my $error = $shash->{S7PLCClient}->getErrorStr($res);
- my $msg =
- "$name S7_DWrite_setABit -S5- ReadArea error: $res=$error";
- Log3( $name, 3, $msg );
- S7_reconnect($shash); #lets try a reconnect
- return ( -2, $msg );
- }
- } else {
- #S7
- $res = S7_WriteBitToPLC( $shash, $writeAreaIndex, $dbNR, $position, $b );
- }
- if ( $newValue eq "trigger" ) {
- my $triggerLength = 1;#1 second
- if ( defined( $main::attr{$name}{trigger_length} ) ) {
- $triggerLength = $main::attr{$name}{trigger_length};
- }
- if ($triggerLength >=1 ) {
- InternalTimer( gettimeofday() + $triggerLength, "S7_DWrite_SwitchOff", $hash, 1 );
- } else {
- #we use usleep
- $triggerLength = $triggerLength*1000*1000;
- $triggerLength = int($triggerLength);
-
- usleep ($triggerLength);
-
- if ( $shash->{S7TYPE} eq "S5" ) {
- $cbuffer[0] &= (~(1 << $bit)) & 0xFF;
-
-
- $readbuffer = pack( "C" x 1, @cbuffer);
-
- #schreiben wir das byte
- $tbuffer = join( ", ", unpack( "H2 " x length($readbuffer), $readbuffer ) );
- Log3( undef, 5, "S5 Write new Value 2 <-- " . $tbuffer );
- $res = S7_WriteToPLC( $shash, $writeAreaIndex, $dbNR, $byte, &S7Client::S7WLByte , $readbuffer );
-
- if ( $res != 0 ) {
- my $error = $shash->{S7PLCClient}->getErrorStr($res);
- my $msg =
- "$name S7_DWrite_setABit -S5- S7_WriteToPLC2 error: $res=$error";
- Log3( $name, 3, $msg );
- }
-
- } else {
- #S7
- $res =
- S7_WriteBitToPLC( $shash, $writeAreaIndex, $dbNR, $position, 0 );
-
- }
- $newValue = "off";
- }
- }
-
- if ( $res == 0 ) {
- main::readingsSingleUpdate( $hash, "state", $newValue, 1 );
- } else {
- main::readingsSingleUpdate( $hash, "state", "", 1 );
- }
- return undef;
- }
- #####################################
- sub S7_DWrite_Set(@) {
- my ( $hash, @a ) = @_;
- return "Need at least one parameter" if ( int(@a) < 2 );
- return S7_DWrite_setABit( $hash, $a[1] );
- }
- #####################################
- sub S7_DWrite_SwitchOff($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- Log3 $name, 4, "S7_DWrite: GetUpdate called ...";
- return S7_DWrite_setABit( $hash, "off" );
- }
- #####################################
- sub S7_DWrite_Parse($$) {
- my ( $hash, $rmsg ) = @_;
- my $name;
- if ( defined( $hash->{NAME} ) ) {
- $name = $hash->{NAME};
- }
- else {
- $name = "dummy";
- Log3 undef, 2, "S7_DWrite_Parse: Error ...";
- return undef;
- }
- my @a = split( "[ \t][ \t]*", $rmsg );
- my @list;
- my ( $area, $DB, $start, $length, $datatype, $s7name, $hexbuffer,
- $clientNames );
- $area = lc $a[1];
- $DB = $a[2];
- $start = $a[3];
- $length = $a[4];
- $s7name = $a[5];
- $hexbuffer = $a[6];
- $clientNames = $a[7];
- my $ID = "$area $DB";
- Log3 $name, 6, "$name S7_DWrite_Parse $rmsg";
- my @clientList = split( ",", $clientNames );
- if ( int(@clientList) > 0 ) {
- my @Writebuffer = unpack( "C" x $length,
- pack( "H2" x $length, split( ",", $hexbuffer ) ) );
-
- my $now = gettimeofday();
- foreach my $clientName (@clientList) {
-
- my $h = $defs{$clientName};
- if ( $h->{TYPE} eq "S7_DWrite"
- && $start <= int( $h->{POSITION} / 8 )
- && $start + $length >= int( $h->{POSITION} / 8 ) )
- {
- push( @list, $clientName )
- ; #damit die werte im client gesetzt werden!
- #aktualisierung des wertes
- my $s = int( $h->{POSITION} / 8 ) - $start;
- my $myI = $hash->{S7PLCClient}->ByteAt( \@Writebuffer, $s );
- Log3 $name, 5, "$name S7_DWrite_Parse update $clientName ";
- # if ( ( int($myI) & ( 1 << ( $h->{POSITION} % 8 ) ) ) > 0 ) {
- # main::readingsSingleUpdate( $h, "state", "on", 1 );
- # }
- # else {
- # main::readingsSingleUpdate( $h, "state", "off", 1 );
- # }
- my $valueText = "";
- my $reading="state";
-
- if ( ( int($myI) & ( 1 << ( $h->{POSITION} % 8 ) ) ) > 0 ) {
- $valueText = "on";
- }
- else {
- $valueText = "off";
- }
-
- #check event-onchange-reading
- #code wurde der datei fhem.pl funktion readingsBulkUpdate entnommen und adaptiert
- my $attreocr= AttrVal($h->{NAME}, "event-on-change-reading", undef);
- my @a;
- if($attreocr) {
- @a = split(/,/,$attreocr);
- $h->{".attreocr"} = \@a;
- }
- # determine whether the reading is listed in any of the attributes
- my @eocrv;
- my $eocr = $attreocr &&
- ( @eocrv = grep { my $l = $_; $l =~ s/:.*//;
- ($reading=~ m/^$l$/) ? $_ : undef} @a);
-
- # check if threshold is given
- my $eocrExists = $eocr;
- if( $eocr
- && $eocrv[0] =~ m/.*:(.*)/ ) {
- my $threshold = $1;
- if($valueText =~ m/([\d\.\-eE]+)/ && looks_like_number($1)) { #41083, #62190
- my $mv = $1;
- my $last_value = $h->{".attreocr-threshold$reading"};
- if( !defined($last_value) ) {
- # $h->{".attreocr-threshold$reading"} = $mv;
- } elsif( abs($mv - $last_value) < $threshold ) {
- $eocr = 0;
- } else {
- # $h->{".attreocr-threshold$reading"} = $mv;
- }
- }
- }
-
- my $changed = !($attreocr)
- || ($eocr && ($valueText ne ReadingsVal($h->{NAME},$reading,"")));
-
- my $attrminint = AttrVal($h->{NAME}, "event-min-interval", undef);
- my @aa;
- if($attrminint) {
- @aa = split(/,/,$attrminint);
- }
-
- my @v = grep { my $l = $_;
- $l =~ s/:.*//;
- ($reading=~ m/^$l$/) ? $_ : undef
- } @aa;
- if(@v) {
- my (undef, $minInt) = split(":", $v[0]);
- my $le = $h->{".lastTime$reading"};
- if($le && $now-$le < $minInt) {
- if(!$eocr || ($eocr && $valueText eq ReadingsVal($h->{NAME},$reading,""))){
- $changed = 0;
- #} else {
- # $hash->{".lastTime$reading"} = $now;
- }
- } else {
- #$hash->{".lastTime$reading"} = $now;
- $changed = 1 if($eocrExists);
- }
- }
- if ($changed == 1) {
- main::readingsSingleUpdate( $h, $reading, $valueText, 1 );
- }
-
- }
- }
- }
- else {
- Log3 $name, 3, "$name S7_DWrite_Parse going the save way ";
- if ( defined( $modules{S7_DWrite}{defptr}{$ID} ) ) {
- foreach my $h ( @{ $modules{S7_DWrite}{defptr}{$ID} } ) {
- if ( defined( $main::attr{ $h->{NAME} }{IODev} )
- && $main::attr{ $h->{NAME} }{IODev} eq $name )
- {
- if ( $start <= int( $h->{POSITION} / 8 )
- && $start + $length >= int( $h->{POSITION} / 8 ) )
- {
- my $n = $h
- ->{NAME}; #damit die werte im client gesetzt werden!
- push( @list, $n );
- #aktualisierung des wertes
- my @Writebuffer = unpack(
- "C" x $length,
- pack(
- "H2" x $length, split( ",", $hexbuffer )
- )
- );
- my $s = int( $h->{POSITION} / 8 ) - $start;
- # my $b = pack( "C" x $length, @Writebuffer );
- my $myI =
- $hash->{S7PLCClient}->ByteAt( \@Writebuffer, $s );
- Log3 $name, 6, "$name S7_DWrite_Parse update $n ";
- if ( ( int($myI) & ( 1 << ( $h->{POSITION} % 8 ) ) )
- > 0 )
- {
- main::readingsSingleUpdate( $h, "state", "on",
- 1 );
- }
- else {
- main::readingsSingleUpdate( $h, "state", "off",
- 1 );
- }
- }
- }
- }
- }
- }
- if ( int(@list) == 0 ) {
- Log3 $name, 6, "S7_DWrite: Parse no client found ($name) ...";
- push( @list, "" );
- }
- return @list;
- }
- #####################################
- sub S7_DWrite_Attr(@) {
- my ( $cmd, $name, $aName, $aVal ) = @_;
- # $cmd can be "del" or "set"
- # $name is device name
- # aName and aVal are Attribute name and value
- my $hash = $defs{$name};
- if ( $cmd eq "set" ) {
- if ( $aName eq "trigger_length" ) {
- if ( !__isfloat ($aVal) ) {
- Log3 $name, 3,
- "S7_DWrite: Invalid $aName in attr $name $aName ($aVal is not a number): $@";
- return "Invalid $aName : $aVal is not a number";
- }
- }
- elsif ( $aName eq "IODev" ) {
- Log3 $name, 4, "S7_DWrite: IODev for $name is $aVal";
- $hash->{IODev}{dirty} = 1;
- }
- }
- return undef;
- }
- 1;
- =pod
- =item summary logical device for a digital writing to a S7/S5
- =item summary_DE logisches Device für einen binären Lese/Schreib Datenpunkt zu einer S5 / S7
- =begin html
- <p><a name="S7_DWrite"></a></p>
- <h3>S7_DWrite</h3>
- <ul>
- <ul>This module is a logical module of the physical module S7.</ul>
- </ul>
- <ul>
- <ul>This module is used to set/unset a Bit in ad DB of the PLC.</ul>
- </ul>
- <ul>
- <ul>Note: you have to configure a PLC writing at the physical modul (S7) first.</ul>
- </ul>
- <p><br /><br /><br /><strong>Define</strong><code>define <name> S7_DWrite {db} <DB> <address></code></p>
- <ul>
- <ul>
- <ul>
- <ul>
- <li>db … defines where to read. Note currently only writing in to DB are supported.</li>
- <li>DB … Number of the DB</li>
- <li>address … address you want to write. bit number to read. Example: 10.6</li>
- </ul>
- Note: the required memory area need to be with in the configured PLC reading of the physical module.</ul>
- </ul>
- </ul>
- <p><strong>Set</strong><code>set <name> S7_AWrite {ON|OFF|TRIGGER};</code></p>
- <ul>
- <ul>Note: TRIGGER sets the bit for 1s to ON than it will set to OFF.</ul>
- </ul>
- <p><strong>Attr</strong><br /> The following parameters are used to scale every reading</p>
- <ul>
- <li>trigger_length ... sets the on-time of a trigger in Seconds. Note out can also use trigger_length less than 1</li>
- </ul>
- =end html
- =begin html_DE
- <p><a name="S7_DWrite"></a></p>
- <h3>S7_DWrite</h3>
- <ul>
- <ul>This module is a logical module of the physical module S7.</ul>
- </ul>
- <ul>
- <ul>This module is used to set/unset a Bit in ad DB of the PLC.</ul>
- </ul>
- <ul>
- <ul>Note: you have to configure a PLC writing at the physical modul (S7) first.</ul>
- </ul>
- <p><br /><br /><br /><strong>Define</strong><code>define <name> S7_DWrite {db} <DB> <position></code></p>
- <ul>
- <ul>
- <ul>
- <ul>
- <li>db … defines where to read. Note currently only writing in to DB are supported.</li>
- <li>DB … Number of the DB</li>
- <li>address … address you want to write. bit number to read. Example: 10.6</li>
- </ul>
- Note: the required memory area need to be with in the configured PLC reading of the physical module.</ul>
- </ul>
- </ul>
- <p><br /><strong>Set</strong><code>set <name> S7_AWrite {ON|OFF|TRIGGER};</code></p>
- <ul>
- <ul>Note: TRIGGER sets the bit for 1s to ON than it will set to OFF.</ul>
- </ul>
- <p><strong>Attr</strong><br /> The following parameters are used to scale every reading</p>
- <ul>
- <li>trigger_length ... sets the on-time of a trigger in Seconds. Note out can also use trigger_length less than 1</li>
- </ul>
- =end html_DE
- =cut
|