44_S7_S5Client.pm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. # $Id: 44_S7_S5Client.pm 15518 2017-11-28 21:17:47Z charlie71 $
  2. ##############################################
  3. use strict;
  4. use warnings;
  5. require Exporter;
  6. use Config;
  7. use AutoLoader;
  8. require "44_S7_Client.pm";
  9. package S5Client;
  10. #use S7ClientBase;
  11. our @ISA = qw(S7ClientBase); # inherits from Person
  12. #---------------------- constants for communication
  13. use constant DLE => 0x10;
  14. use constant ETX => 0x03;
  15. use constant STX => 0x02;
  16. use constant SYN => 0x16;
  17. use constant NAK => 0x15;
  18. use constant EOT => 0x04; # for S5
  19. use constant ACK => 0x06; # for S5
  20. use constant daveS5BlockType_DB => 0x01;
  21. use constant maxSysinfoLen => 87;
  22. use constant daveMaxRawLen => 2048;
  23. use constant MaxPduSize =>
  24. 240;
  25. sub new {
  26. my $class = shift;
  27. my $self = $class->SUPER::new();
  28. $self->{S5PAEAddress} = 0;
  29. $self->{S5PAAAddress} = 0;
  30. $self->{S5flagsAddress} = 0;
  31. $self->{S5timerAddress} = 0;
  32. $self->{S5counterAddress} = 0;
  33. $self->{__davet1006} = [ &DLE, &ACK ];
  34. $self->{__daveT161003} = [ 0x16, &DLE, &ETX ];
  35. $self->{__davet121003} = [ 0x12, &DLE, &ETX ];
  36. $self->{PDULength} = &MaxPduSize;
  37. $self->{MaxReadLength} = ($self->{PDULength} - 18);
  38. #my @__davet1006 = ( &DLE, &ACK );
  39. #my @__daveT161003 = ( 0x16, &DLE, &ETX );
  40. #my @{$self->{__davet121003}} = ( 0x12, &DLE, &ETX );
  41. return bless $self, $class;
  42. }
  43. # ----------- compare arrays
  44. sub compare {
  45. my ( $self, $a_ref, $b_ref ) = @_;
  46. my @a = @{$a_ref}; # dereferencing and copying each array
  47. my @b = @{$b_ref};
  48. if ( @a != @b ) {
  49. return 0;
  50. }
  51. else {
  52. foreach ( my $i = 0 ; $i < @a ; $i++ ) {
  53. # Ideally, check for undef/value comparison here as well
  54. if ( $a[$i] != $b[$i] )
  55. { # use "ne" if elements are strings, not numbers
  56. # Or you can use generic sub comparing 2 values
  57. return 0;
  58. }
  59. }
  60. return 1;
  61. }
  62. }
  63. #
  64. # ----------- This writes a single chracter to the serial interface
  65. #
  66. sub S5SendSingle($$) {
  67. my ( $self, $c ) = @_;
  68. my $buffer = pack( 'C*', $c );
  69. my $tbuffer = join( ", ", unpack( "H2 " x length($buffer), $buffer ) );
  70. main::Log3( undef, 5, "S5Client S5SendSingle <-- " . $tbuffer );
  71. $self->{serial}->write($buffer);
  72. }
  73. #---------------------------------------------------reqest transaction with PLC
  74. sub S5ReqTrans($$) {
  75. my ( $self, $trN ) = @_;
  76. my $buffer;
  77. my $count;
  78. my $tbuffer;
  79. $self->S5SendSingle(&STX); #start trasmission
  80. #expected S5 awnswer DLE,ACK
  81. ( $count, $buffer ) = $self->{serial}->read(2);
  82. my @cbuffer = unpack( "C" x $count, $buffer );
  83. if ( $main::attr{global}{verbose} >= 5 ) {
  84. $tbuffer = join( ", ", unpack( "H2 " x $count, $buffer ) );
  85. main::Log3( undef, 5, "S5Client S5ReqTrans $tbuffer -->" );
  86. }
  87. if ( $self->compare( \@cbuffer, \@{ $self->{__davet1006} } ) == 0 ) {
  88. main::Log3( undef, 3, "S5Client S5ReqTrans: no DLE,ACK before send" );
  89. return -1;
  90. }
  91. $self->S5SendSingle($trN);
  92. ( $count, $buffer ) = $self->{serial}->read(1);
  93. if ( $main::attr{global}{verbose} >= 5 ) {
  94. $tbuffer = join( ", ", unpack( "H2 " x $count, $buffer ) );
  95. main::Log3( undef, 5, "S5Client S5ReqTrans $tbuffer -->" );
  96. }
  97. if ( $count != 1 ) {
  98. #error awnser is too short
  99. return -1;
  100. }
  101. @cbuffer = unpack( "C" x $count, $buffer );
  102. if ( $cbuffer[0] ne &STX ) {
  103. main::Log3( undef, 3, "S5Client S5ReqTrans: no STX before send" );
  104. return -2;
  105. }
  106. $self->S5SendDLEACK();
  107. ( $count, $buffer ) = $self->{serial}->read(3);
  108. if ( $main::attr{global}{verbose} >= 5 ) {
  109. $tbuffer = join( ", ", unpack( "H2 " x $count, $buffer ) );
  110. main::Log3( undef, 5, "S5Client S5ReqTrans $tbuffer -->" );
  111. }
  112. @cbuffer = unpack( "C" x $count, $buffer );
  113. if ( $self->compare( \@cbuffer, \@{ $self->{__daveT161003} } ) == 0 ) {
  114. main::Log3( undef, 3, "S5Client S5ReqTrans: no accept0 from plc" );
  115. return -3;
  116. }
  117. $self->S5SendDLEACK();
  118. return 0;
  119. }
  120. sub S5SendDLEACK($) {
  121. my ($self) = @_;
  122. my $buffer = pack( 'C2', @{ $self->{__davet1006} } );
  123. if ( $main::attr{global}{verbose} >= 5 ) {
  124. my $tbuffer = join( ", ", unpack( "H2 " x 2, $buffer ) );
  125. main::Log3( undef, 5, "S5Client S5SendDLEACK <-- $tbuffer" );
  126. }
  127. return $self->{serial}->write($buffer);
  128. }
  129. #---------------------------------------------- S5 Exchange data
  130. sub S5ExchangeAS511($$$$$) {
  131. my ( $self, $b, $len, $maxlen, $trN ) = @_;
  132. my ( $res, $i, $b1, $count );
  133. my @cbuffer;
  134. my $msgIn = "";
  135. my $tbuffer;
  136. $res = $self->S5ReqTrans($trN);
  137. if ( $res < 0 ) {
  138. main::Log3( undef, 3,
  139. "S5Client S5ExchangeAS511: Error in Exchange.ReqTrans request" );
  140. return ( $res - 10, "" );
  141. }
  142. if ( $trN == 8 ) { #Block write functions have advanced syntax
  143. #LOG1("trN 8\n");
  144. $self->S5SendWithDLEDup( $b, 4 );
  145. #LOG1("trN 8 done\n");
  146. }
  147. else {
  148. #LOG3("trN %d len %d\n",trN,len);
  149. $self->S5SendWithDLEDup( $b, $len );
  150. #LOG2("trN %d done\n",trN);
  151. }
  152. ( $count, $b1 ) = $self->{serial}->read(2);
  153. # if ( $main::attr{global}{verbose} >= 5 ) {
  154. $tbuffer = join( ", ", unpack( "H2 " x $count, $b1 ) );
  155. main::Log3( undef, 5, "S5Client S5ExchangeAS511 $tbuffer -->" );
  156. # }
  157. @cbuffer = unpack( "C" x $count, $b1 );
  158. if ( $self->compare( \@cbuffer, \@{ $self->{__davet1006} } ) == 0 ) {
  159. main::Log3( undef, 3,
  160. "S5Client S5ExchangeAS511: no DLE,ACK in Exchange request" );
  161. return ( -1, "" );
  162. }
  163. if ( ( $trN != 3 ) && ( $trN != 7 ) && ( $trN != 9 ) ) {
  164. #write bytes, compress & delblk
  165. if ( !$self->S5ReadSingle() eq &STX ) {
  166. main::Log3( undef, 3,
  167. "S5Client S5ExchangeAS511: no STX in Exchange request" );
  168. return ( -2, "" );
  169. }
  170. $self->S5SendDLEACK();
  171. $res = 0;
  172. @cbuffer = ();
  173. my $buffer = "";
  174. do {
  175. ( $i, $b1 ) = $self->{serial}->read(1);
  176. $res += $i;
  177. push( @cbuffer, unpack( "C" x $i, $b1 ) ) if ( $i > 0 );
  178. } while (
  179. ( $i > 0 )
  180. && ( ( $cbuffer[ $res - 2 ] != &DLE )
  181. || ( $cbuffer[ $res - 1 ] != &ETX ) )
  182. );
  183. if ( $main::attr{global}{verbose} >= 5 ) {
  184. $tbuffer =
  185. join( ", ", unpack( "H2 " x @cbuffer, pack( "C*", @cbuffer ) ) );
  186. main::Log3( undef, 5, "S5Client S5ExchangeAS511 $tbuffer -->" );
  187. }
  188. #LOG3( "%s *** got %d bytes.\n", dc->iface->name, res );
  189. if ( $res < 0 ) {
  190. main::Log3( undef, 3,
  191. "S5Client S5ExchangeAS511: Error in Exchange.ReadChars request"
  192. );
  193. return ( $res - 20, "" );
  194. }
  195. if ( ( $cbuffer[ $res - 2 ] != &DLE )
  196. || ( $cbuffer[ $res - 1 ] != &ETX ) )
  197. {
  198. main::Log3( undef, 3,
  199. "S5Client S5ExchangeAS511: No DLE,ETX in Exchange data." );
  200. return ( -4, "" );
  201. }
  202. ( $res, $msgIn ) = $self->S5DLEDeDup( \@cbuffer );
  203. if ( $res < 0 ) {
  204. main::Log3( undef, 3,
  205. "S5Client S5ExchangeAS511: Error in Exchange rawdata." );
  206. return ( -3, "" );
  207. }
  208. $self->S5SendDLEACK();
  209. }
  210. if ( $trN == 8 ) { # Write requests have more differences from others
  211. @cbuffer = unpack( "C" x length($msgIn), $msgIn );
  212. if ( $cbuffer[0] != 9 ) { #todo fix
  213. main::Log3( undef, 3,
  214. "S5Client S5ExchangeAS511 No 0x09 in special Exchange request."
  215. );
  216. return ( -5, "" );
  217. }
  218. $self->S5SendSingle(&STX);
  219. ( $count, $b1 ) = $self->{serial}->read(2);
  220. if ( $main::attr{global}{verbose} >= 5 ) {
  221. $tbuffer = $tbuffer = join( ", ", unpack( "H2 " x $count, $b1 ) );
  222. main::Log3( undef, 5, "S5Client S5ExchangeAS511 $tbuffer -->" );
  223. }
  224. @cbuffer = unpack( "C" x $count, $b1 );
  225. if ( $self->compare( \@cbuffer, \@{ $self->{__davet1006} } ) == 0 ) {
  226. main::Log3( undef, 3,
  227. "S5Client S5ExchangeAS511 no DLE,ACK in special Exchange request"
  228. );
  229. return ( -6, "" );
  230. }
  231. my $b2 = substr( $b, 4 );
  232. $self->S5SendWithDLEDup( $b2, $len ); # todo need testing !!!
  233. #$self->S5SendWithDLEDup(dc->iface,b+4,len); #
  234. ( $count, $b1 ) = $self->{serial}->read(2);
  235. if ( $main::attr{global}{verbose} >= 5 ) {
  236. $tbuffer = join( ", ", unpack( "H2 " x $count, $b1 ) );
  237. main::Log3( undef, 5, "S5Client S5ExchangeAS511 $tbuffer -->" );
  238. }
  239. @cbuffer = unpack( "C" x $count, $b1 );
  240. if ( $self->compare( \@cbuffer, \@{ $self->{__davet1006} } ) == 0 ) {
  241. main::Log3( undef, 3,
  242. "S5Client S5ExchangeAS511 no DLE,ACK after transfer in Exchange."
  243. );
  244. return ( -7, "" );
  245. }
  246. }
  247. if ( $trN == 7 ) {
  248. }
  249. $res = $self->S5EndTrans();
  250. if ( $res < 0 ) {
  251. main::Log3( undef, 3,
  252. "S5Client S5ExchangeAS511 Error in Exchange.EndTrans request." );
  253. return ( $res - 30, "" );
  254. }
  255. return ( 0, $msgIn );
  256. }
  257. #
  258. # Sends a sequence of characters after doubling DLEs and adding DLE,EOT.
  259. #
  260. sub S5SendWithDLEDup($$$) {
  261. my ( $self, $b, $size ) = @_;
  262. # uc target[&daveMaxRawLen];
  263. my @target;
  264. my $res;
  265. my $i; #preload
  266. my @cbuffer = unpack( "C" x $size, $b );
  267. #LOG1("SendWithDLEDup: \n");
  268. #_daveDump("I send",b,size);
  269. for ( $i = 0 ; $i < $size ; $i++ ) {
  270. push( @target, $cbuffer[$i] );
  271. if ( $cbuffer[$i] == &DLE ) {
  272. push( @target, &DLE );
  273. }
  274. }
  275. push( @target, &DLE );
  276. push( @target, &EOT );
  277. #LOGx_daveDump("I send", target, targetSize);
  278. my $buffer = pack( 'C*', @target );
  279. $res = $self->{serial}->write($buffer);
  280. if ( $main::attr{global}{verbose} >= 5 ) {
  281. my $tbuffer = join( ", ", unpack( "H2 " x length($buffer), $buffer ) );
  282. main::Log3( undef, 5, "S5Client S5SendWithDLEDup <-- $tbuffer" );
  283. }
  284. #if(daveDebug & daveDebugExchange)
  285. #LOG2("send: res:%d\n",res);
  286. return 0;
  287. }
  288. #
  289. # Remove the DLE doubling:
  290. #
  291. sub S5DLEDeDup($$) {
  292. my ( $self, $b ) = @_;
  293. my @rawBuf = @{$b};
  294. my @msg = ();
  295. my $j = 0;
  296. my $k;
  297. for ( $k = 0 ; $k < @rawBuf - 2 ; $k++ ) {
  298. push( @msg, $rawBuf[$k] );
  299. if ( DLE == $rawBuf[$k] ) {
  300. if ( DLE != $rawBuf[ $k + 1 ] ) {
  301. return ( -1, "" ); #Bad doubling found
  302. }
  303. $k++;
  304. }
  305. }
  306. push( @msg, $rawBuf[$k] );
  307. $k++;
  308. push( @msg, $rawBuf[$k] );
  309. $b = pack( 'C*', @msg );
  310. return ( 0, $b );
  311. }
  312. #
  313. # Executes part of the dialog required to terminate transaction:
  314. #
  315. sub S5EndTrans($) {
  316. my ($self) = @_;
  317. #LOG2("%s daveEndTrans\n", dc->iface->name);
  318. if ( $self->S5ReadSingle() ne &STX ) {
  319. #LOG2("%s daveEndTrans *** no STX at eot sequense.\n", dc->iface->name);
  320. #return -1;
  321. }
  322. $self->S5SendDLEACK();
  323. my ( $res, $b1 ) = $self->{serial}->read(3);
  324. if ( $main::attr{global}{verbose} >= 5 ) {
  325. my $tbuffer = join( ", ", unpack( "H2 " x $res, $b1 ) );
  326. main::Log3( undef, 5, "S5Client S5EndTrans $tbuffer -->" );
  327. }
  328. #_daveDump("3got",b1, res);
  329. my @cbuffer = unpack( "C" x $res, $b1 );
  330. if ( $self->compare( \@cbuffer, \@{ $self->{__davet121003} } ) == 0 ) {
  331. main::Log3( undef, 3,
  332. "S5Client S5EndTransno accept of eot/ETX from plc." );
  333. return -2;
  334. }
  335. $self->S5SendDLEACK();
  336. return 0;
  337. }
  338. #
  339. # This reads a single chracter from the serial interface:
  340. sub S5ReadSingle ($) {
  341. my ($self) = @_;
  342. my ( $res, $i );
  343. ( $i, $res ) = $self->{serial}->read(1);
  344. if ( $main::attr{global}{verbose} >= 5 ) {
  345. my $tbuffer = join( ", ", unpack( "H2 " x $i, $res ) );
  346. main::Log3( undef, 5, "S5Client S5ReadSingle $tbuffer -->" );
  347. }
  348. #if ((daveDebug & daveDebugSpecialChars)!=0)
  349. # LOG3("readSingle %d chars. 1st %02X\n",i,res);
  350. if ( $i == 1 ) {
  351. return $res;
  352. }
  353. return 0;
  354. }
  355. #--------------------------------------------------------------------------------
  356. # Connect to S5 CPU
  357. #
  358. sub S5ConnectPLCAS511($$) {
  359. my ( $self, $portName ) = @_;
  360. my $b1 = "";
  361. my $ttyPort;
  362. if($^O =~ m/Win/) {
  363. require Win32::SerialPort;
  364. #eval ("use Win32::SerialPort;");
  365. $self->{serial} = new Win32::SerialPort ($portName);
  366. }else{
  367. #eval ("use Device::SerialPort;");
  368. require Device::SerialPort;
  369. $self->{serial} = new Device::SerialPort ($portName);
  370. }
  371. main::Log3( undef, 3, "Can't open serial port $portName" )
  372. unless ( $self->{serial} );
  373. die unless ( $self->{serial} );
  374. $self->{serial}->baudrate(9600);
  375. $self->{serial}->databits(8);
  376. $self->{serial}->parity('even');
  377. $self->{serial}->stopbits(1);
  378. $self->{serial}->read_const_time(500); # 500 milliseconds = 0.5 seconds
  379. $self->{serial}->read_char_time(10); # avg time between read char
  380. #$ttyPort->handshake('none');
  381. #$ttyPort->stty_icrnl(1);
  382. #$ttyPort->stty_ocrnl(1);
  383. #$ttyPort->stty_onlcr(1);
  384. #$ttyPort->stty_opost(1)
  385. $self->{serial}->write_settings();
  386. $b1 = pack( "C*", 0, 0 );
  387. my ( $res, $msgIn ) =
  388. $self->S5ExchangeAS511( $b1, 2, &maxSysinfoLen, 0x18 );
  389. if ( $res < 0 ) {
  390. main::Log3( undef, 3,
  391. "S5Client S5ConnectPLCAS511 ImageAddr.Exchange sequence" );
  392. return $res - 10;
  393. }
  394. if ( length($msgIn) < 47 ) {
  395. main::Log3( undef, 3,
  396. "S5Client S5ConnectPLCAS511 Too few chars in ImageAddr data" );
  397. return -2;
  398. }
  399. #_daveDump("connect:",dc->msgIn, 47);
  400. my @cbuffer = unpack( "C" x length($msgIn), $msgIn );
  401. $self->{S5PAEAddress} =
  402. $self->WordAt( \@cbuffer, 5 ); # start of inputs;
  403. $self->{S5PAAAddress} = $self->WordAt( \@cbuffer, 7 ); # start of outputs
  404. $self->{S5flagsAddress} =
  405. $self->WordAt( \@cbuffer, 9 ); # start of flag (marker) memory;
  406. $self->{S5timerAddress} =
  407. $self->WordAt( \@cbuffer, 11 ); #start of timer memory;
  408. $self->{S5counterAddress} =
  409. $self->WordAt( \@cbuffer, 13 ); #start of counter memory
  410. main::Log3( undef, 3,
  411. "S5Client ->S5ConnectPLCAS511 start of inputs in memory "
  412. . $self->{S5PAEAddress} );
  413. main::Log3( undef, 3,
  414. "S5Client ->S5ConnectPLCAS511 start of outputs in memory "
  415. . $self->{S5PAAAddress} );
  416. main::Log3( undef, 3,
  417. "S5Client ->S5ConnectPLCAS511 start of flags in memory "
  418. . $self->{S5flagsAddress} );
  419. main::Log3( undef, 3,
  420. "S5Client ->S5ConnectPLCAS511 start of timers in memory "
  421. . $self->{S5timerAddress} );
  422. main::Log3( undef, 3,
  423. "S5Client ->S5ConnectPLCAS511 start of counters in memory "
  424. . $self->{S5counterAddress} );
  425. return 0;
  426. }
  427. #
  428. # Reads <count> bytes from area <BlockN> with offset <offset>,
  429. # that can be readed with daveGetInteger etc. You can read bytes from
  430. # PBs & FBs too, but use daveReadBlock for this:
  431. #
  432. sub S5ReadS5Bytes($$$$$) {
  433. my ( $self, $area, $BlockN, $offset, $count ) = @_;
  434. my ( $res, $dataend, $datastart, $b1, $msgIn );
  435. if ( $area == &S7ClientBase::S7AreaDB ) { #DB
  436. ( $res, $datastart ) = $self->S5ReadS5BlockAddress( $area, $BlockN );
  437. if ( $res < 0 ) {
  438. main::Log3( undef, 3,
  439. "S5Client S5ReadS5Bytes Error in ReadS5Bytes.BlockAddr request"
  440. );
  441. return ( $res - 50, "" );
  442. }
  443. }
  444. elsif ( $area == &S7ClientBase::S7AreaPE ) { #inputs
  445. $datastart =
  446. $self->{S5PAEAddress}; #need to get this information from a property
  447. }
  448. elsif ( $area == &S7ClientBase::S7AreaPA ) { #outputs
  449. $datastart =
  450. $self->{S5PAAAddress}; #need to get this information from a property
  451. }
  452. elsif ( $area == &S7ClientBase::S7AreaMK ) { #flags
  453. $datastart =
  454. $self->{S5flagsAddress}; #need to get this information from a property
  455. }
  456. elsif ( $area == &S7ClientBase::S7AreaTM ) { #timers
  457. $datastart =
  458. $self->{S5timerAddress}; #need to get this information from a property
  459. }
  460. elsif ( $area == &S7ClientBase::S7AreaCT ) { #counters
  461. $datastart = $self
  462. ->{S5counterAddress}; #need to get this information from a property
  463. }
  464. else {
  465. main::Log3( undef, 3,
  466. "S5Client S5ReadS5Bytes Unknown area in ReadS5Bytes request" );
  467. return ( -1, "" );
  468. }
  469. if ( $count > &daveMaxRawLen ) {
  470. main::Log3( undef, 3,
  471. "S5Client S5ReadS5Bytes: Requested data is out-of-range" );
  472. return ( -1, "" );
  473. }
  474. $datastart += $offset;
  475. $dataend = $datastart + $count - 1;
  476. $b1 = pack( "C*",
  477. $datastart / 256,
  478. $datastart % 256,
  479. $dataend / 256,
  480. $dataend % 256 );
  481. ( $res, $msgIn ) = $self->S5ExchangeAS511( $b1, 4, 2 * $count + 7, 0x04 );
  482. if ( $res < 0 ) {
  483. main::Log3( undef, 3,
  484. "S5Client S5ReadS5Bytes Error in ReadS5Bytes.Exchange sequence" );
  485. return ( $res - 10, "" );
  486. }
  487. #if (dc->AnswLen<count+7) { #todo implement this check
  488. # LOG3("%s *** Too few chars (%d) in ReadS5Bytes data.\n", dc->iface->name,dc->AnswLen);
  489. #return (-5,"");
  490. #}
  491. my @cbuffer = unpack( "C" x length($msgIn), $msgIn );
  492. if ( ( $cbuffer[0] != 0 )
  493. || ( $cbuffer[1] != 0 )
  494. || ( $cbuffer[2] != 0 )
  495. || ( $cbuffer[3] != 0 )
  496. || ( $cbuffer[4] != 0 ) )
  497. {
  498. main::Log3( undef, 3,
  499. "S5Client S5ReadS5Bytes Wrong ReadS5Bytes data signature" );
  500. return ( -6, "" );
  501. }
  502. $msgIn = substr( $msgIn, 5, -2 );
  503. return ( 0, $msgIn );
  504. }
  505. #
  506. # Requests physical addresses and lengths of blocks in PLC memory and writes
  507. # them to ai structure:
  508. #
  509. sub S5ReadS5BlockAddress($$$) {
  510. my ( $self, $area, $BlockN ) = @_;
  511. my ( $res, $msgIn, $dbaddr, $dblen, $ai );
  512. my $b1 = pack( "C*", &daveS5BlockType_DB, $BlockN )
  513. ; #note we only support DB, no PB,FB,SB
  514. ( $res, $msgIn ) = $self->S5ExchangeAS511( $b1, 2, 24, 0x1A );
  515. if ( $res < 0 ) {
  516. main::Log3( undef, 3,
  517. "S5Client >S5ReadS5BlockAddress Error in BlockAddr.Exchange sequense"
  518. );
  519. return ( $res - 10, 0, 0 );
  520. }
  521. if ( length($msgIn) < 15 ) {
  522. main::Log3( undef, 3,
  523. "S5Client S5ReadS5BlockAddress Too few chars in BlockAddr data." );
  524. return ( -2, 0, 0 );
  525. }
  526. my @cbuffer = unpack( "C" x length($msgIn), $msgIn );
  527. if ( ( $cbuffer[0] != 0 )
  528. || ( $cbuffer[3] != 0x70 )
  529. || ( $cbuffer[4] != 0x70 )
  530. || ( $cbuffer[5] != 0x40 + &daveS5BlockType_DB )
  531. || ( $cbuffer[6] != $BlockN ) )
  532. {
  533. main::Log3( undef, 3,
  534. "S5Client S5ReadS5BlockAddress Wrong BlockAddr data signature." );
  535. return ( -3, 0, 0 );
  536. }
  537. $dbaddr = $cbuffer[1];
  538. $dbaddr =
  539. $dbaddr * 256 +
  540. $cbuffer[2]; #Let make shift operations to compiler's optimizer
  541. $dblen = $cbuffer[11];
  542. $dblen =
  543. ( $dblen * 256 + $cbuffer[12] - 5 ) *
  544. 2; #PLC returns dblen in words including
  545. #5 word header (but returnes the
  546. #start address after the header) so
  547. #dblen is length of block body
  548. return ( 0, $dbaddr, $dblen );
  549. }
  550. #
  551. # Writes <count> bytes from area <BlockN> with offset <offset> from buf.
  552. # You can't write data to the program blocks because you can't syncronize
  553. # with PLC cycle. For this purposes use daveWriteBlock:
  554. #
  555. sub S5WriteS5Bytes($$$$$$) {
  556. my ( $self, $area, $BlockN, $offset, $count, $buf ) = @_;
  557. my ( $res, $datastart, $dblen, $b1, $msgIn );
  558. if ( $area == &S7ClientBase::S7AreaDB ) { #DB
  559. ( $res, $datastart, $dblen ) =
  560. $self->S5ReadS5BlockAddress( $area, $BlockN );
  561. if ( $res < 0 ) {
  562. main::Log3( undef, 3,
  563. "S5Client S5WriteS5Bytes Error in ReadS5Bytes.BlockAddr request."
  564. );
  565. return $res - 50;
  566. }
  567. }
  568. elsif ( $area == &S7ClientBase::S7AreaPE ) { #inputs
  569. $datastart =
  570. $self->{S5PAEAddress}; #need to get this information from a property
  571. $dblen = 128;
  572. }
  573. elsif ( $area == &S7ClientBase::S7AreaPA ) { #outputs
  574. $datastart =
  575. $self->{S5PAAAddress}; #need to get this information from a property
  576. $dblen = 128;
  577. }
  578. elsif ( $area == &S7ClientBase::S7AreaMK ) { #flags
  579. $datastart =
  580. $self->{S5flagsAddress}; #need to get this information from a property
  581. #$dblen = 128; # S5-90U
  582. $dblen = 256; # S5-95U
  583. }
  584. elsif ( $area == &S7ClientBase::S7AreaTM ) { #timers
  585. $datastart =
  586. $self->{S5timerAddress}; #need to get this information from a property
  587. #$dblen = 32 *2; # S5-90U
  588. $dblen = 128 *2; # S5-95U
  589. }
  590. elsif ( $area == &S7ClientBase::S7AreaCT ) { #counters
  591. $datastart = $self
  592. ->{S5counterAddress}; #need to get this information from a property
  593. #$dblen = 32 *2; # S5-90U
  594. $dblen = 128 * 2; # S5-95U
  595. }
  596. else {
  597. main::Log3( undef, 3,
  598. "S5Client S5WriteS5Bytes Unknown area in WriteS5Bytes request." );
  599. return -1;
  600. }
  601. if ( ( $count > &daveMaxRawLen ) || ( $offset + $count > $dblen ) ) {
  602. main::Log3( undef, 3,
  603. "S5Client S5WriteS5Bytes Requested data is out-of-range." );
  604. return -1;
  605. }
  606. #LOG2("area start is %04x, ",datastart);
  607. $datastart += $offset;
  608. #LOG2("data start is %04x\n",datastart);
  609. $b1 = pack( "C*", $datastart / 256, $datastart % 256 );
  610. $b1 = $b1 . $buf;
  611. ( $res, $msgIn ) = $self->S5ExchangeAS511( $b1, 2 + $count, 0, 0x03 );
  612. if ( $res < 0 ) {
  613. main::Log3( undef, 3,
  614. "S5Client S5WriteS5Bytes Error in WriteS5Bytes.Exchange sequense."
  615. );
  616. return $res - 10;
  617. }
  618. return 0;
  619. }
  620. 1;
  621. =pod
  622. =item summary low level interface to S5
  623. =item summary_DE low level interface to S5
  624. =begin html
  625. <p><a name="S7_S5Client"></a></p>
  626. <h3>S7_S5Client</h3>
  627. <ul>
  628. <ul>low level interface to S5</ul>
  629. </ul>
  630. =end html
  631. =begin html_DE
  632. <p><a name="S7_S5Client"></a></p>
  633. <h3>S7_S5Client</h3>
  634. <ul>
  635. <ul>low level interface to S5</ul>
  636. </ul>
  637. =end html_DE
  638. =cut