44_S7.pm 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257
  1. # $Id: 44_S7.pm 12784 2016-12-15 17:57:02Z charlie71born $
  2. ####################################################
  3. package main;
  4. use strict;
  5. use warnings;
  6. #use Devel::NYTProf; #profiler
  7. require "44_S7_S7Client.pm";
  8. require "44_S7_S5Client.pm";
  9. my %gets = (
  10. "S7TCPClientVersion" => "",
  11. "PLCTime" => ""
  12. );
  13. my %sets = (
  14. "intervall" => ""
  15. );
  16. my @areasconfig = (
  17. "ReadInputs-Config", "ReadOutputs-Config",
  18. "ReadFlags-Config", "ReadDB-Config",
  19. "WriteInputs-Config", "WriteOutputs-Config",
  20. "WriteFlags-Config", "WriteDB-Config"
  21. );
  22. my @s7areas = (
  23. &S7ClientBase::S7AreaPE, &S7ClientBase::S7AreaPA, &S7ClientBase::S7AreaMK,
  24. &S7ClientBase::S7AreaDB, &S7ClientBase::S7AreaPE, &S7ClientBase::S7AreaPA,
  25. &S7ClientBase::S7AreaMK, &S7ClientBase::S7AreaDB
  26. );
  27. my @areaname =
  28. ( "inputs", "outputs", "flags", "db", "inputs", "outputs", "flags", "db" );
  29. #####################################
  30. sub S7_Initialize($) { #S5_OK
  31. my $hash = shift @_;
  32. # Provider
  33. $hash->{Clients} = ":S7_DRead:S7_ARead:S7_AWrite:S7_DWrite:";
  34. my %matchList = (
  35. "1:S7_DRead" => "^DR",
  36. "2:S7_DWrite" => "^DW",
  37. "3:S7_ARead" => "^AR",
  38. "4:S7_AWrite" => "^AW"
  39. );
  40. $hash->{MatchList} = \%matchList;
  41. # Consumer
  42. $hash->{DefFn} = "S7_Define";
  43. $hash->{UndefFn} = "S7_Undef";
  44. $hash->{GetFn} = "S7_Get";
  45. $hash->{SetFn} = "S7_Set";
  46. $hash->{AttrFn} = "S7_Attr";
  47. $hash->{AttrList} = "MaxMessageLength Intervall " . $readingFnAttributes;
  48. # $hash->{AttrList} = join( " ", @areasconfig )." PLCTime";
  49. }
  50. #####################################
  51. sub S7_connect($) {
  52. my $hash = shift @_;
  53. my $name = $hash->{NAME};
  54. if ( $hash->{STATE} eq "connected to PLC" ) {
  55. Log3( $name, 2, "$name S7_connect: allready connected!" );
  56. return;
  57. }
  58. Log3( $name, 4,
  59. "S7: $name connect PLC_address="
  60. . $hash->{plcAddress}
  61. . ", LocalTSAP="
  62. . $hash->{LocalTSAP}
  63. . ", RemoteTSAP="
  64. . $hash->{RemoteTSAP}
  65. . " " );
  66. if ( !defined( $hash->{S7PLCClient} ) ) {
  67. S7_reconnect($hash);
  68. return;
  69. }
  70. $hash->{STATE} = "disconnected";
  71. main::readingsSingleUpdate( $hash, "state", "disconnected", 1 );
  72. my $res;
  73. if ( $hash->{S7TYPE} eq "S5" ) {
  74. eval {
  75. local $SIG{__DIE__} = sub {
  76. my ($s) = @_;
  77. Log3( $hash, 0, "S7_connect: $s" );
  78. $res = -1;
  79. };
  80. $res =
  81. $hash->{S7PLCClient}->S5ConnectPLCAS511( $hash->{plcAddress} );
  82. };
  83. }
  84. else {
  85. $hash->{S7PLCClient}
  86. ->SetConnectionParams( $hash->{plcAddress}, $hash->{LocalTSAP},
  87. $hash->{RemoteTSAP} );
  88. eval {
  89. local $SIG{__DIE__} = sub {
  90. my ($s) = @_;
  91. Log3( $hash, 0, "S7_connect: $s" );
  92. $res = -1;
  93. };
  94. $res = $hash->{S7PLCClient}->Connect();
  95. };
  96. }
  97. if ($res) {
  98. Log3( $name, 2, "S7_connect: $name Could not connect to PLC ($res)" );
  99. return;
  100. }
  101. my $PDUlength = $hash->{S7PLCClient}->{PDULength};
  102. $hash->{maxPDUlength} = $PDUlength;
  103. Log3( $name, 3,
  104. "$name S7_connect: connect to PLC with maxPDUlength=$PDUlength" );
  105. $hash->{STATE} = "connected to PLC";
  106. main::readingsSingleUpdate( $hash, "state", "connected to PLC", 1 );
  107. return undef;
  108. }
  109. #####################################
  110. sub S7_disconnect($) { #S5 OK
  111. my $hash = shift @_;
  112. my ( $ph, $res, $di );
  113. my $name = $hash->{NAME};
  114. my $error = "";
  115. $hash->{S7PLCClient}->Disconnect() if ( defined( $hash->{S7PLCClient} ) );
  116. $hash->{S7PLCClient} = undef; #PLC Client freigeben
  117. $hash->{STATE} = "disconnected";
  118. main::readingsSingleUpdate( $hash, "state", "disconnected", 1 );
  119. Log3( $name, 2, "$name S7 disconnected" );
  120. }
  121. #####################################
  122. sub S7_reconnect($) { #S5 OK
  123. my $hash = shift @_;
  124. S7_disconnect($hash) if ( defined( $hash->{S7PLCClient} ) );
  125. if ( $hash->{S7TYPE} eq "S5" ) {
  126. $hash->{S7PLCClient} = S5Client->new();
  127. }
  128. else {
  129. $hash->{S7PLCClient} = S7Client->new();
  130. }
  131. InternalTimer( gettimeofday() + 3, "S7_connect", $hash, 1 )
  132. ; #wait 3 seconds for reconnect
  133. }
  134. #####################################
  135. sub S7_Define($$) { # S5 OK
  136. my ( $hash, $def ) = @_;
  137. my @a = split( "[ \t][ \t]*", $def );
  138. my ( $name, $PLC_address, $LocalTSAP, $RemoteTSAP, $res, $PDUlength, $rack,
  139. $slot );
  140. $name = $a[0];
  141. if ( uc $a[2] eq "S5" ) {
  142. $hash->{S7TYPE} = "S5";
  143. $PLC_address = $a[3];
  144. if (@a > 4) {
  145. $hash->{Interval} = $a[4];
  146. } else {
  147. $hash->{Interval} = 1;
  148. }
  149. $LocalTSAP = -1;
  150. $RemoteTSAP = -1;
  151. $PDUlength = 240;
  152. }
  153. elsif ( uc $a[2] eq "LOGO7" || uc $a[2] eq "LOGO8" ) {
  154. $PLC_address = $a[3];
  155. $LocalTSAP = 0x0100;
  156. $RemoteTSAP = 0x0200;
  157. if (@a > 4) {
  158. $hash->{Interval} = $a[4];
  159. } else {
  160. $hash->{Interval} = 1;
  161. }
  162. if ( uc $a[2] eq "LOGO7" ) {
  163. $hash->{S7TYPE} = "LOGO7";
  164. }
  165. else {
  166. $hash->{S7TYPE} = "LOGO8";
  167. }
  168. $PDUlength = 240;
  169. }
  170. else {
  171. $PLC_address = $a[2];
  172. $rack = int( $a[3] );
  173. return "invalid rack parameter (0 - 15)"
  174. if ( $rack < 0 || $rack > 15 );
  175. $slot = int( $a[4] );
  176. return "invalid slot parameter (0 - 15)"
  177. if ( $slot < 0 || $slot > 15 );
  178. $hash->{Interval} = 1;
  179. if ( int(@a) == 6 ) {
  180. $hash->{Interval} = int( $a[5] );
  181. return "invalid intervall parameter (1 - 86400)"
  182. if ( $hash->{Interval} < 1 || $hash->{Interval} > 86400 );
  183. }
  184. $LocalTSAP = 0x0100;
  185. $RemoteTSAP = ( &S7Client::S7_PG << 8 ) + ( $rack * 0x20 ) + $slot;
  186. $PDUlength = 0x3c0;
  187. $hash->{S7TYPE} = "NATIVE";
  188. }
  189. $hash->{plcAddress} = $PLC_address;
  190. $hash->{LocalTSAP} = $LocalTSAP;
  191. $hash->{RemoteTSAP} = $RemoteTSAP;
  192. $hash->{maxPDUlength} = $PDUlength; #initial PDU length
  193. Log3 $name, 4,
  194. "S7: define $name PLC_address=$PLC_address,LocalTSAP=$LocalTSAP, RemoteTSAP=$RemoteTSAP ";
  195. $hash->{STATE} = "disconnected";
  196. main::readingsSingleUpdate( $hash, "state", "disconnected", 1 );
  197. S7_connect($hash);
  198. InternalTimer( gettimeofday() + $hash->{Interval},
  199. "S7_GetUpdate", $hash, 0 );
  200. return undef;
  201. }
  202. #####################################
  203. sub S7_Undef($) { #S5 OK
  204. my $hash = shift;
  205. RemoveInternalTimer($hash);
  206. S7_disconnect($hash);
  207. delete( $modules{S7}{defptr} );
  208. return undef;
  209. }
  210. #####################################
  211. sub S7_Set($@) {
  212. }
  213. #####################################
  214. sub S7_Get($@) { #S5 OK
  215. my ( $hash, @a ) = @_;
  216. return "Need at least one parameters" if ( @a < 2 );
  217. return "Unknown argument $a[1], choose one of "
  218. . join( " ", sort keys %gets )
  219. if ( !defined( $gets{ $a[1] } ) );
  220. my $name = shift @a;
  221. my $cmd = shift @a;
  222. ARGUMENT_HANDLER: {
  223. $cmd eq "S7TCPClientVersion" and do {
  224. return $hash->{S7PLCClient}->version();
  225. last;
  226. };
  227. $cmd eq "PLCTime" and do {
  228. return $hash->{S7PLCClient}->getPLCDateTime();
  229. last;
  230. };
  231. }
  232. }
  233. #####################################
  234. sub S7_Attr(@) {
  235. my ( $cmd, $name, $aName, $aVal ) = @_;
  236. my $hash = $defs{$name};
  237. # $cmd can be "del" or "set"
  238. # $name is device name
  239. # aName and aVal are Attribute name and value
  240. if ( $cmd eq "set" ) {
  241. if ( $aName eq "MaxMessageLength" ) {
  242. if ( $aVal < $hash->{S7PLCClient}->{MaxReadLength} ) {
  243. $hash->{S7PLCClient}->{MaxReadLength} = $aVal;
  244. Log3( $name, 3, "$name S7_Attr: setting MaxReadLength= $aVal" );
  245. }
  246. } elsif ($aName eq "MaxMessageLength") {
  247. if ( $aVal >= 1 ) {
  248. $hash->{Interval} = $aVal;
  249. Log3( $name, 3, "$name S7_Attr: setting Intervall= $aVal" );
  250. }
  251. }
  252. ###########
  253. if ( $aName eq "WriteInputs-Config"
  254. || $aName eq "WriteOutputs-Config"
  255. || $aName eq "WriteFlags-Config"
  256. || $aName eq "WriteDB-Config" )
  257. {
  258. my $PDUlength = $hash->{maxPDUlength};
  259. my @a = split( "[ \t][ \t]*", $aVal );
  260. if ( int(@a) % 3 != 0 || int(@a) == 0 ) {
  261. Log3( $name, 3,
  262. "S7: Invalid $aName in attr $name $aName $aVal: $@" );
  263. return
  264. "Invalid $aName $aVal \n Format: <DB> <STARTPOSITION> <LENGTH> [<DB> <STARTPOSITION> <LENGTH> ]";
  265. }
  266. else {
  267. for ( my $i = 0 ; $i < int(@a) ; $i++ ) {
  268. if ( $a[$i] ne int( $a[$i] ) ) {
  269. my $s = $a[$i];
  270. Log3( $name, 3,
  271. "S7: Invalid $aName in attr $name $aName $aVal ($s is not a number): $@"
  272. );
  273. return "Invalid $aName $aVal: $s is not a number";
  274. }
  275. if ( $i % 3 == 0 && ( $a[$i] < 0 || $a[$i] > 1024 ) ) {
  276. Log3( $name, 3,
  277. "S7: Invalid $aName db. valid db 0 - 1024: $@" );
  278. return
  279. "Invalid $aName length: $aVal db: valid db 0 - 1024";
  280. }
  281. if ( $i % 3 == 1 && ( $a[$i] < 0 || $a[$i] > 32768 ) ) {
  282. Log3( $name, 3,
  283. "S7: Invalid $aName startposition. valid startposition 0 - 32768: $@"
  284. );
  285. return
  286. "Invalid $aName startposition: $aVal db: valid startposition 0 - 32768";
  287. }
  288. if ( $i % 3 == 2
  289. && ( $a[$i] < 1 || $a[$i] > $PDUlength ) )
  290. {
  291. Log3( $name, 3,
  292. "S7: Invalid $aName length. valid length 1 - $PDUlength: $@"
  293. );
  294. return
  295. "Invalid $aName lenght: $aVal: valid length 1 - $PDUlength";
  296. }
  297. }
  298. return undef if ( $hash->{STATE} ne "connected to PLC" );
  299. #we need to fill-up the internal buffer from current PLC values
  300. my $hash = $defs{$name};
  301. my $res =
  302. S7_getAllWritingBuffersFromPLC( $hash, $aName, $aVal );
  303. if ( int($res) != 0 ) {
  304. #quit because of error
  305. return $res;
  306. }
  307. }
  308. }
  309. }
  310. return undef;
  311. }
  312. #####################################
  313. sub S7_getAreaIndex4AreaName($) { #S5 OK
  314. my ($aName) = @_;
  315. my $AreaIndex = -1;
  316. for ( my $j = 0 ; $j < int(@areaname) ; $j++ ) {
  317. if ( $aName eq $areasconfig[$j] || $aName eq $areaname[$j] ) {
  318. $AreaIndex = $j;
  319. last;
  320. }
  321. }
  322. if ( $AreaIndex < 0 ) {
  323. Log3( undef, 2, "S7_Attr: Internal error invalid WriteAreaIndex" );
  324. return "Internal error invalid WriteAreaIndex";
  325. }
  326. return $AreaIndex;
  327. }
  328. #####################################
  329. sub S7_WriteToPLC($$$$$$) {
  330. my ( $hash, $areaIndex, $dbNr, $startByte, $WordLen, $dataBlock ) = @_;
  331. my $PDUlength = -1;
  332. if ( defined $hash->{maxPDUlength} ) {
  333. $PDUlength = $hash->{maxPDUlength};
  334. }
  335. my $name = $hash->{NAME};
  336. my $res = -1;
  337. my $Bufferlength = 59999;
  338. $Bufferlength = length($dataBlock);
  339. if ( $Bufferlength <= $PDUlength ) {
  340. if ( $hash->{STATE} eq "connected to PLC" ) {
  341. my $bss = join( ", ", unpack( "H2" x $Bufferlength, $dataBlock ) );
  342. Log3( $name, 5,
  343. "$name S7_WriteToPLC: Write Bytes to PLC: $areaIndex, $dbNr,$startByte , $Bufferlength, $bss"
  344. );
  345. eval {
  346. local $SIG{__DIE__} = sub {
  347. my ($s) = @_;
  348. print "DIE:$s";
  349. Log3( $hash, 0, "DIE:$s" );
  350. $res = -2;
  351. };
  352. if ( $hash->{S7TYPE} eq "S5" ) {
  353. $res = $hash->{S7PLCClient}->S5WriteS5Bytes(
  354. $s7areas[$areaIndex], $dbNr, $startByte, $Bufferlength,
  355. $dataBlock
  356. );
  357. }
  358. else {
  359. $res =
  360. $hash->{S7PLCClient}
  361. ->WriteArea( $s7areas[$areaIndex], $dbNr, $startByte,
  362. $Bufferlength, $WordLen, $dataBlock );
  363. }
  364. };
  365. if ( $res != 0 ) {
  366. my $error = $hash->{S7PLCClient}->getErrorStr($res);
  367. my $msg = "$name S7_WriteToPLC WriteArea error: $res=$error";
  368. Log3( $name, 3, $msg );
  369. S7_reconnect($hash); #lets try a reconnect
  370. return ( -2, $msg );
  371. }
  372. }
  373. else {
  374. my $msg = "$name S7_WriteToPLC: PLC is not connected ";
  375. Log3( $name, 3, $msg );
  376. S7_reconnect($hash); #lets try a reconnect
  377. return ( -2, $msg );
  378. }
  379. }
  380. else {
  381. my $msg =
  382. "S7_WriteToPLC: wrong block length $Bufferlength (max length $PDUlength)";
  383. Log3( $name, 3, $msg );
  384. return ( -1, $msg );
  385. }
  386. }
  387. #####################################
  388. sub S7_WriteBitToPLC($$$$$) {
  389. my ( $hash, $areaIndex, $dbNr, $bitPosition, $bitValue ) = @_;
  390. my $PDUlength = -1;
  391. if ( defined $hash->{maxPDUlength} ) {
  392. $PDUlength = $hash->{maxPDUlength};
  393. }
  394. my $name = $hash->{NAME};
  395. my $res = -1;
  396. my $Bufferlength = 1;
  397. if ( $Bufferlength <= $PDUlength ) {
  398. if ( $hash->{STATE} eq "connected to PLC" ) {
  399. my $bss = join( ", ", unpack( "H2" x $Bufferlength, $bitValue ) );
  400. Log3( $name, 5,
  401. "$name S7_WriteBitToPLC: Write Bytes to PLC: $areaIndex, $dbNr, $bitPosition , $Bufferlength, $bitValue"
  402. );
  403. eval {
  404. local $SIG{__DIE__} = sub {
  405. my ($s) = @_;
  406. print "DIE:$s";
  407. Log3 $hash, 0, "DIE:$s";
  408. $res = -2;
  409. };
  410. if ( $hash->{S7TYPE} eq "S5" ) {
  411. #todo fix S5 Handling
  412. }
  413. else {
  414. $res =
  415. $hash->{S7PLCClient}
  416. ->WriteArea( $s7areas[$areaIndex], $dbNr, $bitPosition,
  417. $Bufferlength, &S7Client::S7WLBit, chr($bitValue) );
  418. }
  419. };
  420. if ( $res != 0 ) {
  421. my $error = $hash->{S7PLCClient}->getErrorStr($res);
  422. my $msg = "$name S7_WriteBitToPLC WriteArea error: $res=$error";
  423. Log3 $name, 3, $msg;
  424. S7_reconnect($hash); #lets try a reconnect
  425. return ( -2, $msg );
  426. }
  427. }
  428. else {
  429. my $msg = "$name S7_WriteBitToPLC: PLC is not connected ";
  430. Log3 $name, 3, $msg;
  431. return ( -1, $msg );
  432. }
  433. }
  434. else {
  435. my $msg =
  436. "S7_WriteBitToPLC: wrong block length $Bufferlength (max length $PDUlength)";
  437. Log3 $name, 3, $msg;
  438. return ( -1, $msg );
  439. }
  440. }
  441. #####################################
  442. #sub S7_WriteBlockToPLC($$$$$) {
  443. # my ( $hash, $areaIndex, $dbNr, $startByte, $dataBlock ) = @_;
  444. #
  445. #
  446. # return S7_WriteToPLC($hash, $areaIndex, $dbNr, $startByte, &S7Client::S7WLByte, $dataBlock);
  447. #
  448. #}
  449. #####################################
  450. sub S7_ReadBlockFromPLC($$$$$) {
  451. my ( $hash, $areaIndex, $dbNr, $startByte, $requestedLength ) = @_;
  452. my $PDUlength = -1;
  453. if ( defined $hash->{maxPDUlength} ) {
  454. $PDUlength = $hash->{maxPDUlength};
  455. }
  456. my $name = $hash->{NAME};
  457. my $readbuffer = "";
  458. my $res = -1;
  459. if ( $requestedLength <= $PDUlength ) {
  460. if ( $hash->{STATE} eq "connected to PLC" ) {
  461. eval {
  462. local $SIG{__DIE__} = sub {
  463. my ($s) = @_;
  464. print "DIE:$s";
  465. Log3 $hash, 0, "DIE:$s";
  466. $res = -2;
  467. };
  468. if ( $hash->{S7TYPE} eq "S5" ) {
  469. ( $res, $readbuffer ) =
  470. $hash->{S7PLCClient}
  471. ->S5ReadS5Bytes( $s7areas[$areaIndex], $dbNr, $startByte,
  472. $requestedLength );
  473. }
  474. else {
  475. ( $res, $readbuffer ) =
  476. $hash->{S7PLCClient}
  477. ->ReadArea( $s7areas[$areaIndex], $dbNr, $startByte,
  478. $requestedLength, &S7Client::S7WLByte );
  479. }
  480. };
  481. if ( $res != 0 ) {
  482. my $error = $hash->{S7PLCClient}->getErrorStr($res);
  483. my $msg =
  484. "$name S7_ReadBlockFromPLC ReadArea error: $res=$error";
  485. Log3( $name, 3, $msg );
  486. S7_reconnect($hash); #lets try a reconnect
  487. return ( -2, $msg );
  488. }
  489. else {
  490. #reading was OK
  491. return ( 0, $readbuffer );
  492. }
  493. }
  494. else {
  495. my $msg = "$name S7_ReadBlockFromPLC: PLC is not connected ";
  496. Log3( $name, 3, $msg );
  497. return ( -1, $msg );
  498. }
  499. }
  500. else {
  501. my $msg =
  502. "$name S7_ReadBlockFromPLC: wrong block length (max length $PDUlength)";
  503. Log3( $name, 3, $msg );
  504. return ( -1, $msg );
  505. }
  506. }
  507. #####################################
  508. sub S7_setBitInBuffer($$$) { #S5 OK
  509. my ( $bitPosition, $buffer, $newValue ) = @_;
  510. my $Bufferlength = ( length($buffer) + 1 ) / 3;
  511. my $bytePosition = int( $bitPosition / 8 );
  512. # Log3 undef, 3, "S7_setBitInBuffer in: ".length($buffer)." , $Bufferlength , $bytePosition , $bitPosition";
  513. if ( $bytePosition < 0 || $bytePosition > $Bufferlength - 1 ) {
  514. #out off buffer request !!!!!
  515. # Log3 undef, 3, "S7_setBitInBuffer out -1 : ".length($buffer);
  516. return ( -1, undef );
  517. }
  518. my @Writebuffer = unpack( "C" x $Bufferlength,
  519. pack( "H2" x $Bufferlength, split( ",", $buffer ) ) );
  520. my $intrestingBit = $bitPosition % 8;
  521. if ( $newValue eq "on" || $newValue eq "trigger" ) {
  522. $Writebuffer[$bytePosition] |= ( 1 << $intrestingBit );
  523. }
  524. else {
  525. $Writebuffer[$bytePosition] &= ( ( ~( 1 << $intrestingBit ) ) & 0xff );
  526. }
  527. my $resultBuffer = join(
  528. ",",
  529. unpack(
  530. "H2" x $Bufferlength,
  531. pack( "C" x $Bufferlength, @Writebuffer )
  532. )
  533. );
  534. $Bufferlength = length($resultBuffer);
  535. # Log3 undef, 3, "S7_setBitInBuffer out: $Bufferlength";
  536. return ( 0, $resultBuffer );
  537. }
  538. #####################################
  539. sub S7_getBitFromBuffer($$) { #S5 OK
  540. my ( $bitPosition, $buffer ) = @_;
  541. my $Bufferlength = ( length($buffer) * 3 ) - 1;
  542. my $bytePosition = int( $bitPosition / 8 );
  543. if ( $bytePosition < 0 || $bytePosition > length($Bufferlength) ) {
  544. #out off buffer request !!!!!
  545. return "unknown";
  546. }
  547. my @Writebuffer = unpack( "C" x $Bufferlength,
  548. pack( "H2" x $Bufferlength, split( ",", $buffer ) ) );
  549. my $intrestingByte = $Writebuffer[$bytePosition];
  550. my $intrestingBit = $bitPosition % 8;
  551. if ( ( $intrestingByte & ( 1 << $intrestingBit ) ) != 0 ) {
  552. return "on";
  553. }
  554. else {
  555. return "off";
  556. }
  557. }
  558. #####################################
  559. sub S7_getAllWritingBuffersFromPLC($$$) { #S5 OK
  560. #$hash ... from S7 physical modul
  561. #$writerConfig ... writer Config
  562. #$aName ... area name
  563. my ( $hash, $aName, $writerConfig ) = @_;
  564. Log3( $aName, 4, "S7: getAllWritingBuffersFromPLC called" );
  565. my @a = split( "[ \t][ \t]*", $writerConfig );
  566. my $PDUlength = $hash->{maxPDUlength};
  567. my @writingBuffers = ();
  568. my $readbuffer;
  569. my $writeAreaIndex = S7_getAreaIndex4AreaName($aName);
  570. return $writeAreaIndex if ( $writeAreaIndex ne int($writeAreaIndex) );
  571. my $nr = int(@a);
  572. # Log3 undef, 4, "S7: getAllWritingBuffersFromPLC $nr";
  573. my $res;
  574. for ( my $i = 0 ; $i < int(@a) ; $i = $i + 3 ) {
  575. my $readbuffer;
  576. my $res;
  577. my $dbnr = $a[$i];
  578. my $startByte = $a[ $i + 1 ];
  579. my $requestedLength = $a[ $i + 2 ];
  580. ( $res, $readbuffer ) =
  581. S7_ReadBlockFromPLC( $hash, $writeAreaIndex, $dbnr, $startByte,
  582. $requestedLength );
  583. if ( $res == 0 ) { #reading was OK
  584. my $hexbuffer =
  585. join( ",", unpack( "H2" x length($readbuffer), $readbuffer ) );
  586. push( @writingBuffers, $hexbuffer );
  587. }
  588. else {
  589. #error in reading so just return the error MSG
  590. return $readbuffer;
  591. }
  592. }
  593. if ( int(@writingBuffers) > 0 ) {
  594. $hash->{"${areaname[$writeAreaIndex]}_DBWRITEBUFFER"} =
  595. join( " ", @writingBuffers );
  596. }
  597. else {
  598. $hash->{"${areaname[$writeAreaIndex]}_DBWRITEBUFFER"} = undef;
  599. }
  600. return 0;
  601. }
  602. #####################################
  603. sub S7_GetUpdate($) {
  604. my ($hash) = @_;
  605. my $name = $hash->{NAME};
  606. Log3( $name, 4, "S7: $name GetUpdate called ..." );
  607. my $res = S7_readFromPLC($hash);
  608. if ( $res == 0 ) {
  609. InternalTimer( gettimeofday() + $hash->{Interval},
  610. "S7_GetUpdate", $hash, 1 );
  611. }
  612. else {
  613. #an error has occoured --> 10sec break
  614. InternalTimer( gettimeofday() + 10, "S7_GetUpdate", $hash, 1 );
  615. }
  616. }
  617. #####################################
  618. sub S7_dispatchMsg($$$$$$$$) {
  619. my ( $hash, $msgprefix, $areaIndex, $dbNr, $startByte, $hexbuffer, $length,
  620. $clientsNames )
  621. = @_;
  622. my $name = $hash->{NAME};
  623. my $dmsg =
  624. $msgprefix . " "
  625. . $areaname[$areaIndex] . " "
  626. . $dbNr . " "
  627. . $startByte . " "
  628. . $length . " "
  629. . $name . " "
  630. . $hexbuffer . " "
  631. . $clientsNames;
  632. Log3( $name, 5, $name . " S7_dispatchMsg " . $dmsg );
  633. Dispatch( $hash, $dmsg, {} );
  634. }
  635. #####################################
  636. sub S7_readAndDispatchBlockFromPLC($$$$$$$$$$) { #S5 OK
  637. my (
  638. $hash, $area, $dbnr,
  639. $blockstartpos, $blocklength, $hasAnalogReading,
  640. $hasDigitalReading, $hasAnalogWriting, $hasDigitalWriting,
  641. $clientsNames
  642. ) = @_;
  643. my $name = $hash->{NAME};
  644. my $state = $hash->{STATE};
  645. my $areaIndex = S7_getAreaIndex4AreaName($area);
  646. Log3( $name, 4,
  647. $name
  648. . " READ Block AREA="
  649. . $area . " ("
  650. . $areaIndex
  651. . "), DB ="
  652. . $dbnr
  653. . ", ADDRESS="
  654. . $blockstartpos
  655. . ", LENGTH="
  656. . $blocklength );
  657. if ( $state ne "connected to PLC" ) {
  658. Log3 $name, 3, "$name is disconnected ? --> reconnect";
  659. S7_reconnect($hash); #lets try a reconnect
  660. #@nextreadings[ $i / 4 ] = $now + 10; #retry in 10s
  661. return -2;
  662. }
  663. my $res;
  664. my $readbuffer;
  665. ( $res, $readbuffer ) =
  666. S7_ReadBlockFromPLC( $hash, $areaIndex, $dbnr, $blockstartpos,
  667. $blocklength );
  668. if ( $res == 0 ) {
  669. #reading was OK
  670. my $length = length($readbuffer);
  671. my $hexbuffer = join( ",", unpack( "H2" x $length, $readbuffer ) );
  672. #dispatch to reader
  673. S7_dispatchMsg( $hash, "AR", $areaIndex, $dbnr, $blockstartpos,
  674. $hexbuffer, $length, $clientsNames )
  675. if ( $hasAnalogReading > 0 );
  676. S7_dispatchMsg( $hash, "DR", $areaIndex, $dbnr, $blockstartpos,
  677. $hexbuffer, $length, $clientsNames )
  678. if ( $hasDigitalReading > 0 );
  679. #dispatch to writer
  680. S7_dispatchMsg( $hash, "AW", $areaIndex, $dbnr, $blockstartpos,
  681. $hexbuffer, $length, $clientsNames )
  682. if ( $hasAnalogWriting > 0 );
  683. S7_dispatchMsg( $hash, "DW", $areaIndex, $dbnr, $blockstartpos,
  684. $hexbuffer, $length, $clientsNames )
  685. if ( $hasDigitalWriting > 0 );
  686. return 0;
  687. }
  688. else {
  689. #reading failed
  690. return -1;
  691. }
  692. }
  693. #####################################
  694. sub S7_getReadingsList($) { #S5 OK
  695. my ($hash) = @_;
  696. my $name = $hash->{NAME};
  697. my @readings;
  698. # Jetzt suchen wir alle Readings
  699. my @mykeys;
  700. my %logoClients;
  701. @mykeys =
  702. grep $defs{$_}{TYPE} =~ /^S7_/ && $defs{$_}{IODev}{NAME} eq $hash->{NAME},
  703. keys(%defs);
  704. @logoClients{@mykeys} =
  705. @defs{@mykeys}; #jetzt haben wir alle clients in logoClients
  706. #we need to find out the unique areas
  707. my %tmphash = map { $logoClients{$_}{AREA} => 1 } keys %logoClients;
  708. my @uniqueArea = keys %tmphash;
  709. foreach my $Area (@uniqueArea) {
  710. my %logoClientsArea;
  711. @mykeys =
  712. grep $defs{$_}{TYPE} =~ /^S7_/
  713. && $defs{$_}{IODev}{NAME} eq $hash->{NAME}
  714. && $defs{$_}{AREA} eq $Area, keys(%defs);
  715. @logoClientsArea{@mykeys} = @defs{@mykeys};
  716. #now we findout which DBs are used (unique)
  717. %tmphash = map { $logoClientsArea{$_}{DB} => 1 } keys %logoClientsArea;
  718. my @uniqueDB = keys %tmphash;
  719. foreach my $DBNr (@uniqueDB) {
  720. #now we filter all readinfy by DB!
  721. my %logoClientsDB;
  722. @mykeys =
  723. grep $defs{$_}{TYPE} =~ /^S7_/
  724. && $defs{$_}{IODev}{NAME} eq $hash->{NAME}
  725. && $defs{$_}{AREA} eq $Area
  726. && $defs{$_}{DB} == $DBNr, keys(%defs);
  727. @logoClientsDB{@mykeys} = @defs{@mykeys};
  728. #next step is, sorting all clients by ADDRESS
  729. my @positioned = sort {
  730. $logoClientsDB{$a}{ADDRESS} <=> $logoClientsDB{$b}{ADDRESS}
  731. } keys %logoClientsDB;
  732. my $blockstartpos = -1;
  733. my $blocklength = 0;
  734. my $hasAnalogReading = 0;
  735. my $hasDigitalReading = 0;
  736. my $hasAnalogWriting = 0;
  737. my $hasDigitalWriting = 0;
  738. my $clientsName = "";
  739. for ( my $i = 0 ; $i < int(@positioned) ; $i++ ) {
  740. if ( $blockstartpos < 0 ) {
  741. #we start a new block
  742. $blockstartpos =
  743. int( $logoClientsDB{ $positioned[$i] }{ADDRESS} );
  744. $blocklength = $logoClientsDB{ $positioned[$i] }{LENGTH};
  745. $hasAnalogReading++
  746. if (
  747. $logoClientsDB{ $positioned[$i] }{TYPE} eq "S7_ARead" );
  748. $hasDigitalReading++
  749. if (
  750. $logoClientsDB{ $positioned[$i] }{TYPE} eq "S7_DRead" );
  751. $hasAnalogWriting++
  752. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  753. "S7_AWrite" );
  754. $hasDigitalWriting++
  755. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  756. "S7_DWrite" );
  757. $clientsName = $logoClientsDB{ $positioned[$i] }{NAME};
  758. }
  759. else {
  760. if ( $logoClientsDB{ $positioned[$i] }{ADDRESS} +
  761. $logoClientsDB{ $positioned[$i] }{LENGTH} -
  762. $blockstartpos <=
  763. $hash->{S7PLCClient}->{MaxReadLength} )
  764. {
  765. #extend existing block
  766. if (
  767. int( $logoClientsDB{ $positioned[$i] }{ADDRESS} ) +
  768. $logoClientsDB{ $positioned[$i] }{LENGTH} -
  769. $blockstartpos > $blocklength )
  770. {
  771. $blocklength =
  772. int( $logoClientsDB{ $positioned[$i] }{ADDRESS} )
  773. + $logoClientsDB{ $positioned[$i] }{LENGTH} -
  774. $blockstartpos;
  775. $hasAnalogReading++
  776. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  777. "S7_ARead" );
  778. $hasDigitalReading++
  779. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  780. "S7_DRead" );
  781. $hasAnalogWriting++
  782. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  783. "S7_AWrite" );
  784. $hasDigitalWriting++
  785. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  786. "S7_DWrite" );
  787. }
  788. $clientsName .=
  789. "," . $logoClientsDB{ $positioned[$i] }{NAME};
  790. }
  791. else {
  792. #block would exeed MaxReadLength
  793. #read and dispatch block from PLC
  794. #block in liste speichern
  795. push(
  796. @readings,
  797. [
  798. $logoClientsDB{ $positioned[$i] }{AREA},
  799. $logoClientsDB{ $positioned[$i] }{DB},
  800. $blockstartpos,
  801. $blocklength,
  802. $hasAnalogReading,
  803. $hasDigitalReading,
  804. $hasAnalogWriting,
  805. $hasDigitalWriting,
  806. $clientsName
  807. ]
  808. );
  809. $hasAnalogReading = 0;
  810. $hasDigitalReading = 0;
  811. $hasAnalogWriting = 0;
  812. $hasDigitalWriting = 0;
  813. #start new block new time
  814. $blockstartpos =
  815. int( $logoClientsDB{ $positioned[$i] }{ADDRESS} );
  816. $blocklength =
  817. $logoClientsDB{ $positioned[$i] }{LENGTH};
  818. $hasAnalogReading++
  819. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  820. "S7_ARead" );
  821. $hasDigitalReading++
  822. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  823. "S7_DRead" );
  824. $hasAnalogWriting++
  825. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  826. "S7_AWrite" );
  827. $hasDigitalWriting++
  828. if ( $logoClientsDB{ $positioned[$i] }{TYPE} eq
  829. "S7_DWrite" );
  830. $clientsName = $logoClientsDB{ $positioned[$i] }{NAME};
  831. }
  832. }
  833. }
  834. if ( $blockstartpos >= 0 ) {
  835. #read and dispatch block from PLC
  836. push(
  837. @readings,
  838. [
  839. $logoClientsDB{ $positioned[ int(@positioned) - 1 ] }
  840. {AREA},
  841. $logoClientsDB{ $positioned[ int(@positioned) - 1 ] }
  842. {DB},
  843. $blockstartpos,
  844. $blocklength,
  845. $hasAnalogReading,
  846. $hasDigitalReading,
  847. $hasAnalogWriting,
  848. $hasDigitalWriting,
  849. $clientsName
  850. ]
  851. );
  852. }
  853. }
  854. }
  855. @{ $hash->{ReadingList} } = @readings;
  856. return 0;
  857. }
  858. #####################################
  859. sub S7_readFromPLC($) { #S5 OK
  860. my ($hash) = @_;
  861. my $name = $hash->{NAME};
  862. my $res;
  863. if ( ( !defined( $hash->{dirty} ) ) || $hash->{dirty} == 1 ) {
  864. S7_getReadingsList($hash);
  865. $hash->{dirty} = 0;
  866. }
  867. my @readingList = @{ $hash->{ReadingList} };
  868. for ( my $i = 0 ; $i < int(@readingList) ; $i++ ) {
  869. my @readingSet = @{ $readingList[$i] };
  870. $res = S7_readAndDispatchBlockFromPLC(
  871. $hash, $readingSet[0], $readingSet[1], $readingSet[2],
  872. $readingSet[3], $readingSet[4], $readingSet[5], $readingSet[6],
  873. $readingSet[7], $readingSet[8]
  874. );
  875. return $res if ( $res != 0 );
  876. }
  877. return 0;
  878. }
  879. 1;
  880. =pod
  881. =item summary basic interface to a SIEMENS S7 / S5
  882. =item summary_DE Schnittstelle zu einer Siemens S7 / S5
  883. =begin html
  884. <a name="S7"></a>
  885. <h3>S7</h3>
  886. <ul>
  887. This module connects a SIEMENS PLC (Note: also SIEMENS Logo is supported).
  888. The TCP communication module is based on settimino (http://settimino.sourceforge.net)
  889. You can found a german wiki here: httl://www.fhemwiki.de/wiki/S7
  890. <br><br>
  891. For the communication the following modules have been implemented:
  892. <ul>
  893. <li>S7 … sets up the communication channel to the PLC</li>
  894. <li>S7_ARead … Is used for reading integer Values from the PLC</li>
  895. <li>S7_AWrite … Is used for write integer Values to the PLC</li>
  896. <li>S7_DRead … Is used for read bits </li>
  897. <li>S7_DWrite … Is used for writing bits.</li>
  898. </ul>
  899. <br>
  900. <br>
  901. Reading work flow:
  902. <br>
  903. <br>
  904. The S7 module reads periodically the configured DB areas from the PLC and stores the data in an internal buffer. Then all reading client modules are informed. Each client module extracts his data and the corresponding readings are set.
  905. <brl>
  906. <brl>
  907. Writing work flow:
  908. <br>
  909. <br>
  910. At the S7 module you need to configure the PLC writing target. Also the S7 module holds a writing buffer. Which contains a master copy of the data needs to send.
  911. <br>
  912. (Note: after configuration of the writing area a copy from the PLC is read and used as initial fill-up of the writing buffer)
  913. <br>
  914. Note: The S7 module will send always the whole data block to the PLC.
  915. When data on the clients modules is set then the client module updates the internal writing buffer on the S7 module and triggers the writing to the PLC.
  916. <br>
  917. <br>
  918. <a name="S7define"></a>
  919. <b>Define</b>
  920. <ul>
  921. <code>define &lt;name&gt; S7 &lt;PLC_address&gt; &lt;rack&gt; &lt;slot&gt; [&lt;Interval&gt;] </code><br><br>
  922. <code>define logo S7 10.0.0.241 2 0 </code><br>
  923. <ul>
  924. <li>PLC_address … IP address of the S7 PLC (For S5 see below)</li>
  925. <li>rack … rack of the PLC</li>
  926. <li>slot … slot of the PLC</li>
  927. <li>Interval … Intervall how often the modul should check if a reading is required</li>
  928. </ul>
  929. <br>
  930. Note: For Siemens logo you should use a alternative (more simply configuration method):<br>
  931. define logo S7 LOGO7 10.0.0.241
  932. </ul>
  933. </ul>
  934. <br>
  935. Note: For Siemens S5 you must use a alternative (more simply configuration method):<br>
  936. define logo S7 S5 /dev/tty1
  937. in this case the PLC_address is the serial port number
  938. </ul>
  939. <br><br>
  940. <b>Attr</b><br>
  941. The following attributes are supported:<br>
  942. <ul>
  943. <li>MaxMessageLength</li>
  944. <br>
  945. MaxMessageLength ... restricts the packet length if lower than the negioated PDULength. This could be used to increate the processing speed. 2 small packages may be smaler than one large package
  946. =end html
  947. =begin html_DE
  948. <a name="S7"></a>
  949. <h3>S7</h3>
  950. <ul>
  951. This module connects a SIEMENS PLC (Note: also SIEMENS Logo is supported).
  952. The TCP communication module is based on settimino (http://settimino.sourceforge.net)
  953. You can found a german wiki here: httl://www.fhemwiki.de/wiki/S7
  954. <br><br>
  955. For the communication the following modules have been implemented:
  956. <ul>
  957. <li>S7 … sets up the communication channel to the PLC</li>
  958. <li>S7_ARead … Is used for reading integer Values from the PLC</li>
  959. <li>S7_AWrite … Is used for write integer Values to the PLC</li>
  960. <li>S7_DRead … Is used for read bits </li>
  961. <li>S7_DWrite … Is used for writing bits.</li>
  962. </ul>
  963. <br>
  964. <br>
  965. Reading work flow:
  966. <br>
  967. <br>
  968. The S7 module reads periodically the configured DB areas from the PLC and stores the data in an internal buffer. Then all reading client modules are informed. Each client module extracts his data and the corresponding readings are set.
  969. <brl>
  970. <brl>
  971. Writing work flow:
  972. <br>
  973. <br>
  974. At the S7 module you need to configure the PLC writing target. Also the S7 module holds a writing buffer. Which contains a master copy of the data needs to send.
  975. <br>
  976. (Note: after configuration of the writing area a copy from the PLC is read and used as initial fill-up of the writing buffer)
  977. <br>
  978. Note: The S7 module will send always the whole data block to the PLC.
  979. When data on the clients modules is set then the client module updates the internal writing buffer on the S7 module and triggers the writing to the PLC.
  980. <br>
  981. <br>
  982. <a name="S7define"></a>
  983. <b>Define</b>
  984. <ul>
  985. <code>define &lt;name&gt; S7 &lt;PLC_address&gt; &lt;rack&gt; &lt;slot&gt; [&lt;Interval&gt;] </code><br><br>
  986. <code>define logo S7 10.0.0.241 2 0 </code><br>
  987. <ul>
  988. <li>PLC_address … IP address of the S7 PLC (For S5 see below)</li>
  989. <li>rack … rack of the PLC</li>
  990. <li>slot … slot of the PLC</li>
  991. <li>Interval … Intervall how often the modul should check if a reading is required</li>
  992. </ul>
  993. <br>
  994. Note: For Siemens logo you should use a alternative (more simply configuration method):<br>
  995. define logo S7 LOGO7 10.0.0.241
  996. </ul>
  997. </ul>
  998. <br>
  999. Note: For Siemens S5 you must use a alternative (more simply configuration method):<br>
  1000. define logo S7 S5 /dev/tty1
  1001. in this case the PLC_address is the serial port number
  1002. </ul>
  1003. <br><br>
  1004. <b>Attr</b><br>
  1005. The following attributes are supported:<br>
  1006. <ul>
  1007. <li>MaxMessageLength</li>
  1008. <br>
  1009. MaxMessageLength ... restricts the packet length if lower than the negioated PDULength. This could be used to increate the processing speed. 2 small packages may be smaler than one large package
  1010. =end html_DE
  1011. =cut