iop_serial.cpp
Go to the documentation of this file.
1 /** @file iop_serial.h Process image via serial line */
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_serial.h"
13 
14 // only compile for use with spi configured
15 #ifdef FAUDES_IODEVICE_SERIAL
16 
17 namespace faudes {
18 
19 /*
20  **********************************************
21  **********************************************
22  **********************************************
23 
24  implementation: serial helpers
25 
26  **********************************************
27  **********************************************
28  **********************************************
29  */
30 
31 
32 // hardcoded block size (64bit)
33 #define PSIZE 8
34 
35 // hardcoded timeout (usec) for serial transmission
36 #define PUSECPB ((int) 1000000.0* (8.0+1.0+1.0) / 115200.0)
37 #define PUSECXX PUSECPB
38 
39 
40 // open serial port, return fd or -1 on error
41 int serialOpen(const std::string& devname) {
42  // open device / det filedescriptor
43  int fd=-1;
44  fd = open(devname.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
45  if(fd == -1) return -1;
46  // set termio options (raw 115200 8N1, no flow control)
47  // see also "Serial Programming Guide for POSIX Operating Systems" by Michael R. Sweet
48  struct termios options;
49  tcgetattr(fd, &options);
50  cfsetispeed(&options, B115200);
51  cfsetospeed(&options, B115200);
52  options.c_cflag |= (CLOCAL | CREAD);
53  options.c_cflag &= ~CSIZE; // 8 data bit
54  options.c_cflag |= CS8;
55  options.c_cflag &= ~PARENB; // no parity
56  options.c_cflag &= ~CSTOPB; // one stop bit
57  options.c_cflag &= ~CRTSCTS; // no harware flow control
58  options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw input (no line editing, no signals)
59  options.c_iflag &= ~(IXON | IXOFF | IXANY); // no software flow control
60  options.c_iflag &= ~(INLCR | IGNCR | ICRNL | IUCLC | IMAXBEL); // no CR/LF mapping and thelike
61  options.c_oflag &= ~OPOST; // raw output (no delays after CR etc)
62  tcsetattr(fd, TCSANOW, &options);
63  // configure via fcntl
64  fcntl(fd, F_SETFL, FNDELAY); // nonblocking read (should be by open option anyway)
65  // done
66  return fd;
67 }
68 
69 // close serial port
70 void serialClose(int fd) {
71  close(fd);
72 }
73 
74 // write block (-1 on error, else 0)
75 int serialWriteBlock(int fd, char* data, int len) {
76  // debug
77  /*
78  static char mdata[1024];
79  if(len>1024) len=1024;
80  for(int i=0; i<len; i++) mdata[i]= data[i]+'A';
81  data=mdata;
82  */
83  while(len>0) {
84  int n=write(fd, data, len);
85  if(n<0) break;
86  len-=n;
87  }
88  if(len!=0)
89  FD_DH("spiDevice()::serialWriteBlock(): cannot transmit");
90  return len == 0 ? 0 : -1;
91 }
92 
93 // flush input buffer
94 void serialFlush(int fd) {
95  char d;
96  int cnt=0;
97  while(read(fd, &d,1)==1) cnt++;
98  if(cnt!=0) FD_DH("spiDevice()::serialFlush(): rec/drop #" << cnt);
99 }
100 
101 // read block (return -1 on error, else 0)
102 // Note: this fnct reads the data if the amount of characters available
103 // exactly matches the specified block length; in any other case,
104 // the read buffer is flushed and -1 is returned to indicate an
105 // error.
106 int serialReadBlock(int fd, char* data, int len) {
107  int n1= read(fd, data,len);
108  // error / no data at all
109  if(n1<=0) return -1;
110  // bytes missing, allow for transmission to complete
111  if(n1<len){
112  usleep( (len-n1)* PUSECPB + PUSECXX );
113  int n2= read(fd, data+n1,len-n1);
114  if(n2<=0) {
115  FD_DH("spiDevice()::serialReadBlock(): rec/drop #" << n1)
116  return -1;
117  }
118  n1+=n2;
119  }
120  // test for empty buffer
121  int n3= read(fd, data,1);
122  if(n3==1) {
123  FD_DH("spiDevice()::serialReadBlock(): rec/drop #" << n1);
124  serialFlush(fd);
125  return -1;
126  }
127  // success
128  return 0;
129 }
130 
131 
132 
133 /*
134  **********************************************
135  **********************************************
136  **********************************************
137 
138  implementation: spiDevice
139 
140  **********************************************
141  **********************************************
142  **********************************************
143  */
144 
145 
146 // std faudes, incl dummy
147 FAUDES_TYPE_IMPLEMENTATION(SpiDevice,spiDevice,sDevice)
148 
149 // autoregister (not functional, see xdevice constructor)
150 AutoRegisterType<spiDevice> gRtiRegisterSpiDevice("SpiDevice");
151 
152 //constructor
153 spiDevice::spiDevice(void) : sDevice() {
154  FD_DHV("spiDevice(" << mName << ")::spiDevice()");
155  // have appropriate default label for token io
156  mDefaultLabel = "SpiDevice";
157  // pointer to internal I/O-image
158  mpImage=0;
159  pOutputImage=0;
160  pInputImage=0;
161  mpOutputMask=0;
162  // behavioural defaults
163  mMaster=false;
164  mSyncWrite=true;
165 }
166 
167 //deconstructor
168 spiDevice::~spiDevice(void) {
169  FD_DHV("spiDevice(" << mName << ")::~spiDevice()");
170  Stop();
171 }
172 
173 // Clear
174 void spiDevice::Clear(void) {
175  // clear base
176  sDevice::Clear();
177  // my configuration
178  mMaster=false;
179  mDeviceFiles.clear();
180  mPorts.clear();
181  mSyncWrite=true;
182 }
183 
184 
185 //Compile(void)
186 void spiDevice::Compile(void){
187  //setup up internal data structure
188  FD_DHV("spiDevice(" << mName << ")::Compile()");
189  // call base
190  sDevice::Compile();
191  // test for illegal address range
192  if(mMaxBitAddress+1 > PSIZE*8) {
193  std::stringstream errstr;
194  errstr << "Invalid address range (must not exceed " << PSIZE*8 << ")";
195  throw Exception("spiDevice:Compile", errstr.str(), 52);
196  }
197  // slave must have exactly one serial device
198  if((!mMaster) && (mDeviceFiles.size()!=1)) {
199  std::stringstream errstr;
200  errstr << "Slave must have exactly one device file specified";
201  throw Exception("spiDevice:Compile()", errstr.str(), 52);
202  }
203  // master must have at least one serial device
204  if((mMaster) && (mDeviceFiles.size()==0)) {
205  std::stringstream errstr;
206  errstr << "Master must have at least one device file specified";
207  throw Exception("spiDevice:Compile()", errstr.str(), 52);
208  }
209 }
210 
211 
212 //DoWrite(rTr,rLabel,pContext)
213 void spiDevice::DoWritePreface(TokenWriter& rTw, const std::string& rLabel, const Type* pContext) const {
214  FD_DHV("spiDevice("<<mName<<")::DoWritePreface()");
215  //call base
216  sDevice::DoWritePreface(rTw,"",pContext);
217  // role
218  Token ftoken;
219  ftoken.SetEmpty("Role");
220  if(mMaster) {
221  ftoken.InsAttributeString("value","master");
222  } else {
223  ftoken.InsAttributeString("value","slave");
224  }
225  rTw << ftoken;
226  // devicefiles
227  for(unsigned int i=0; i<mDeviceFiles.size(); i++) {
228  Token dtoken;
229  ftoken.SetEmpty("DeviceFile");
230  ftoken.InsAttributeString("value",mDeviceFiles.at(i));
231  rTw << ftoken;
232  }
233 }
234 
235 
236 //DoReadPreface(rTr,rLabel,pContext)
237 void spiDevice::DoReadPreface(TokenReader& rTr,const std::string& rLabel, const Type* pContext){
238  //dummy for token-input
239  FD_DHV("spiDevice("<<mName<<")::DoReadPreface()");
240  //call base
241  sDevice::DoReadPreface(rTr,"",pContext);
242  // my global configuration
243  Token token;
244  while(rTr.Peek(token)) {
245  // role
246  if(token.IsBegin("Role")) {
247  rTr.ReadBegin("Role");
248  mMaster=false;
249  if(!token.ExistsAttributeString("value")) {
250  std::stringstream errstr;
251  errstr << "Invalid role tag" << rTr.FileLine();
252  throw Exception("spiDevice:Read", errstr.str(), 52);
253  }
254  std::string val=token.AttributeStringValue("value");
255  std::transform(val.begin(), val.end(), val.begin(), tolower);
256  if(val=="master") mMaster =true;
257  else if(val=="slave") mMaster =false;
258  else {
259  std::stringstream errstr;
260  errstr << "Invalid role tag" << rTr.FileLine();
261  throw Exception("spiDevice:Read", errstr.str(), 52);
262  }
263  rTr.ReadEnd("Role");
264  continue;
265  }
266  // device file
267  if(token.IsBegin("DeviceFile")) {
268  rTr.ReadBegin("DeviceFile");
269  if(!token.ExistsAttributeString("value")) {
270  std::stringstream errstr;
271  errstr << "Invalid device tag" << rTr.FileLine();
272  throw Exception("spiDevice:Read", errstr.str(), 52);
273  }
274  mDeviceFiles.push_back(token.AttributeStringValue("value"));
275  rTr.ReadEnd("DeviceFile");
276  continue;
277  }
278  // unknown: break
279  break;
280  }
281 }
282 
283 
284 // Start(void)
285 void spiDevice::Start(void) {
286  //open wago-device
287  if(mState!=Down) return;
288  FD_DH("spiDevice(" << mName << ")::Start(): open devices #" << mDeviceFiles.size());
289  // initialize serial line(s)
290  for(unsigned int i=0; i<mDeviceFiles.size(); i++) {
291  int fd=serialOpen(mDeviceFiles.at(i));
292  if(fd<0) {
293  std::stringstream errstr;
294  errstr << "cannot open serial line " << mDeviceFiles.at(i);
295  throw Exception("spiDevice()::Start()", errstr.str(), 552);
296  }
297  mPorts.push_back(fd);
298  }
299  // initialize images
300  mpImage = new char[PSIZE];
301  memset(mpImage,0,PSIZE);
302  pOutputImage=mpImage;
303  pInputImage=mpImage;
304  // initialize output mask
305  mpOutputMask = new char[PSIZE];
306  memset(mpOutputMask,0,PSIZE);
307  for(int bit=0; bit<=mMaxBitAddress; bit++)
308  if(!mOutputLevelIndexMap[bit].Empty())
309  mpOutputMask[bit/8] |= (0x01 << (bit %8));
310  // call base (incl. reset)
311  sDevice::Start();
312  // pessimistic: let background thread figure presence of other nodes
313  mState=StartUp;
314 }
315 
316 // Stop()
317 void spiDevice::Stop(void) {
318  //close serial interface
319  if(mState != Up && mState != StartUp) return;
320  FD_DHV("spiDevice(" << mName << ")::Stop()");
321  // call base
322  sDevice::Stop();
323  // close lines
324  for(unsigned int i=0; i< mPorts.size(); i++)
325  serialClose(mPorts.at(i));
326  mPorts.clear();
327  // invalidate images
328  if(mpImage) delete mpImage;
329  mpImage=0;
330  pOutputImage=0;
331  pInputImage=0;
332  if(mpOutputMask) delete mpOutputMask;
333  mpOutputMask=0;
334  // down
335  mState=Down;
336 }
337 
338 
339 // loopcall-back for serial comminucation
340 void spiDevice::DoLoopCallback(void) {
341  // bail out
342  if(mState!=Up && mState!=StartUp) return;
343  //FD_DHV("spiDevice(" << mName << ")::DoLoopCallBack()");
344  // master: send image block to each client and await reply
345  if(mMaster) {
346  FD_DHV("spiDevice()::DoLoopCallBack(): master send images #" << mPorts.size());
347  for(unsigned int i=0; i< mPorts.size(); i++) {
348  // discard input buffer
349  serialFlush(mPorts.at(i));
350  // write
351  serialWriteBlock(mPorts.at(i),mpImage,PSIZE);
352  // await reply
353  usleep(3000); // hardcoded, conservative ... here the cheap design shows :-(
354  char buffer[PSIZE];
355  int err=serialReadBlock(mPorts.at(i),buffer,PSIZE);
356  if(err!=0) continue;
357  FD_DHV("spiDevice()::DoLoopCallBack(): master received image");
358  // copy to my image, except for my outputs
359  for(int i=0; i<PSIZE; i++)
360  mpImage[i]= (buffer[i] & ~mpOutputMask[i]) | (mpImage[i] & mpOutputMask[i]);
361  }
362  }
363  // slave: receive image block if available, send reply
364  if(!mMaster) {
365  //FD_DHV("spiDevice()::DoLoopCallBack(): slave await images #" << mPorts.size());
366  // test for image
367  char buffer[PSIZE];
368  int err=serialReadBlock(mPorts.at(0),buffer,PSIZE);
369  if(err==0) {
370  FD_DHV("spiDevice()::DoLoopCallBack(): slave received image");
371  // copy to my image, except for my outputs
372  for(int i=0; i<PSIZE; i++)
373  mpImage[i]= (buffer[i] & ~mpOutputMask[i]) | (mpImage[i] & mpOutputMask[i]);
374  // write
375  serialWriteBlock(mPorts.at(0),mpImage,PSIZE);
376  }
377  } // end: slave
378 }
379 
380 
381 // DoReadSignalsPre(void)
382 bool spiDevice::DoReadSignalsPre(void) {
383  return pInputImage!=0;
384 }
385 
386 
387 // DoReadSignalsPost(void)
388 void spiDevice::DoReadSignalsPost(void) {
389 }
390 
391 
392 //ReadSignal(int)
393 bool spiDevice::DoReadSignal(int bit){
394  // Read one input value, addressed by bit number (0 to 63);
395 
396  // Determine byte and bit address
397  int byte = bit / 8;
398  bit = bit % 8;
399 
400  // Read bit
401  return ( pInputImage[byte] & (0x01 << (bit)) ) != 0x00;
402 }
403 
404 
405 // DoWriteSignalsPre(void)
406 bool spiDevice::DoWriteSignalsPre(void) {
407  return pOutputImage!=0;
408 }
409 
410 // DoWrtieSignalsPost(void)
411 void spiDevice::DoWriteSignalsPost(void) {
412 }
413 
414 
415 //DoWriteSignal(int,int)
416 void spiDevice::DoWriteSignal(int bit, bool value){
417 
418  // Write one actor value, adressed by bit number (0 to 63);
419  FD_DHV("spiDevice("<<mName<<")::DoWriteSignal(" << bit << ", " << value <<")");
420 
421  // Determine byte and bit addresse.
422  int byte = (bit) / 8;
423  bit = (bit) % 8;
424 
425  // Write value to output-image using bit-operations
426  if(value) pOutputImage[byte] |= (0x01 << (bit));
427  else pOutputImage[byte] &= ~(0x01 << (bit));
428 
429 }
430 
431 
432 } // namespace
433 
434 
435 
436 #endif // end serial support
#define FAUDES_TYPE_IMPLEMENTATION(ftype, ctype, cbase)
faudes type implementation macros, overall
Definition: cfl_types.h:946
Process image via serial line.
#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")

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