44_S7_DRead.pm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. # $Id: 44_S7_DRead.pm 15539 2017-12-01 21:52:13Z charlie71 $
  2. ##############################################
  3. package main;
  4. use strict;
  5. use warnings;
  6. use Time::HiRes qw(gettimeofday);
  7. my %gets = (
  8. # "libnodaveversion" => ""
  9. );
  10. #####################################
  11. sub S7_DRead_Initialize($) {
  12. my $hash = shift @_;
  13. # Provider
  14. # Consumer
  15. $hash->{Match} = "^DR";
  16. $hash->{DefFn} = "S7_DRead_Define";
  17. $hash->{UndefFn} = "S7_DRead_Undef";
  18. $hash->{ParseFn} = "S7_DRead_Parse";
  19. $hash->{AttrFn} = "S7_DRead_Attr";
  20. $hash->{AttrList} = "IODev " . $readingFnAttributes;
  21. main::LoadModule("S7");
  22. }
  23. #####################################
  24. sub S7_DRead_Define($$) {
  25. my ( $hash, $def ) = @_;
  26. my @a = split( "[ \t][ \t]*", $def );
  27. my ( $name, $area, $DB, $start, $position );
  28. $name = $a[0];
  29. AssignIoPort($hash); # logisches modul an physikalisches binden !!!
  30. my $sname = $hash->{IODev}{NAME};
  31. my $byte;
  32. my $bit;
  33. if ( uc $a[2] =~ m/^[QIMN](\d*)/ ) {
  34. my $Offset;
  35. $area = "db";
  36. $DB = 0;
  37. my $startposition;
  38. if ( uc $a[2] =~ m/^Q(\d*)/ ) {
  39. $startposition = 1;
  40. if ( defined($hash->{IODev}{S7TYPE}) && $hash->{IODev}{S7TYPE} eq "LOGO7" ) {
  41. $Offset = 942;
  42. }
  43. elsif ( defined($hash->{IODev}{S7TYPE}) && $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
  44. $Offset = 1064;
  45. }
  46. else {
  47. my $msg =
  48. "wrong syntax : define <name> S7_DRead {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DRead {I|Q|M|NI|NQ}1..24";
  49. Log3 undef, 2, $msg;
  50. return $msg;
  51. }
  52. }
  53. elsif ( uc $a[2] =~ m/^I(\d*)/ ) {
  54. $startposition = 1;
  55. if ( $hash->{IODev}{S7TYPE} eq "LOGO7" ) {
  56. $Offset = 923;
  57. }
  58. elsif ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
  59. $Offset = 1024;
  60. }
  61. else {
  62. my $msg =
  63. "wrong syntax : define <name> S7_DRead {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DRead {I|Q|M|NI|NQ}1..24";
  64. Log3 undef, 2, $msg;
  65. return $msg;
  66. }
  67. }
  68. elsif ( uc $a[2] =~ m/^NI(\d*)/ ) {
  69. $startposition = 2;
  70. if ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
  71. $Offset = 1246;
  72. }
  73. else {
  74. my $msg =
  75. "wrong syntax : define <name> S7_DRead {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DRead {I|Q|M|NI|NQ}1..24";
  76. Log3 undef, 2, $msg;
  77. return $msg;
  78. }
  79. }
  80. elsif ( uc $a[2] =~ m/^NQ(\d*)/ ) {
  81. $startposition = 2;
  82. if ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
  83. $Offset = 1390;
  84. }
  85. else {
  86. my $msg =
  87. "wrong syntax : define <name> S7_DRead {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DRead {I|Q|M|NI|NQ}1..24";
  88. Log3 undef, 2, $msg;
  89. return $msg;
  90. }
  91. }
  92. elsif ( uc $a[2] =~ m/^M(\d*)/ ) {
  93. $startposition = 1;
  94. if ( $hash->{IODev}{S7TYPE} eq "LOGO7" ) {
  95. $Offset = 948;
  96. }
  97. elsif ( $hash->{IODev}{S7TYPE} eq "LOGO8" ) {
  98. $Offset = 1104;
  99. }
  100. else {
  101. my $msg =
  102. "wrong syntax : define <name> S7_DRead {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DRead {I|Q|M|NI|NQ}1..24";
  103. Log3 undef, 2, $msg;
  104. return $msg;
  105. }
  106. }
  107. else {
  108. my $msg =
  109. "wrong syntax : define <name> S7_DRead {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DRead {I|Q|M|NI|NQ}1..24";
  110. Log3 undef, 2, $msg;
  111. return $msg;
  112. }
  113. $position =
  114. ( $Offset * 8 ) + int( substr( $a[2], $startposition ) ) - 1;
  115. $byte = int( $position / 8 );
  116. $bit = ( $position % 8 );
  117. }
  118. else {
  119. $area = lc $a[2];
  120. $DB = $a[3];
  121. $position = $a[4];
  122. if ( $area ne "inputs"
  123. && $area ne "outputs"
  124. && $area ne "flags"
  125. && $area ne "db" )
  126. {
  127. my $msg =
  128. "wrong syntax : define <name> S7_DRead {inputs|outputs|flags|db} <DB> <address> \n Only for Logo7 or Logo8:\n define <name> S7_DRead {I|Q|M|NI|NQ}1..24";
  129. Log3 undef, 2, $msg;
  130. return $msg;
  131. }
  132. my @address = split( /\./, $position );
  133. if ( int(@address) == 2 ) {
  134. $byte = $address[0];
  135. $bit = $address[1];
  136. }
  137. else {
  138. $byte = int( $address[0] / 8 );
  139. $bit = ( $address[0] % 8 );
  140. }
  141. }
  142. $hash->{AREA} = $area;
  143. $hash->{DB} = $DB;
  144. $hash->{POSITION} = ( $byte * 8 ) + $bit;
  145. $hash->{ADDRESS} = "$byte.$bit";
  146. $hash->{LENGTH} = 1;
  147. my $ID = "$area $DB";
  148. if ( !defined( $modules{S7_DRead}{defptr}{$ID} ) ) {
  149. my @b = ();
  150. push( @b, $hash );
  151. $modules{S7_DRead}{defptr}{$ID} = \@b;
  152. }
  153. else {
  154. push( @{ $modules{S7_DRead}{defptr}{$ID} }, $hash );
  155. }
  156. $hash->{IODev}{dirty} = 1;
  157. Log3 $name, 4, "S7_DRead ($sname): define $name Adress:$byte.$bit";
  158. return undef;
  159. }
  160. #####################################
  161. sub S7_DRead_Undef($$) {
  162. my ( $hash, $name ) = @_;
  163. Log3 $name, 4,
  164. "S7_DRead ("
  165. . $hash->{IODev}{NAME}
  166. . "): undef "
  167. . $hash->{NAME}
  168. . " Adress:"
  169. . $hash->{ADDRESS};
  170. delete( $modules{S7_DRead}{defptr} );
  171. return undef;
  172. }
  173. #####################################
  174. sub S7_DRead_Parse_new($$) {
  175. my ( $hash, $rmsg ) = @_;
  176. my $name;
  177. if ( defined( $hash->{NAME} ) ) {
  178. $name = $hash->{NAME};
  179. }
  180. else {
  181. Log3 undef, 2, "S7_DRead: Error ...";
  182. return undef;
  183. }
  184. my @a = split( "[ \t][ \t]*", $rmsg );
  185. my @list;
  186. my ( $area, $DB, $start, $length, $datatype, $s7name, $hexbuffer );
  187. $area = lc $a[1];
  188. $DB = $a[2];
  189. $start = $a[3];
  190. $length = $a[4];
  191. $s7name = $a[5];
  192. $hexbuffer = $a[6];
  193. my $ID = "$area $DB";
  194. Log3 $name, 6, "$name S7_DRead_Parse $rmsg";
  195. my @Writebuffer =
  196. unpack( "C" x $length, pack( "H2" x $length, split( ",", $hexbuffer ) ) );
  197. # my $b = pack( "C" x $length, @Writebuffer );
  198. my $clientArray = $hash->{"Clients"};
  199. foreach my $h ( @{$clientArray} ) {
  200. if ( $start <= int( $h->{POSITION} / 8 )
  201. && $start + $length >= int( $h->{POSITION} / 8 ) )
  202. {
  203. #die Nachricht ist für den client
  204. my $n = $h->{NAME}; #damit die werte im client gesetzt werden!
  205. push( @list, $n );
  206. #aktualisierung des wertes
  207. my $s = int( $h->{POSITION} / 8 ) - $start;
  208. my $myI = $hash->{S7PLCClient}->ByteAt( \@Writebuffer, $s );
  209. Log3 $name, 6, "$name S7_DRead_Parse update $n ";
  210. my $valueText = "";
  211. if ( ( int($myI) & ( 1 << ( $h->{POSITION} % 8 ) ) ) > 0 ) {
  212. $valueText = "on";
  213. }
  214. else {
  215. $valueText = "off";
  216. }
  217. if (ReadingsVal($h->{NAME},"state","") ne $valueText) {
  218. main::readingsSingleUpdate( $h, "state", $valueText, 1 );
  219. } else {
  220. my $reading="state";
  221. #value not changed check event-min-interval attribute
  222. my $attrminint = AttrVal($name, "event-min-interval", undef);
  223. if($attrminint) {
  224. my @a = split(/,/,$attrminint);
  225. }
  226. my @v = grep { my $l = $_;
  227. $l =~ s/:.*//;
  228. ($reading=~ m/^$l$/) ? $_ : undef} @a;
  229. if(@v) {
  230. my (undef, $minInt) = split(":", $v[0]);
  231. my $now = gettimeofday();
  232. my $le = $hash->{".lastTime$reading"};
  233. if($le && $now-$le >= $minInt) {
  234. main::readingsSingleUpdate( $h, $reading, $valueText, 1 );
  235. }
  236. }
  237. }
  238. }
  239. }
  240. if ( int(@list) == 0 ) {
  241. Log3 $name, 6, "S7_DRead: Parse no client found ($name) ...";
  242. push( @list, "" );
  243. }
  244. return @list;
  245. }
  246. #####################################
  247. sub S7_DRead_Parse($$) {
  248. my ( $hash, $rmsg ) = @_;
  249. my $name;
  250. if ( defined( $hash->{NAME} ) ) {
  251. $name = $hash->{NAME};
  252. }
  253. else {
  254. Log3 undef, 2, "S7_DRead: Error ...";
  255. return undef;
  256. }
  257. my @a = split( "[ \t][ \t]*", $rmsg );
  258. my @list;
  259. my ( $area, $DB, $start, $length, $datatype, $s7name, $hexbuffer,
  260. $clientNames );
  261. $area = lc $a[1];
  262. $DB = $a[2];
  263. $start = $a[3];
  264. $length = $a[4];
  265. $s7name = $a[5];
  266. $hexbuffer = $a[6];
  267. $clientNames = $a[7];
  268. my $ID = "$area $DB";
  269. Log3 $name, 5, "$name S7_DRead_Parse $rmsg";
  270. # main::readingsBeginUpdate($h);
  271. # main::readingsBulkUpdate($h,"reading",$res,1);
  272. # main::readingsEndUpdate($h, 1);
  273. my @clientList = split( ",", $clientNames );
  274. if ( int(@clientList) > 0 ) {
  275. my @Writebuffer = unpack( "C" x $length,
  276. pack( "H2" x $length, split( ",", $hexbuffer ) ) );
  277. my $now = gettimeofday();
  278. foreach my $clientName (@clientList) {
  279. my $h = $defs{$clientName};
  280. # if ( defined( $main::attr{ $h->{NAME} }{IODev} )
  281. # && $main::attr{ $h->{NAME} }{IODev} eq $name )
  282. # {
  283. if ( $h->{TYPE} eq "S7_DRead"
  284. && $start <= int( $h->{POSITION} / 8 )
  285. && $start + $length >= int( $h->{POSITION} / 8 ) )
  286. {
  287. push( @list, $clientName )
  288. ; #damit die werte im client gesetzt werden!
  289. #aktualisierung des wertes
  290. my $s = int( $h->{POSITION} / 8 ) - $start;
  291. my $myI = $hash->{S7PLCClient}->ByteAt( \@Writebuffer, $s );
  292. Log3 $name, 6, "$name S7_DRead_Parse update $clientName ";
  293. # if ( ( int($myI) & ( 1 << ( $h->{POSITION} % 8 ) ) ) > 0 ) {
  294. # main::readingsSingleUpdate( $h, "state", "on", 1 );
  295. # }
  296. # else {
  297. # main::readingsSingleUpdate( $h, "state", "off", 1 );
  298. # }
  299. my $valueText = "";
  300. my $reading="state";
  301. if ( ( int($myI) & ( 1 << ( $h->{POSITION} % 8 ) ) ) > 0 ) {
  302. $valueText = "on";
  303. }
  304. else {
  305. $valueText = "off";
  306. }
  307. #check event-onchange-reading
  308. #code wurde der datei fhem.pl funktion readingsBulkUpdate entnommen und adaptiert
  309. my $attreocr= AttrVal($h->{NAME}, "event-on-change-reading", undef);
  310. my @a;
  311. if($attreocr) {
  312. @a = split(/,/,$attreocr);
  313. $h->{".attreocr"} = \@a;
  314. }
  315. # determine whether the reading is listed in any of the attributes
  316. my @eocrv;
  317. my $eocr = $attreocr &&
  318. ( @eocrv = grep { my $l = $_; $l =~ s/:.*//;
  319. ($reading=~ m/^$l$/) ? $_ : undef} @a);
  320. # check if threshold is given
  321. my $eocrExists = $eocr;
  322. if( $eocr
  323. && $eocrv[0] =~ m/.*:(.*)/ ) {
  324. my $threshold = $1;
  325. if($valueText =~ m/([\d\.\-eE]+)/ && looks_like_number($1)) { #41083, #62190
  326. my $mv = $1;
  327. my $last_value = $h->{".attreocr-threshold$reading"};
  328. if( !defined($last_value) ) {
  329. # $h->{".attreocr-threshold$reading"} = $mv;
  330. } elsif( abs($mv - $last_value) < $threshold ) {
  331. $eocr = 0;
  332. } else {
  333. # $h->{".attreocr-threshold$reading"} = $mv;
  334. }
  335. }
  336. }
  337. my $changed = !($attreocr)
  338. || ($eocr && ($valueText ne ReadingsVal($h->{NAME},$reading,"")));
  339. my $attrminint = AttrVal($h->{NAME}, "event-min-interval", undef);
  340. my @aa;
  341. if($attrminint) {
  342. @aa = split(/,/,$attrminint);
  343. }
  344. my @v = grep { my $l = $_;
  345. $l =~ s/:.*//;
  346. ($reading=~ m/^$l$/) ? $_ : undef
  347. } @aa;
  348. if(@v) {
  349. my (undef, $minInt) = split(":", $v[0]);
  350. my $le = $h->{".lastTime$reading"};
  351. if($le && $now-$le < $minInt) {
  352. if(!$eocr || ($eocr && $valueText eq ReadingsVal($h->{NAME},$reading,""))){
  353. $changed = 0;
  354. #} else {
  355. # $hash->{".lastTime$reading"} = $now;
  356. }
  357. } else {
  358. #$hash->{".lastTime$reading"} = $now;
  359. $changed = 1 if($eocrExists);
  360. }
  361. }
  362. if ($changed == 1) {
  363. main::readingsSingleUpdate( $h, $reading, $valueText, 1 );
  364. }
  365. }
  366. # }
  367. }
  368. }
  369. else {
  370. Log3 $name, 3, "$name S7_DRead_Parse going the save way ";
  371. if ( defined( $modules{S7_DRead}{defptr}{$ID} ) ) {
  372. foreach my $h ( @{ $modules{S7_DRead}{defptr}{$ID} } ) {
  373. if ( defined( $main::attr{ $h->{NAME} }{IODev} )
  374. && $main::attr{ $h->{NAME} }{IODev} eq $name )
  375. {
  376. if ( $start <= int( $h->{POSITION} / 8 )
  377. && $start + $length >= int( $h->{POSITION} / 8 ) )
  378. {
  379. my $n =
  380. $h->{NAME}; #damit die werte im client gesetzt werden!
  381. push( @list, $n );
  382. #aktualisierung des wertes
  383. my @Writebuffer = unpack( "C" x $length,
  384. pack( "H2" x $length, split( ",", $hexbuffer ) ) );
  385. my $s = int( $h->{POSITION} / 8 ) - $start;
  386. #my $b = pack( "C" x $length, @Writebuffer );
  387. my $myI =
  388. $hash->{S7PLCClient}->ByteAt( \@Writebuffer, $s );
  389. Log3 $name, 6, "$name S7_DRead_Parse update $n ";
  390. if ( ( int($myI) & ( 1 << ( $h->{POSITION} % 8 ) ) ) >
  391. 0 )
  392. {
  393. main::readingsSingleUpdate( $h, "state", "on", 1 );
  394. }
  395. else {
  396. main::readingsSingleUpdate( $h, "state", "off", 1 );
  397. }
  398. }
  399. }
  400. }
  401. }
  402. }
  403. if ( int(@list) == 0 ) {
  404. Log3 $name, 6, "S7_DRead: Parse no client found ($name) ...";
  405. push( @list, "" );
  406. }
  407. return @list;
  408. }
  409. #####################################
  410. sub S7_DRead_Attr(@) {
  411. my ( $cmd, $name, $aName, $aVal ) = @_;
  412. # $cmd can be "del" or "set"
  413. # $name is device name
  414. # aName and aVal are Attribute name and value
  415. my $hash = $defs{$name};
  416. if ( $cmd eq "set" ) {
  417. if ( $aName eq "IODev" ) {
  418. if ( defined( $hash->{IODev} ) ) { #set old master device dirty
  419. $hash->{IODev}{dirty} = 1;
  420. }
  421. if ( defined( $defs{$aVal} ) ) { #set new master device dirty
  422. $defs{$aVal}{dirty} = 1;
  423. }
  424. Log3 $name, 4, "S7_DRead: IODev for $name is $aVal";
  425. }
  426. }
  427. return undef;
  428. }
  429. #####################################
  430. 1;
  431. =pod
  432. =item summary logical device for a digital reading from a S7/S5
  433. =item summary_DE logisches Device für einen binären Nur Lese Datenpunkt von einer S5 / S7
  434. =begin html
  435. <a name="S7_DRead"></a>
  436. <h3>S7_DRead</h3>
  437. <ul>
  438. This module is a logical module of the physical module S7. <br>
  439. This module provides digital data (ON/OFF).<br>
  440. Note: you have to configure a PLC reading at the physical modul (S7) first.<br>
  441. <br><br>
  442. <b>Define</b>
  443. <ul>
  444. <code>define &lt;name&gt; S7_DRead {inputs|outputs|flags|db} &lt;DB&gt; &lt;address&gt;</code>
  445. <ul>
  446. <li>inputs|outputs|flags|db … defines where to read.</li>
  447. <li>DB … Number of the DB</li>
  448. <li>address … address you want to read. bit number to read. Example: 10.3</li>
  449. </ul>
  450. Note: the required memory area need to be with in the configured PLC reading of the physical module.
  451. </ul>
  452. </ul>
  453. =end html
  454. =begin html_DE
  455. <a name="S7_DRead"></a>
  456. <h3>S7_DRead</h3>
  457. <ul>
  458. This module is a logical module of the physical module S7. <br>
  459. This module provides digital data (ON/OFF).<br>
  460. Note: you have to configure a PLC reading at the physical modul (S7) first.<br>
  461. <br><br>
  462. <b>Define</b>
  463. <ul>
  464. <code>define &lt;name&gt; S7_DRead {inputs|outputs|flags|db} &lt;DB&gt; &lt;address&gt;</code>
  465. <ul>
  466. <li>inputs|outputs|flags|db … defines where to read.</li>
  467. <li>DB … Number of the DB</li>
  468. <li>address … address you want to read. bit number to read. Example: 10.3</li>
  469. </ul>
  470. Note: the required memory area need to be with in the configured PLC reading of the physical module.
  471. </ul>
  472. </ul>
  473. =end html_DE
  474. =cut