iop_modbus.cpp
Go to the documentation of this file.
1 /** @file iop_modbus.cpp Process image via modbus/tcp */
2 
3 /*
4  FAU Discrete Event Systems Library (libfaudes)
5 
6  Copyright (C) 2011, Thomas Moor.
7 
8 */
9 
10 
11 // include header
12 #include "iop_modbus.h"
13 
14 // only compile for use with spi configured
15 #ifdef FAUDES_IODEVICE_MODBUS
16 
17 
18 
19 namespace faudes {
20 
21 
22 
23 /*
24  **********************************************
25  **********************************************
26  **********************************************
27 
28  implementation: mbDevice
29 
30  **********************************************
31  **********************************************
32  **********************************************
33  */
34 
35 
36 // std faudes, incl dummy
37 FAUDES_TYPE_IMPLEMENTATION(ModbusDevice,mbDevice,sDevice)
38 
39 // autoregister (not functional, see xdevice constructor)
41 
42 //constructor
44  FD_DHV("mbDevice(" << mName << ")::mbDevice()");
45  // have appropriate default label for token io
46  mDefaultLabel = "ModbusDevice";
47  // pointer to internal I/O-image
48  mpImage=0;
49  pOutputImage=0;
50  pInputImage=0;
51  mpOutputMask=0;
52  // modbus/tcp io buffers
53  mMessage= new char[260];
54  // behavioural defaults
55  mMasterRole=true;
56  mSlaveAddress.IpColonPort("localhost:502");
57  mSyncWrite=true;
58 }
59 
60 //deconstructor
62  FD_DHV("mbDevice(" << mName << ")::~mbDevice()");
63  Stop();
64  // must wait for thread to terminate
65  while(Status()!=Down);
66  // free buffers
67  if(mpImage) delete mpImage;
68  if(mpOutputMask) delete mpOutputMask;
69  delete mMessage;
70 }
71 
72 // Clear
73 void mbDevice::Clear(void) {
74  // clear base
76  // my configuration
77  mSlaveIoRanges.clear();
78  mSlaveAddress.IpColonPort("localhost:502");
79  mSyncWrite=true;
80 }
81 
82 
83 //Compile(void)
84 void mbDevice::Compile(void){
85  //setup up internal data structure
86  FD_DHV("mbDevice(" << mName << ")::Compile()");
87  // call base
89  // implement consistency tests here
91  for(unsigned int i=0; i< mSlaveIoRanges.size(); i++) {
92  const IoRange& ior=mSlaveIoRanges.at(i);
93  if(ior.mFdAddress + ior.mCount > mImageSize)
94  mImageSize=ior.mFdAddress + ior.mCount;
95  if(ior.mCount>2000) {
96  std::stringstream errstr;
97  errstr << "image size must not exceed 2000 bits (image #" << i+1 << ")";
98  throw Exception("mbDevice:Read", errstr.str(), 52);
99  }
100  }
101  mImageSize = ( (mImageSize-1) / 16 ) *16 + 16;
102  if(mImageSize<=0) mImageSize=16;
103  /*
104  if(mImageSize > mMaxBitAddress+1) fail=true;
105  if(fail) {
106  std::stringstream errstr;
107  errstr << "Invalid remote address ranges";
108  throw Exception("mbDevice:Compile()", errstr.str(), 52);
109  }
110  */
111  //
112  if(!mMasterRole) if(mSlaveAddress.Ip()!="localhost") {
113  std::stringstream errstr;
114  errstr << "Slave must have localhost as SlaveAddress";
115  throw Exception("mbDevice:Compile()", errstr.str(), 52);
116  }
117  // (re-)initialize images
118  if(mpImage) delete mpImage;
119  mpImage = new char[mImageSize];
120  memset(mpImage,0,mImageSize);
123  // initialize output mask
124  mpOutputMask = new char[mImageSize];
125  memset(mpOutputMask,0,mImageSize);
126  for(int bit=0; bit< mImageSize; bit++)
127  if(!mOutputLevelIndexMap[bit].Empty())
128  mpOutputMask[bit]=1;
129  // debug
130  //Write();
131 }
132 
133 
134 // programmatic config: append remote image
135 void mbDevice::AppendRemoteInputs(int mbid, int mbaddr, int cnt, int fdaddr) {
136  IoRange ior;
137  ior.mInputs=true;
138  ior.mMbId=mbid;
139  ior.mMbAddress=mbaddr;
140  ior.mCount=cnt;
141  ior.mFdAddress=fdaddr;
142  mSlaveIoRanges.push_back(ior);
143 }
144 
145 // programmatic config: append remote image
146 void mbDevice::AppendRemoteOutputs(int mbid, int mbaddr, int cnt, int fdaddr) {
147  IoRange ior;
148  ior.mInputs=false;
149  ior.mMbId=mbid;
150  ior.mMbAddress=mbaddr;
151  ior.mCount=cnt;
152  ior.mFdAddress=fdaddr;
153  mSlaveIoRanges.push_back(ior);
154 }
155 
156 // programmatic config: slave address
157 void mbDevice::SlaveAddress(const std::string& rAddr) {
158  if(mState!=Down) return;
159  mSlaveAddress.IpColonPort(rAddr);
160 }
161 
162 //DoWrite(rTr,rLabel,pContext)
163 void mbDevice::DoWritePreface(TokenWriter& rTw, const std::string& rLabel, const Type* pContext) const {
164  FD_DHV("mbDevice("<<mName<<")::DoWritePreface()");
165  //call base
166  sDevice::DoWritePreface(rTw,"",pContext);
167  // role
168  Token stoken;
169  stoken.SetEmpty("Role");
170  if(mMasterRole) {
171  stoken.InsAttributeString("value","master");
172  } else {
173  stoken.InsAttributeString("value","slave");
174  }
175  rTw << stoken;
176  // slave address
177  Token ftoken;
178  ftoken.SetEmpty("SlaveAddress");
179  ftoken.InsAttributeString("value",mSlaveAddress.IpColonPort());
180  rTw << ftoken;
181  // ranges
182  rTw.WriteBegin("RemoteImage");
183  for(unsigned int i=0; i<mSlaveIoRanges.size(); i++) {
184  Token rtoken;
185  if(mSlaveIoRanges.at(i).mInputs)
186  rtoken.SetEmpty("Inputs");
187  else
188  rtoken.SetEmpty("Outputs");
189  if(mSlaveIoRanges.at(i).mMbId!=1)
190  rtoken.InsAttributeInteger("mbid",mSlaveIoRanges.at(i).mMbId);
191  rtoken.InsAttributeInteger("mbaddr",mSlaveIoRanges.at(i).mMbAddress);
192  if(mSlaveIoRanges.at(i).mFdAddress!=mSlaveIoRanges.at(i).mMbAddress)
193  rtoken.InsAttributeInteger("fdaddr",mSlaveIoRanges.at(i).mFdAddress);
194  rtoken.InsAttributeInteger("count",mSlaveIoRanges.at(i).mCount);
195  rTw << rtoken;
196  }
197  rTw.WriteEnd("RemoteImage");
198 }
199 
200 
201 //DoReadPreface(rTr,rLabel,pContext)
202 void mbDevice::DoReadPreface(TokenReader& rTr,const std::string& rLabel, const Type* pContext){
203  //dummy for token-input
204  FD_DHV("mbDevice("<<mName<<")::DoReadPreface()");
205  //call base
206  sDevice::DoReadPreface(rTr,"",pContext);
207  // my global configuration
208  Token token;
209  while(rTr.Peek(token)) {
210  // role
211  if(token.IsBegin("Role")) {
212  rTr.ReadBegin("Role");
213  mMasterRole=true;
214  if(!token.ExistsAttributeString("value")) {
215  std::stringstream errstr;
216  errstr << "Invalid role tag, value attribute missing" << rTr.FileLine();
217  throw Exception("mpiDevice:Read", errstr.str(), 52);
218  }
219  std::string val=token.AttributeStringValue("value");
220  std::transform(val.begin(), val.end(), val.begin(), tolower);
221  if(val=="master") mMasterRole =true;
222  else if(val=="slave") mMasterRole =false;
223  else {
224  std::stringstream errstr;
225  errstr << "Invalid value attribute in role tag" << rTr.FileLine();
226  throw Exception("spiDevice:Read", errstr.str(), 52);
227  }
228  rTr.ReadEnd("Role");
229  continue;
230  }
231  // address
232  if(token.IsBegin("SlaveAddress")) {
233  FD_DHV("mBDevice::DoRead(): found slave address");
234  rTr.ReadBegin("SlaveAddress");
235  if(!token.ExistsAttributeString("value")) {
236  std::stringstream errstr;
237  errstr << "Invalid ip address" << rTr.FileLine();
238  throw Exception("mbDevice:Read", errstr.str(), 52);
239  }
241  rTr.ReadEnd("SlaveAddress");
242  continue;
243  }
244  // process image
245  if(token.IsBegin("RemoteImage")) {
246  rTr.ReadBegin("RemoteImage");
247  while(!rTr.Eos("RemoteImage")) {
248  Token rtoken;
249  rTr.Get(rtoken);
250  if(!rtoken.IsBegin("Inputs"))
251  if(!rtoken.IsBegin("Outputs")) {
252  std::stringstream errstr;
253  errstr << "invalid io range" << rTr.FileLine();
254  throw Exception("mbDevice:Read", errstr.str(), 52);
255  }
256  IoRange iorange;
257  iorange.mMbId=1;
258  iorange.mFdAddress=-1;
259  iorange.mInputs = rtoken.IsBegin("Inputs");
260  if(rtoken.ExistsAttributeInteger("mbid"))
261  iorange.mMbId=rtoken.AttributeIntegerValue("mbid");
262  if(!rtoken.ExistsAttributeInteger("mbaddr")) {
263  std::stringstream errstr;
264  errstr << "missing remote address" << rTr.FileLine();
265  throw Exception("mbDevice:Read", errstr.str(), 52);
266  }
267  iorange.mMbAddress=rtoken.AttributeIntegerValue("mbaddr");
268  if(rtoken.ExistsAttributeInteger("fdaddr"))
269  iorange.mFdAddress=rtoken.AttributeIntegerValue("fdaddr");
270  if(iorange.mFdAddress<0) iorange.mFdAddress=iorange.mMbAddress;
271  iorange.mCount=1;
272  if(rtoken.ExistsAttributeInteger("count"))
273  iorange.mCount=rtoken.AttributeIntegerValue("count");
274  rTr.ReadEnd(rtoken.StringValue());
275  if(iorange.mCount>2000) {
276  std::stringstream errstr;
277  errstr << "image size must not exceed 2000 bits" << rTr.FileLine();
278  throw Exception("mbDevice:Read", errstr.str(), 52);
279  }
280  mSlaveIoRanges.push_back(iorange);
281  }
282  rTr.ReadEnd("RemoteImage");
283  continue;
284  }
285  // unknown: break
286  break;
287  }
288 }
289 
290 
291 // Start(void)
292 void mbDevice::Start(void) {
293  // bail out
294  if(mState!=Down) return;
295  // initialize modbus/tcp io data
296  mSlaveSocket=-1;
297  mRequestCount=1;
298  // as a slave, we listen for masters to connect
299  if(!mMasterRole) {
300  // clear connected masters
301  mMasterSockets.clear();
302  // open a tcp port to listen: create socket
303  mSlaveSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
304  if(mSlaveSocket<=0) {
305  std::stringstream errstr;
306  errstr << "Modbus fatal network error (cannot open server socket)";
307  throw Exception("mbDevice::Start", errstr.str(), 553);
308  }
309  int reuse=1;
310  faudes_setsockopt(mSlaveSocket,SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
311  // open a tcp port to listen: set up address
312  struct sockaddr_in slaveaddress;
313  memset(&slaveaddress, 0, sizeof(slaveaddress));
314  slaveaddress.sin_family = AF_INET;
315  slaveaddress.sin_addr.s_addr = htonl(INADDR_ANY);
316  slaveaddress.sin_port = htons(mSlaveAddress.Port());
317  // open a tcp port to listen: bind socket to address
318  if(bind(mSlaveSocket, (struct sockaddr *) &slaveaddress,sizeof(slaveaddress)) <0) {
319  std::stringstream errstr;
320  errstr << "Modbus fatal network error (cannot bind socket)";
321  throw Exception("nDevice::Start", errstr.str(), 553);
322  }
323  // open a tcp port to listen: start to listen
324  if(listen(mSlaveSocket, 77) < 0) { // todo: max pending connections
325  std::stringstream errstr;
326  errstr << "Simplenet fatal network error (cannot listen from socket)";
327  throw Exception("mbDevice::Start", errstr.str(), 553);
328  }
329  }
330  // call base (incl. reset)
331  sDevice::Start();
332  // pessimistic master: let background thread figure presence of the slave
334 }
335 
336 // Stop()
337 void mbDevice::Stop(void) {
338  //close serial interface
339  if(mState != Up && mState != StartUp) return;
340  FD_DHV("mbDevice(" << mName << ")::Stop()");
341  // call base (this will join the thread)
342  sDevice::Stop();
343  // close connections to masters (effective only when we are slave)
344  for(unsigned int i=0; i<mMasterSockets.size(); i++) {
345  if(mMasterSockets.at(i)>0) {
346  FD_DH("mbDevice::Stop(): closing master socket #" << mMasterSockets.at(i));
347  faudes_closesocket(mMasterSockets.at(i));
348  }
349  }
350  mMasterSockets.clear();
351  // close my socket
352  if(mSlaveSocket>0) {
353  FD_DH("mbDevice::Stop(): closing slave socket");
354  faudes_closesocket(mSlaveSocket);
355  }
356  mSlaveSocket=-1;
357  // done
358  FD_DH("mbDevice::Stop(): done");
359 }
360 
361 
362 // modbus access macros
363 #define MB_PDUOFF 7
364 #define MB_SETINT(p,v) { mMessage[p] = ((v)>>8); mMessage[p+1] = ((v) & 0xff); }
365 #define MB_GETINT(p) ( ( mMessage[p] << 8) + ((int) mMessage[p+1]) )
366 #define MB_SETBYTE(p,v) { mMessage[p] = (v);}
367 #define MB_GETBYTE(p) ( mMessage[p] )
368 
369 
370 
371 // helper: flush buffers
373 // flush recv buffer
374  while(1) {
375  struct timeval tv;
376  tv.tv_sec = 0;
377  tv.tv_usec = 0;
378  fd_set mysocks;
379  FD_ZERO(&mysocks);
380  FD_SET(mSlaveSocket, &mysocks);
381  int avail= select(mSlaveSocket+1, &mysocks, NULL, NULL, &tv);
382  if(avail<=0) break;
383  if(!FD_ISSET(mSlaveSocket,&mysocks)) break;
384  FD_DH("mbDevice::MbFlushBuffers(): flush recv buffer");
385  char data;
386  int rc = recv(mSlaveSocket, &data, 1, 0);
387  if(rc==1) continue;
388  FD_DH("mbDevice::MbFlushBuffers(): flush recv buffer: fatal error?");
389  return -1;
390  }
391  FD_DHV("mbDevice::MbFlushBuffers(): ok");
392  return 0;
393 }
394 
395 // helper: send modbus request
397  // flush buffers
398  if(MbFlushBuffers()!=0) return -1;
399  // setup mbab header
400  mRequestCount++;
402  MB_SETINT(2,0);
403  MB_SETINT(4,mMessageLen+1);
404  MB_SETBYTE(6,id);
405  // sync send
406  int from=0;
407  int left=mMessageLen+MB_PDUOFF;
408  while(left>0) {
409  int rc=send(mSlaveSocket, mMessage+from, left, 0);
410  if(rc<0) return rc;
411  left-=rc;
412  from+=rc;
413  }
414  return 0;
415 }
416 
417 
418 // helper: receive modbus response
420  // prepare relevant source
421  fd_set mysocks;
422  int mysocks_max=0;
423  FD_ZERO(&mysocks);
424  FD_SET(mSlaveSocket, &mysocks);
425  if(mysocks_max< mSlaveSocket) mysocks_max=mSlaveSocket;
426  // set moderate timeout
427  struct timeval tv;
428  tv.tv_sec = 0;
429  tv.tv_usec = 500000;
430  // wait for message
431  int avail=select(mysocks_max+1, &mysocks, NULL, NULL, &tv);
432  // read availabe data (expect only one packet)
433  mMessageLen=0;
434  if(avail<=0) return -1;
435  if(!FD_ISSET(mSlaveSocket,&mysocks)) return -1;
436  mMessageLen = recv(mSlaveSocket, mMessage, 260, 0);
437  if(mMessageLen<7) return -1;
438  int mbablen = MB_GETINT(4);
439  if(mbablen+6 != mMessageLen) {
440  FD_DH("mbDevice::MbReceiveResponse(): invalid MBAB header (size mismatch)");
441  return -1;
442  }
443  return 0;
444 }
445 
446 
447 // helper: receive request
448 int mbDevice::MbReceiveRequest(int mastersock) {
449  // read availabe data (expect only one packet)
450  mMessageLen = recv(mastersock, mMessage, 260, 0);
451  // test Modbus compliance
452  if(mMessageLen<7) return -1; // perhaps connection closed
453  int mbablen = MB_GETINT(4);
454  if(mbablen+6 != mMessageLen) {
455  FD_DH("mbDevice::MbReceiveRequest(): invalid MBAB header (size mismatch)");
456  return -1;
457  }
458  // set net length
460  return 0;
461 }
462 
463 // helper: send modbus request
464 int mbDevice::MbSendResponse(int mastersock) {
465  // update mbab header
466  MB_SETINT(4,mMessageLen+1);
467  // sync send
468  int from=0;
469  int left=mMessageLen+MB_PDUOFF;
470  while(left>0) {
471  int rc=send(mastersock, mMessage+from, left, 0);
472  if(rc<0) return rc;
473  left-=rc;
474  from+=rc;
475  }
476  return 0;
477 }
478 
479 // loopcall-back for serial comminucation
481 
482  // master role: try to connect to remote slave
483 
484  if((mMasterRole) && (mState==StartUp) && (mSlaveSocket<0)) {
485 
486  // dont congest network / dont mess up console
487  static int ctimer=0;
488  ctimer+=CycleTime();
489  if(ctimer< 1000000) return;
490  ctimer=0;
491  // report
492  FD_DH("mbDevice::DoLoopCallBack(): connecting to remote slave " << mSlaveAddress.IpColonPort());
493  // open a tcp port: create socket
494  int slavesock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
495  if(slavesock<=0) {
496  FD_DH("mbDevice::DoLoopCallBack(): connection to slave failed: internal err A0");
497  return;
498  }
499  // open a tcp port: set up internet address
500  unsigned long int slaveinaddr = INADDR_NONE;
501  if(slaveinaddr==INADDR_NONE) {
502  FD_DHV("mbDevice::DoLoopCallBack(): using provided address literaly");
503  slaveinaddr = inet_addr(mSlaveAddress.Ip().c_str());
504  }
505  if(slaveinaddr==INADDR_NONE) {
506  struct hostent *host;
507  host = gethostbyname(mSlaveAddress.Ip().c_str());
508  if(host!=0) {
509  FD_DHV("mbDevice::DoLooCallBack(): retrieve alternative address by name-lookup");
510  slaveinaddr = *(unsigned long int*) host->h_addr;
511  }
512  }
513  if(slaveinaddr==INADDR_NONE) {
514  FD_DH("mbDevice::DoLooCallBack():: connection to slave failed: invalid address " << mSlaveAddress.Ip());
515  faudes_closesocket(slavesock);
516  return;
517  }
518  // open a tcp port: set up socket address
519  struct sockaddr_in slaveaddress;
520  memset(&slaveaddress, 0, sizeof(slaveaddress));
521  slaveaddress.sin_family = AF_INET;
522  slaveaddress.sin_addr.s_addr=slaveinaddr;
523  slaveaddress.sin_port = htons(mSlaveAddress.Port());
524  // make my socket nonblocking
525  int rc = faudes_setsocket_nonblocking(slavesock, true);
526  if(rc<0) {
527  FD_DH("mbDevice::DoLoopCallBack():: connection to slave failed: internal error A1");
528  faudes_closesocket(slavesock);
529  return;
530  }
531  // try to connect
532  int rcc= connect(slavesock, (struct sockaddr*) &slaveaddress, sizeof(slaveaddress));
533  // wait for host to accept
534 #ifdef FAUDES_POSIX
535  if(rcc<0) {
536  if(errno!=EINPROGRESS) {
537  FD_DH("mbDevice::DoLoopCallBack(): connection to slave failed: connect() errno " << errno);
538  } else {
539  FD_DH("mbDevice::DoLoopCallBack(): connection to slave: wait for host to accept");
540  // sense success via select befor timeout
541  struct timeval tv;
542  tv.tv_sec = 0;
543  tv.tv_usec = 500000;
544  fd_set mysocks;
545  FD_ZERO(&mysocks);
546  FD_SET(slavesock, &mysocks);
547  rcc= select(slavesock+1, NULL, &mysocks, NULL, &tv);
548  rcc--; // map 1 to no err aka 0 ;-)
549  if(rcc<0) FD_DH("mbDevice::DoLoopCallBack(): connection to slave failed: timeout");
550  }
551  }
552 #endif
553 #ifdef FAUDES_WINDOWS
554  if(rcc<0) {
555  int lerr = WSAGetLastError();
556  if(lerr!=WSAEWOULDBLOCK) {
557  FD_DH("mbDevice::DoLoopCallBack(): connection to slave failed: connect() errno " << lerr);
558  } else {
559  FD_DH("mbDevice::DoLoopCallBack(): wait for host to accept");
560  // sense success via select befor timeout
561  struct timeval tv;
562  tv.tv_sec = 0;
563  tv.tv_usec = 500000;
564  fd_set mysocks;
565  FD_ZERO(&mysocks);
566  FD_SET(slavesock, &mysocks);
567  rcc= select(slavesock+1, NULL, &mysocks, NULL, &tv);
568  rcc--; // map 1 to no err aka 0 ;-)
569  if(rcc<0) FD_DH("mbDevice::DoLoopCallBack(): connection to slave failed: timeout");
570  }
571  }
572 #endif
573  // connection failed
574  if(rcc<0) {
575  FD_DH("mbDevice::DoLoopCallBack():: connection to slave failed: host unreachable");
576  faudes_closesocket(slavesock);
577  return;
578  }
579  // sense errors on socket level
580  if(faudes_getsocket_error(slavesock)<0) {
581  FD_DH("mbDevice::DoLoopCallBack():: connection to slave failed: internal error A2");
582  faudes_closesocket(slavesock);
583  return;
584  }
585  // restore blocking socket
586  rc = faudes_setsocket_nonblocking(slavesock, false);
587  if(rc<0) {
588  FD_DH("mbDevice::DoLoopCallBack():: connection to slave failed: internal error A3");
589  faudes_closesocket(slavesock);
590  return;
591  }
592  // record success
593  FD_DH("mbDevice::DoLoopCallBack(): connected to remote slave, using socket #" << slavesock);
594  mSlaveSocket=slavesock;
595  }
596 
597 
598  // slave role: accept remote master connection
599 
600  if((!mMasterRole) && (mState==Up)) {
601 
602  // sense connection requests
603  struct timeval tv;
604  tv.tv_sec = 0;
605  tv.tv_usec = 0;
606  fd_set mysocks;
607  FD_ZERO(&mysocks);
608  FD_SET(mSlaveSocket,&mysocks);
609  int avail = select(mSlaveSocket+1, &mysocks, NULL, NULL, &tv);
610 
611  // accept
612  if(avail==1)
613  if(FD_ISSET(mSlaveSocket,&mysocks)) {
614  FD_DH("mbDevice::DoLoopCallBack(): accepting remote master to connect");
615  struct sockaddr_in masteraddr;
616  socklen_t masteraddr_len = sizeof(masteraddr);
617  int mastersock=accept(mSlaveSocket, (struct sockaddr *) &masteraddr, &masteraddr_len );
618  if(mastersock<0) {
619  FD_DH("mbDevice::DoLoopCallback(): failed to accept incomming connection");
620  } else {
621  mMasterSockets.push_back(mastersock);
622  }
623  }
624 
625  }
626 
627 
628  // master role: sync image with remote slave
629 
630  if((mMasterRole) && (mSlaveSocket>0) && (mState!=Down) && (mState!=ShutDown) ) {
631  FD_DHV("mbDevice::DoLooCallBack(): update image from remote slave");
632  // read all inputs
633  for(unsigned int i=0; i<mSlaveIoRanges.size(); i++) {
634  const IoRange& ior=mSlaveIoRanges.at(i);
635  if(!ior.mInputs) continue;
636  // assemble request<
637  MB_SETBYTE(MB_PDUOFF,0x02); // read multiple digital inputs
639  MB_SETINT(MB_PDUOFF+3,ior.mCount);
640  mMessageLen=5;
641  // send request
642  FD_DHV("mbDevice::DoLoopCallBack(): sending request");
643  if(MbSendRequest(ior.mMbId)!=0) {mState=StartUp; faudes_closesocket(mSlaveSocket); mSlaveSocket=-1; return;};
644  FD_DHV("mbDevice::DoLoopCallBack(): read response");
645  if(MbReceiveResponse()!=0) {mState=StartUp; faudes_closesocket(mSlaveSocket); mSlaveSocket=-1; return;};
646  FD_DHV("mbDevice::DoLoopCallBack(): received responde #" << mMessageLen);
647  // interpret response
648  if(MB_GETBYTE(MB_PDUOFF)==0x02) { // no error
649  FD_DHV("mbDevice::DoLoopCallBack(): input image received");
650  int count=MB_GETBYTE(MB_PDUOFF+1); // todo: verify
651  count=ior.mCount;
652  int src=MB_PDUOFF+2;
653  int data=MB_GETBYTE(src);
654  int addr=ior.mFdAddress;
655  int shft=0x01;
656  while(count) {
657  if(!mpOutputMask[addr]) mpImage[addr]= (( data & shft) != 0);
658  addr++; count--; shft = shft <<1;
659  if(shft==0x100) { shft=0x01; data=MB_GETBYTE(++src);};
660  }
661  } else {
662  FD_DH("mbDevice::DoLoopCallBack(): received error response on read inputs");
663  }
664  }
665  // write all outputs
666  for(unsigned int i=0; i<mSlaveIoRanges.size(); i++) {
667  const IoRange& ior=mSlaveIoRanges.at(i);
668  if(ior.mInputs) continue;
669  // assemble request
670  MB_SETBYTE(MB_PDUOFF,0x0f); // write multiple coils
672  MB_SETINT(MB_PDUOFF+3,ior.mCount);
673  int bcount= (ior.mCount-1)/8 +1;
674  MB_SETBYTE(MB_PDUOFF+5,bcount);
675  int dst=MB_PDUOFF+6;
676  int data=0x00;
677  int shft=0x01;
678  int addr=ior.mFdAddress;
679  int count=ior.mCount;
680  while(count) {
681  if(mpImage[addr]) data |= shft;
682  addr++; count--; shft = shft <<1;
683  if(shft==0x100) { shft=0x01; MB_SETBYTE(dst,data); data=0x00; dst++;}
684  }
685  if(shft!=0x01) { MB_SETBYTE(dst,data);};
686  mMessageLen=6+bcount;
687  // send request
688  FD_DHV("mbDevice::DoLoopCallBack(): sending request");
689  if(MbSendRequest(ior.mMbId)!=0) {mState=StartUp; faudes_closesocket(mSlaveSocket); mSlaveSocket=-1; return;};
690  if(MbReceiveResponse()!=0) {mState=StartUp; faudes_closesocket(mSlaveSocket); mSlaveSocket=-1; return;};
691  if(MB_GETBYTE(MB_PDUOFF)!=0x0f) {
692  FD_DH("mbDevice::DoLoopCallBack(): received error response on write coils");
693  }
694  }
695  }
696 
697 
698  // slave role: sync image with remote masters
699 
700  if((!mMasterRole) && (mState==Up)) {
701 
702  // prepare relevant sources
703  fd_set mysocks;
704  int mysocks_max=0;
705  FD_ZERO(&mysocks);
706  for(unsigned int i=0; i< mMasterSockets.size(); i++) {
707  int mastersock=mMasterSockets.at(i);
708  if(mastersock<0) continue;
709  FD_SET(mastersock, &mysocks);
710  if(mysocks_max< mastersock) mysocks_max=mastersock;
711  }
712 
713  // sense requests
714  struct timeval tv;
715  tv.tv_sec = 0;
716  tv.tv_usec = 0;
717  int avail=select(mysocks_max+1, &mysocks, NULL, NULL, &tv);
718 
719  // loop master requests
720  if(avail>0)
721  for(unsigned int i=0; i< mMasterSockets.size(); i++) {
722  int mastersock=mMasterSockets.at(i);
723  if(mastersock<0) continue;
724  if(!FD_ISSET(mastersock, &mysocks)) continue;
725  FD_DHV("mbDevice::DoLoopCallback(): received message on sock " << mastersock);
726  if(MbReceiveRequest(mastersock)<0) {
727  FD_DH("mbDevice::DoLoopCallback(): receive error on sock " << mastersock);
728  faudes_closesocket(mastersock);
729  mMasterSockets.at(i)=-1; // todo: remove
730  continue;
731  }
732 
733  // interpret request
734  int fnct=MB_GETBYTE(MB_PDUOFF);
735  int errcode = 0x01;
736  // read inputs or coils
737  if((fnct==0x01) || (fnct==0x02)) {
738  FD_DHV("mbDevice::DoLoopCallback(): coil-read or input read request");
739  int addr = MB_GETINT(MB_PDUOFF+1);
740  int count = MB_GETINT(MB_PDUOFF+3);
741  int bcount= ((count-1)/8+1);
742  FD_DHV("mbDevice::DoLoopCallback(): address range: @" << addr << " #" << count);
743  // test validity
744  errcode=0x00;
745  if(addr+count>mImageSize) errcode=0x02;
746  if(count>2000) errcode=0x02;
747  // perform
748  if(errcode==0x00) {
749  // fill in bits
750  int dst=MB_PDUOFF+2;
751  int data=0x00;
752  int shft=0x01;
753  while(count) {
754  if(mpImage[addr]) data |= shft;
755  addr++; count--; shft = shft <<1;
756  if(shft==0x100) { shft=0x01; MB_SETBYTE(dst,data); dst++; data=0x00;}
757  }
758  if(shft!=0x01) { MB_SETBYTE(dst++,data);};
759  MB_SETBYTE(MB_PDUOFF+1,bcount);
760  // set nessage length
761  mMessageLen=bcount+2;
762  }
763  }
764  // read input registers or holding registers
765  if((fnct==0x03) || (fnct==0x04)) {
766  FD_DHV("mbDevice::DoLoopCallback(): register or holding register read request");
767  int addr = MB_GETINT(MB_PDUOFF+1);
768  int count = MB_GETINT(MB_PDUOFF+3);
769  FD_DHV("mbDevice::DoLoopCallback(): address range: @" << addr << " #" << count);
770  // test validity
771  errcode=0x00;
772  if(16*addr+16*count>mImageSize)
773  errcode=0x02;
774  // perform
775  if(errcode==0x00) {
776  // set header length
777  mMessageLen=2*count+2;
778  MB_SETBYTE(MB_PDUOFF+1,2*count);
779  // fill in bits
780  int src= addr*16;
781  int dst=MB_PDUOFF+2;
782  for(;count>0; count--) {
783  int shft=0x01;
784  int lbyte=0x00;
785  for(;src<mImageSize && shft!=0x100; src++, shft = shft << 1)
786  if(mpImage[src]) lbyte |= shft;
787  shft=0x01;
788  int hbyte=0x00;
789  for(;src<mImageSize && shft!=0x100; src++, shft = shft << 1)
790  if(mpImage[src]) hbyte |= shft;
791  MB_SETBYTE(dst,hbyte); dst++;
792  MB_SETBYTE(dst,lbyte); dst++;
793  }
794  }
795  }
796  // write single coil
797  if(fnct==0x05) {
798  FD_DHV("mbDevice::DoLoopCallback(): write single coil request");
799  int addr = MB_GETINT(MB_PDUOFF+1);
800  bool val = ( ((unsigned char) MB_GETBYTE(MB_PDUOFF+3))==0xff);
801  FD_DHV("mbDevice::DoLoopCallback(): write single coil request: " << addr << " to " << val);
802  // test
803  errcode=0x00;
804  if(addr>=mImageSize) errcode=0x02;
805  // perform
806  if(errcode==0x00) {
807  if(mpOutputMask[addr]) mpImage[addr] = val;
808  mMessageLen=5;
809  }
810  }
811  // write single register
812  if(fnct==0x06) {
813  FD_DHV("mbDevice::DoLoopCallback(): write holding register request");
814  int addr = MB_GETINT(MB_PDUOFF+1);
815  int val = MB_GETINT(MB_PDUOFF+3);
816  FD_DHV("mbDevice::DoLoopCallback(): set @" << addr << " to " << val);
817  // test validity
818  errcode=0x00;
819  if(16*addr+16 >mImageSize)
820  errcode=0x02;
821  // perform
822  if(errcode==0x00) {
823  // extract bits
824  int dst=16*addr;
825  int hbyte= (val >> 8); // :-)
826  int lbyte= (val & 0xff);
827  int shft;
828  for(shft=0x01; shft!=0x100; shft = shft << 1, dst++)
829  mpImage[dst] = (( lbyte & shft) != 0);
830  for(shft=0x01; shft!=0x100; shft = shft << 1, dst++)
831  mpImage[dst] = (( hbyte & shft) != 0);
832  // setup reply
833  mMessageLen=5;
834  }
835  }
836  // write multiple coils
837  if(fnct==0x0f) {
838  FD_DHV("mbDevice::DoLoopCallback(): write multiple coils request");
839  int addr = MB_GETINT(MB_PDUOFF+1);
840  int count = MB_GETINT(MB_PDUOFF+3);
841  int bcount= MB_GETBYTE(MB_PDUOFF+5);
842  FD_DHV("mbDevice::DoLoopCallback(): address range: @" << addr << " #" << count << "(" << bcount << ")");
843  // test validity
844  errcode=0x00;
845  if(addr+count>mImageSize) errcode=0x02;
846  if( (bcount < ((count-1)/8+1)) || (mMessageLen < 6+bcount) ) errcode=0x03;
847  // perform
848  if(errcode==0x00) {
849  // extract bits
850  int src=MB_PDUOFF+6;
851  int data=0;
852  int shft=0x100;
853  while(count) {
854  if(shft==0x100) { shft=0x01; data=MB_GETBYTE(src);src++;};
855  if(!mpOutputMask[addr]) mpImage[addr]= (( data & shft) != 0);
856  addr++; count--; shft = shft <<1;
857  }
858  // setup reply
859  mMessageLen=5;
860  }
861  }
862  // write multiple holding registers
863  if(fnct==0x10) {
864  FD_DHV("mbDevice::DoLoopCallback(): write multiple holding registers request");
865  int addr = MB_GETINT(MB_PDUOFF+1);
866  int count = MB_GETINT(MB_PDUOFF+3);
867  int bcount= MB_GETBYTE(MB_PDUOFF+5);
868  FD_DHV("mbDevice::DoLoopCallback(): address range: @" << addr << " #" << count << "(" << bcount << ")");
869  // test validity
870  errcode=0x00;
871  if(16*addr+16*count>mImageSize)
872  errcode=0x02;
873  if( bcount != 2* count)
874  errcode=0x03;
875  // perform
876  if(errcode==0x00) {
877  // extract bits
878  int src=MB_PDUOFF+6;
879  int dst=16*addr;
880  for(;count>0;count--) {
881  int hbyte=MB_GETBYTE(src); src++;
882  int lbyte=MB_GETBYTE(src); src++;
883  int shft;
884  for(shft=0x01; shft!=0x100; shft = shft << 1, dst++)
885  mpImage[dst] = (( lbyte & shft) != 0);
886  for(shft=0x01; shft!=0x100; shft = shft << 1, dst++)
887  mpImage[dst] = (( hbyte & shft) != 0);
888  }
889  // setup reply
890  mMessageLen=5;
891  }
892  }
893  // send reply
894  if(errcode==0x00) {
895  FD_DHV("mbDevice::DoLoopCallback(): sending reply #" << mMessageLen);
896  MbSendResponse(mastersock);
897  }
898  // send error
899  if(errcode!=0x00) {
900  FD_DH("mbDevice::DoLoopCallback(): sending error reply, code " << errcode);
901  MB_SETBYTE(MB_PDUOFF, fnct | 0x80);
902  MB_SETBYTE(MB_PDUOFF+1, errcode);
903  mMessageLen=2;
904  MbSendResponse(mastersock);
905  }
906 
907 
908  } // end: slave role loops all clients for requests
909  } // end: slave role receiving requests
910 
911 }
912 
913 
914 // DoReadSignalsPre(void)
916  return mSlaveSocket!=-1;
917 }
918 
919 
920 // DoReadSignalsPost(void)
922 }
923 
924 
925 //ReadSignal(int)
927  return pInputImage[bit];
928 }
929 
930 
931 // DoWriteSignalsPre(void)
933  return mSlaveSocket!=-1;
934 }
935 
936 // DoWrtieSignalsPost(void)
938 }
939 
940 
941 //DoWriteSignal(int,int)
942 void mbDevice::DoWriteSignal(int bit, bool value){
943 
944  // Write one actuator value, adressed by bit number (0 to 63);
945  //FD_DHV("mbDevice("<<mName<<")::DoWriteSignal(" << bit << ", " << value <<")");
946 
947  pOutputImage[bit]=value;
948 
949 }
950 
951 
952 } // namespace
953 
954 
955 
956 #endif // end modbus support
int faudes_setsocket_nonblocking(int fd, bool noblo)
int faudes_getsocket_error(int fd)
#define FAUDES_TYPE_IMPLEMENTATION(ftype, ctype, cbase)
faudes type implementation macros, overall
Definition: cfl_types.h:946
Auto register faudes-type with specified type name.
Definition: cfl_registry.h:458
Faudes exception class.
int Port(void) const
Get TCP port.
Definition: iop_simplenet.h:51
std::string IpColonPort(void) const
Get as colon seperated string.
std::string Ip(void) const
Get IP address.
Definition: iop_simplenet.h:48
A TokenReader reads sequential tokens from a file or string.
std::string FileLine(void) const
Return "filename:line".
bool Eos(const std::string &rLabel)
Peek a token and check whether it ends the specified section.
void ReadEnd(const std::string &rLabel)
Close the current section by matching the previous ReadBegin().
void ReadBegin(const std::string &rLabel)
Open a section by specified label.
bool Get(Token &token)
Get next token.
bool Peek(Token &token)
Peek next token.
A TokenWriter writes sequential tokens to a file, a string or stdout.
void WriteEnd(const std::string &rLabel)
Write end label.
void WriteBegin(const std::string &rLabel)
Write begin label.
Tokens model atomic data for stream IO.
Definition: cfl_token.h:53
const std::string & StringValue(void) const
Get string value of a name token.
Definition: cfl_token.cpp:177
Int AttributeIntegerValue(const std::string &name)
Access attribute value.
Definition: cfl_token.cpp:396
bool ExistsAttributeString(const std::string &name)
Test attibute existence.
Definition: cfl_token.cpp:355
bool IsBegin(void) const
Test token Type.
Definition: cfl_token.cpp:258
void SetEmpty(const std::string &rName)
Initialize as empty-tag token.
Definition: cfl_token.cpp:105
bool ExistsAttributeInteger(const std::string &name)
Test attibute existence.
Definition: cfl_token.cpp:365
void InsAttributeInteger(const std::string &name, Int value)
Insert named attribute with integer value.
Definition: cfl_token.cpp:318
void InsAttributeString(const std::string &name, const std::string &value)
Insert named attribute with string value.
Definition: cfl_token.cpp:309
const std::string & AttributeStringValue(const std::string &name)
Access attribute value.
Definition: cfl_token.cpp:385
Base class of all libFAUDES objects that participate in the run-time interface.
Definition: cfl_types.h:239
Processimage synchronisation via Modbus/TCP.
Definition: iop_modbus.h:72
virtual ~mbDevice(void)
Explicit destructor.
Definition: iop_modbus.cpp:61
void DoReadPreface(TokenReader &rTr, const std::string &rLabel="", const Type *pContext=0)
Read non-event-related configuration data from tokenreader.
Definition: iop_modbus.cpp:202
char * pInputImage
Definition: iop_modbus.h:288
virtual bool DoReadSignal(int bitaddr)
Get input signal.
Definition: iop_modbus.cpp:926
int MbSendResponse(int mastersock)
Definition: iop_modbus.cpp:464
int MbReceiveRequest(int mastersock)
Definition: iop_modbus.cpp:448
virtual void DoReadSignalsPost(void)
IO Hook, inputs.
Definition: iop_modbus.cpp:921
void SlaveAddress(const std::string &rAddr)
Set server address of this node.
Definition: iop_modbus.cpp:157
virtual void Compile(void)
Compile to internal data-structures.
Definition: iop_modbus.cpp:84
bool mMasterRole
Role: master/slave.
Definition: iop_modbus.h:270
virtual void Stop(void)
Deactivate the device.
Definition: iop_modbus.cpp:337
int mImageSize
Remote process image buffer.
Definition: iop_modbus.h:286
virtual void Start(void)
Activate the device.
Definition: iop_modbus.cpp:292
int MbReceiveResponse(void)
Definition: iop_modbus.cpp:419
char * mpOutputMask
Definition: iop_modbus.h:290
int MbFlushBuffers(void)
I/O helper.
Definition: iop_modbus.cpp:372
virtual bool DoWriteSignalsPre(void)
IO Hook, outputs.
Definition: iop_modbus.cpp:932
void DoWritePreface(TokenWriter &rTw, const std::string &rLabel, const Type *pContext) const
Write non-event-related configuration data to tokenreader.
Definition: iop_modbus.cpp:163
std::vector< IoRange > mSlaveIoRanges
Definition: iop_modbus.h:283
virtual void Clear(void)
Clear all configuration (implies Stop)
Definition: iop_modbus.cpp:73
virtual void DoLoopCallback(void)
Loop hook.
Definition: iop_modbus.cpp:480
virtual void DoWriteSignal(int bitaddr, bool value)
Set output signal.
Definition: iop_modbus.cpp:942
virtual bool DoReadSignalsPre(void)
IO Hook, inputs.
Definition: iop_modbus.cpp:915
virtual void DoWriteSignalsPost(void)
IO Hook, outputs.
Definition: iop_modbus.cpp:937
int MbSendRequest(int id)
Definition: iop_modbus.cpp:396
std::vector< int > mMasterSockets
Background thread: tcp connection to remote masters.
Definition: iop_modbus.h:300
int mSlaveSocket
Background thread: tcp connection to remote slave.
Definition: iop_modbus.h:293
void AppendRemoteInputs(int mbid, int mbaddr, int cnt, int fdaddr)
Append remotely implemented inputs.
Definition: iop_modbus.cpp:135
char * pOutputImage
Definition: iop_modbus.h:289
SimplenetAddress mSlaveAddress
IP addresses.
Definition: iop_modbus.h:273
void AppendRemoteOutputs(int mbid, int mbaddr, int cnt, int fdaddr)
Append remotely implemented outputs.
Definition: iop_modbus.cpp:146
An sDevice implements signal based semantics for faudes events.
Definition: iop_sdevice.h:291
virtual int CycleTime() const
Report cycle time.
virtual void Stop(void)
Deactivate the device.
int mMaxBitAddress
Address range.
Definition: iop_sdevice.h:486
virtual void DoReadPreface(TokenReader &rTr, const std::string &rLabel="", const Type *pContext=0)
Reads non-event-related configuration from TokenReader.
virtual void Clear(void)
Clear all configuration.
bool mSyncWrite
True for synchronous output writes.
Definition: iop_sdevice.h:616
void DoWritePreface(TokenWriter &rTw, const std::string &rLabel, const Type *pContext=0) const
Writes non-event-related configuration to TokenWriter.
virtual void Start(void)
Activate the device.
std::map< int, EventSet > mOutputLevelIndexMap
Reverse output map: map signal addres to events that affect the resp.
Definition: iop_sdevice.h:483
virtual void Compile(void)
Build up internal data-structure (e.g.
virtual DeviceState Status(void)
Get status.
Definition: iop_vdevice.h:486
std::string mName
Name.
Definition: iop_vdevice.h:758
DeviceState mState
Status: running, starting etc.
Definition: iop_vdevice.h:770
#define MB_SETINT(p, v)
Definition: iop_modbus.cpp:364
#define MB_SETBYTE(p, v)
Definition: iop_modbus.cpp:366
#define MB_PDUOFF
Definition: iop_modbus.cpp:363
#define MB_GETBYTE(p)
Definition: iop_modbus.cpp:367
#define MB_GETINT(p)
Definition: iop_modbus.cpp:365
Process image via modbus/tcp.
#define FD_DHV(message)
Definition: iop_vdevice.h:37
#define FD_DH(message)
Definition: iop_vdevice.h:27
libFAUDES resides within the namespace faudes.
AutoRegisterType< mbDevice > gRtiRegisterSpiDevice("ModbusDevice")
Modbus address ranges.
Definition: iop_modbus.h:276

libFAUDES 2.32b --- 2024.03.01 --- c++ api documentaion by doxygen