44_S7_ARead.pm 16 KB

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