WINDEX
com.h
1 #pragma once
2 #include <iostream>
3 #include <windows.h>
4 #include <vector>
5 #include <future>
6 #include <functional>
7 #include "wex.h"
8 
9 namespace wex
10 {
15  class com : public gui
16  {
17  public:
21  com(gui *parent)
22  : gui(parent),
23  myCOMHandle(0),
24  myfOverlapped(true),
25  myfCTSFlowControl(true),
26  myInputBufferLength( 0 )
27  {
28  }
31 
33  void port(const std::string &port)
34  {
35  if (atoi(port.c_str()) < 10)
36  myPortNumber = "COM" + port;
37  else
38  myPortNumber = "\\\\.\\COM" + port;
39  }
40 
50  void overlapped(bool f = true)
51  {
52  myfOverlapped = f;
53  }
54 
63  void CTSFlowControl(bool f = true)
64  {
65  myfCTSFlowControl = f;
66  }
67 
80  void DeviceControlString(const std::string &controlString)
81  {
82  // Initalize all the configuration parameters to zero
83  // BuildCommDCBA is supposed to do this,
84  // but does not seem to do so - in particular the CTS flow is not diabled
85  DCB dcbSerialParams = {0};
86 
87  if (!myCOMHandle)
88  return;
89  if (!BuildCommDCBA(
90  controlString.c_str(),
91  &dcbSerialParams))
92  return;
93 
94  // Force the CTS control to state requested
95  dcbSerialParams.fOutxCtsFlow = myfCTSFlowControl;
96 
97  SetCommState(myCOMHandle, &dcbSerialParams);
98  }
99  void baud(int rate)
100  {
101  if (!isOpen())
102  return;
103  DCB dcbSerialParams = {0}; // Initializing DCB structure
104  dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
105  GetCommState(myCOMHandle, &dcbSerialParams);
106  dcbSerialParams.BaudRate = rate;
107  dcbSerialParams.fOutxCtsFlow = 0;
108  SetCommState(myCOMHandle, &dcbSerialParams);
109  }
114  void deviceInputBuffer(int length)
115  {
116  myInputBufferLength = length;
117  }
118 
121  const std::string &portNumber() const
122  {
123  return myPortNumber;
124  }
125  int baud()
126  {
127  if (!isOpen())
128  return 0;
129  DCB dcbSerialParams = {0}; // Initializing DCB structure
130  dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
131  GetCommState(myCOMHandle, &dcbSerialParams);
132  return dcbSerialParams.BaudRate;
133  }
135  bool isOpen()
136  {
137  return myCOMHandle != 0;
138  }
142  std::string configText()
143  {
144  std::stringstream ss;
145  if (!isOpen())
146  {
147  ss << "COM not connected\n";
148  return ss.str();
149  }
150  if (myfOverlapped)
151  ss << "overlapped\n";
152  else
153  ss << "not overlapped\n";
154  _COMMCONFIG cfg;
155  cfg.dcb = {0};
156  DWORD sz = sizeof(cfg);
157  if (!GetCommConfig(
158  myCOMHandle, // Handle to the Serial port
159  &cfg,
160  &sz))
161  {
162  ss << "GetCommConfig FAILED\n";
163  return ss.str();
164  }
165  DCB dcb = cfg.dcb;
166  ss << "\nBaudRate " << dcb.BaudRate
167  << "\nfBinary " << dcb.fBinary
168  << "\nfParity " << dcb.fParity
169  << "\nfOutxCtsFlow " << dcb.fOutxCtsFlow
170  << "\nfOutxDsrFlow " << dcb.fOutxDsrFlow
171  << "\nfDtrControl " << dcb.fDtrControl
172  << "\nfDsrSensitivity " << dcb.fDsrSensitivity
173  << "\nfTXContinueOnXoff " << dcb.fTXContinueOnXoff
174  << "\nfOutX " << dcb.fOutX
175  << "\nfInX " << dcb.fInX
176  << "\nfErrorChar " << dcb.fErrorChar
177  << "\nfBinary " << dcb.fNull
178  << "\nfNull " << dcb.fRtsControl
179  << "\nfAbortOnError " << dcb.fAbortOnError
180  << "\nXonLim " << dcb.XonLim
181  << "\nXoffLim " << dcb.XoffLim
182  << "\nByteSize " << dcb.ByteSize
183  << "\nParity " << dcb.Parity
184  << "\nStopBits " << dcb.StopBits
185  << "\nXonChar " << dcb.XonChar
186  << "\nXoffChar " << dcb.XoffChar
187  << "\nErrorChar " << dcb.ErrorChar
188  << "\nEofChar " << dcb.EofChar
189  << "\nEvtChar " << dcb.EvtChar << "\n";
190  return ss.str();
191  }
192  std::string &errorMsg()
193  {
194  return myError;
195  }
196 
198 
208  bool open()
209  {
210  if (!myPortNumber.length())
211  return false;
212 
213  DWORD dwFlagsAndAttributes = 0;
214  if (myfOverlapped)
215  dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED;
216 
217  myCOMHandle = CreateFile(
218  myPortNumber.c_str(), // port name
219  GENERIC_READ | GENERIC_WRITE, // Read/Write
220  0, // No Sharing
221  NULL, // No Security
222  OPEN_EXISTING, // Open existing port only
223  dwFlagsAndAttributes,
224  // 0,
225  NULL); // Null for Comm Devices
226 
227  if (myCOMHandle == INVALID_HANDLE_VALUE)
228  {
229  DWORD dw = GetLastError();
230 
231  LPVOID lpMsgBuf;
232  FormatMessage(
233  FORMAT_MESSAGE_ALLOCATE_BUFFER |
234  FORMAT_MESSAGE_FROM_SYSTEM |
235  FORMAT_MESSAGE_IGNORE_INSERTS,
236  NULL,
237  dw,
238  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
239  (LPTSTR)&lpMsgBuf,
240  0, NULL);
241  std::cout << (char *)lpMsgBuf;
242 
243  std::cout << "Cannot open COM at "
244  << myPortNumber << " error " << myPortNumber << "\n";
245  myError = myPortNumber;
246  switch (dw)
247  {
248  case 2:
249  myError += " There seems to be no device connected to this port";
250  break;
251  case 5:
252  myError += " This port seems to be in use by another application";
253  break;
254  case 21:
255  myError += " Driver reports device is not ready";
256  break;
257  default:
258  myError += " This port will not open, error " + std::to_string(dw);
259  }
260 
261  LocalFree(lpMsgBuf);
262 
263  myCOMHandle = 0;
264  return false;
265  }
266 
267  myError = "";
268 
270  "baud=9600 parity=N data=8 stop=1 octs=off");
271 
272  // empty the input buffer
273  PurgeComm(myCOMHandle, PURGE_RXCLEAR);
274 
275  if (myInputBufferLength > 0)
276  {
277  // set device input buffer length
278  COMMPROP CommProp;
279  GetCommProperties(myCOMHandle, &CommProp);
280  if (!SetupComm(
281  myCOMHandle,
282  myInputBufferLength,
283  CommProp.dwCurrentRxQueue))
284  myError += " Input buffer resize failed";
285  return false;
286  }
287 
288  return true;
289  }
290  void close()
291  {
292  if (myCOMHandle)
293  {
294  CloseHandle(myCOMHandle);
295  }
296  }
297 
305  void read(int needed)
306  {
307  DWORD NoBytesRead;
308 
309  int chunk; // size of next chunk of data to be read
310  int totalBytesRead = 0;
311 
312  // ensure there is room for the data read
313  if (needed > 0)
314  myRcvbuffer.resize(needed);
315 
316  // loop reading chunks of data as they arrive
317  do
318  {
319  // wait for data
320  int waiting = waitForData();
321 
322  if (needed < 0)
323  {
324  needed = waiting;
325  chunk = waiting;
326  myRcvbuffer.resize(needed);
327  }
328  else if (waiting >= needed)
329  {
330  // read all we need
331  chunk = needed;
332  }
333  else
334  {
335  // read all that is available
336  chunk = waiting;
337  }
338 
339  // read chunk
340  _OVERLAPPED over;
341  memset(&over, 0, sizeof(over));
342  bool ret = ReadFile(
343  myCOMHandle, // Handle of the Serial port
344  myRcvbuffer.data() + totalBytesRead, // pointer to buffer
345  chunk, // amount to read
346  &NoBytesRead, // Number of bytes read
347  &over);
348  if (!ret)
349  {
350  throw std::runtime_error("windex com read block failed " + std::to_string(GetLastError()));
351  }
352 
353  // update data counts
354  totalBytesRead += chunk;
355  needed -= chunk;
356 
357  // std::cout << "COM read block read " << totalBytesRead << "\n";
358  } while (needed > 0);
359  }
360 
362  std::vector<unsigned char>
364  {
365  return myRcvbuffer;
366  }
367 
383  int bytes)
384  {
385  // std::cout << "com read_async " << bytes << "\n";
386 
387  // start blocking read in own thread
388  myFuture = std::async(
389  std::launch::async, // insist on starting immediatly
390  &com::read,
391  this,
392  bytes);
393 
394  // start waiting for read completion in own thread
395  myThread = new std::thread(read_sync_wait, this);
396 
397  /* There are now two threads running
398  one doing the reading
399  and the other waiting for the read to be finished
400 
401  We can return now and get on with something else
402  */
403  }
404 
406  int write(const std::vector<unsigned char> &buffer)
407  {
408  // std::cout << "Write buffersize " << buffer.size() << "\n";
409  // std::cout << configText();
410 
411  _OVERLAPPED over;
412  memset(&over, 0, sizeof(over));
413  DWORD dNoOfBytesWritten;
414  bool ret = WriteFile(
415  myCOMHandle, // Handle to the Serial port
416  buffer.data(), // Data to be written to the port
417  buffer.size(), // No of bytes to write
418  &dNoOfBytesWritten, // Bytes written
419  &over);
420  if (!ret)
421  {
422  int syserrno = GetLastError();
423  std::cout << "write failed " << syserrno << "\n";
424  int count = 0;
425  if (syserrno == 997)
426  {
427  std::cout << "polling ..." << std::endl;
428  while (1)
429  {
430  HasOverlappedIoCompleted(&over);
431  if (over.Internal != STATUS_PENDING)
432  {
433  // for better or worse the write has completed
434  break;
435  }
436 
437  count++;
438  if (count > 10000)
439  {
440  std::cout << "async write timed out\n";
441  return 0;
442  }
443  if (!(count % 1000))
444  std::cout << " ." << std::endl;
445 
446  // Yield
447  Sleep(0);
448  }
449  std::cout << "async write completed\n";
450  return buffer.size();
451  }
452  }
453  // std::cout << "write " << dNoOfBytesWritten << "\n";
454  return dNoOfBytesWritten;
455  }
456 
458  int write(const std::string &msg)
459  {
460  std::vector<unsigned char> buffer(msg.size());
461  memcpy(buffer.data(), msg.data(), msg.size());
462  return write(buffer);
463  }
464 
465  private:
466  std::string myPortNumber;
467  HANDLE myCOMHandle;
468  std::future<void> myFuture;
469  std::thread *myThread;
470  std::vector<unsigned char> myRcvbuffer; // memory buffer to copy data read from devicer
471  int myInputBufferLength; // device input buffer. 0 for default ( 4K )
472  std::string myError;
473  bool myfOverlapped;
474  bool myfCTSFlowControl;
475 
476  int
477  waitForData()
478  {
479  DWORD dwEventMask;
480  _COMSTAT comstat;
481  DWORD errors;
482 
483  while (1)
484  {
485  // check for bytes waiting to be read
486  ClearCommError(
487  myCOMHandle,
488  &errors,
489  &comstat);
490  int waiting = comstat.cbInQue;
491  if (waiting)
492  return waiting;
493 
494  // wait for some data to arrive
495  SetCommMask(myCOMHandle, EV_RXCHAR);
496  WaitCommEvent(myCOMHandle, &dwEventMask, NULL);
497  }
498  }
499  void read_sync_wait()
500  {
501  // check if read is complete from time to time
502  const int check_interval_msecs = 50;
503  while (myFuture.wait_for(std::chrono::milliseconds(check_interval_msecs)) == std::future_status::timeout)
504  {
505  // std::cout << '.' << std::flush;
506  // read still running, loop and check again after an interval
507  }
508 
509  // read complete
510  // send WM_APP+1 message to parent window
511  // which will be handled, not in this thread,
512  // but in the thread that created the window.
513 
514  PostMessageA(
515  myParent->handle(),
516  WM_APP + 1,
517  myID,
518  0);
519 
520  // return, terminating the wait thread
521  return;
522  }
523  };
524 }
read / write to COM serial port
Definition: com.h:16
std::vector< unsigned char > readData()
get reference to buffer containg data that was read
Definition: com.h:363
com(gui *parent)
CTOR.
Definition: com.h:21
void read(int needed)
blocking read from COM port
Definition: com.h:305
void read_async(int bytes)
non-blocking read from COM port
Definition: com.h:382
void port(const std::string &port)
Set port number to which connection will be made.
Definition: com.h:33
std::string configText()
Get human readable port configuration.
Definition: com.h:142
void overlapped(bool f=true)
Enable/Disable open connection overlapped.
Definition: com.h:50
int write(const std::string &msg)
Write string of data to the COM port.
Definition: com.h:458
bool open()
Open connection to port.
Definition: com.h:208
void CTSFlowControl(bool f=true)
Enable/Disable CTS flow control.
Definition: com.h:63
void deviceInputBuffer(int length)
set device input buffer length
Definition: com.h:114
void DeviceControlString(const std::string &controlString)
Configure device.
Definition: com.h:80
bool isOpen()
true if connected
Definition: com.h:135
int write(const std::vector< unsigned char > &buffer)
Write buffer of data to the COM port.
Definition: com.h:406
The base class for all windex gui elements.
Definition: wex.h:824
HWND handle()
get window handle
Definition: wex.h:1655