Serial Scol plugin
AsyncSerial.cpp
1/*
2 * File: AsyncSerial.cpp
3 * Author: Terraneo Federico
4 * Distributed under the Boost Software License, Version 1.0.
5 * Created on September 7, 2009, 10:46 AM
6 *
7 * v1.02: Fixed a bug in BufferedAsyncSerial: Using the default constructor
8 * the callback was not set up and reading didn't work.
9 *
10 * v1.01: Fixed a bug that did not allow to reopen a closed serial port.
11 *
12 * v1.00: First release.
13 *
14 * IMPORTANT:
15 * On Mac OS X boost asio's serial ports have bugs, and the usual implementation
16 * of this class does not work. So a workaround class was written temporarily,
17 * until asio (hopefully) will fix Mac compatibility for serial ports.
18 *
19 * Please note that unlike said in the documentation on OS X until asio will
20 * be fixed serial port *writes* are *not* asynchronous, but at least
21 * asynchronous *read* works.
22 * In addition the serial port open ignores the following options: parity,
23 * character size, flow, stop bits, and defaults to 8N1 format.
24 * I know it is bad but at least it's better than nothing.
25 *
26 */
27
28#include "AsyncSerial.h"
29
30#include <string>
31#include <algorithm>
32#include <iostream>
33#include <boost/bind/bind.hpp>
34
35using namespace std;
36
37//
38//Class AsyncSerial
39//
40class AsyncSerialImpl : private boost::noncopyable
41{
42public:
43 AsyncSerialImpl() : io(), port(io), backgroundThread(), open(false),
44 error(false), quit(false) {}
45
46 boost::asio::io_service io;
47 boost::asio::serial_port port;
48 boost::thread backgroundThread;
49 bool open;
50 bool error;
51 bool quit;
52
53 mutable boost::mutex errorMutex;
54 mutable boost::mutex quitMutex;
55
57 std::vector<char> writeQueue;
58 boost::shared_array<char> writeBuffer;
60 boost::mutex writeQueueMutex;
62
64 boost::function<void(const char*, size_t)> callback;
65};
66
67AsyncSerial::AsyncSerial() : pimpl(new AsyncSerialImpl)
68{
69
70}
71
72bool AsyncSerial::isPortAvailable(std::string portName)
73{
74 try
75 {
76 boost::asio::io_service service;
77 boost::asio::serial_port sp(service, portName);
78
79 if (sp.is_open())
80 {
81 sp.close();
82 return true;
83 }
84 }
85 catch (std::exception&)
86 {
87 return false;
88 }
89
90 return false;
91}
92
93AsyncSerial::AsyncSerial(const std::string& devname, unsigned int baud_rate,
94 boost::asio::serial_port_base::parity opt_parity,
95 boost::asio::serial_port_base::character_size opt_csize,
96 boost::asio::serial_port_base::flow_control opt_flow,
97 boost::asio::serial_port_base::stop_bits opt_stop)
98 : pimpl(new AsyncSerialImpl)
99{
100 mDevname = devname;
101 mBaudrate = boost::asio::serial_port_base::baud_rate(baud_rate);
102 mParity = opt_parity;
103 mCsize = opt_csize;
104 mFlow = opt_flow;
105 mStopbit = opt_stop;
106
107 pimpl->backgroundThread = boost::thread(boost::bind(&boost::asio::io_service::run, &pimpl->io));
108 pimpl->io.post(boost::bind(&AsyncSerial::retry, this));
109}
110
111void AsyncSerial::open(const std::string& devname, unsigned int baud_rate,
112 boost::asio::serial_port_base::parity opt_parity,
113 boost::asio::serial_port_base::character_size opt_csize,
114 boost::asio::serial_port_base::flow_control opt_flow,
115 boost::asio::serial_port_base::stop_bits opt_stop)
116{
117 if (isOpen())
118 doClose();
119
120 setErrorStatus(true);//If an exception is thrown, error_ remains true
121 pimpl->port.open(devname);
122 pimpl->port.set_option(mBaudrate);
123 pimpl->port.set_option(mParity);
124 pimpl->port.set_option(mCsize);
125 pimpl->port.set_option(mFlow);
126 pimpl->port.set_option(mStopbit);
127
128 //This gives some work to the io_service before it is started
129 pimpl->io.post(boost::bind(&AsyncSerial::doRead, this));
130
131 setErrorStatus(false);//If we get here, no error
132 pimpl->open = true; //Port is now open
133}
134
136{
137 if (isOpen())
138 return;
139
140 try
141 {
142 //look for port name
143 if (mDevname.length() <= 3)
144 {
145#ifdef _WIN32
146 mDevname = "COM" + mDevname;
147#else
148 std::string testport = "/dev/ttyS" + mDevname;
149 if (isPortAvailable(testport))
150 {
151 mDevname = testport;
152 }
153 else
154 {
155 testport = "/dev/ttyUSB" + mDevname;
156 if (isPortAvailable(testport))
157 {
158 mDevname = testport;
159 }
160 else
161 {
162 testport = "/dev/ttyACM" + mDevname;
163 if (isPortAvailable(testport))
164 {
165 mDevname = testport;
166 }
167 else
168 {
169 testport = "/dev/rfcomm" + mDevname;
170 if (isPortAvailable(testport))
171 {
172 mDevname = testport;
173 }
174 }
175 }
176 }
177#endif
178 }
179
180 setErrorStatus(true);//If an exception is thrown, error_ remains true
181 pimpl->port.open(mDevname);
182 pimpl->port.set_option(boost::asio::serial_port_base::baud_rate(mBaudrate));
183 pimpl->port.set_option(mParity);
184 pimpl->port.set_option(mCsize);
185 pimpl->port.set_option(mFlow);
186 pimpl->port.set_option(mStopbit);
187 pimpl->writeQueue.clear();
188 pimpl->writeBuffer.reset();
189 pimpl->writeBufferSize = 0;
190
191 //This gives some work to the io_service before it is started
192 pimpl->io.post(boost::bind(&AsyncSerial::doRead, this));
193
194 setErrorStatus(false);//If we get here, no error
195 pimpl->open = true; //Port is now open
196 }
197 catch (std::exception&)
198 {
199 if (!quitStatus())
200 pimpl->io.post(boost::bind(&AsyncSerial::retry, this));
201 }
202}
203
205{
206 return pimpl->open;
207}
208
210{
211 boost::lock_guard<boost::mutex> l(pimpl->errorMutex);
212 return pimpl->error;
213}
214
215void AsyncSerial::setQuitStatus(bool e)
216{
217 boost::lock_guard<boost::mutex> l(pimpl->quitMutex);
218 pimpl->quit = e;
219}
220
221bool AsyncSerial::quitStatus() const
222{
223 boost::lock_guard<boost::mutex> l(pimpl->quitMutex);
224 return pimpl->quit;
225}
226
228{
229 setQuitStatus(true);
230 pimpl->io.stop();
231 pimpl->backgroundThread.join();
232
233 if (isOpen())
234 {
235 pimpl->open = false;
236 doClose();
237
238 if (errorStatus())
239 {
240 throw(boost::system::system_error(boost::system::error_code(),
241 "Error while closing the device"));
242 }
243 }
244}
245
246void AsyncSerial::write(const char *data, size_t size)
247{
248 if (isOpen())
249 {
250 {
251 boost::lock_guard<boost::mutex> l(pimpl->writeQueueMutex);
252 pimpl->writeQueue.insert(pimpl->writeQueue.end(), data, data + size);
253 }
254 pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
255 }
256}
257
258void AsyncSerial::write(const std::vector<char>& data)
259{
260 if (isOpen())
261 {
262 {
263 boost::lock_guard<boost::mutex> l(pimpl->writeQueueMutex);
264 pimpl->writeQueue.insert(pimpl->writeQueue.end(), data.begin(),
265 data.end());
266 }
267 pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
268 }
269}
270
271void AsyncSerial::writeString(const std::string& s)
272{
273 if (isOpen())
274 {
275 {
276 boost::lock_guard<boost::mutex> l(pimpl->writeQueueMutex);
277 pimpl->writeQueue.insert(pimpl->writeQueue.end(), s.begin(), s.end());
278 }
279 pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
280 }
281}
282
283AsyncSerial::~AsyncSerial()
284{
285 try
286 {
287 close();
288 }
289 catch (...)
290 {
291 //Don't throw from a destructor
292 }
293}
294
295void AsyncSerial::doRead()
296{
297 pimpl->port.async_read_some(boost::asio::buffer(pimpl->readBuffer, readBufferSize),
298 boost::bind(&AsyncSerial::readEnd,
299 this,
300 boost::asio::placeholders::error,
301 boost::asio::placeholders::bytes_transferred));
302}
303
304void AsyncSerial::readEnd(const boost::system::error_code& error,
305 size_t bytes_transferred)
306{
307 if (error)
308 {
309 //error can be true even because the serial port was closed.
310 //In this case it is not a real error, so ignore
311 if (isOpen())
312 {
313 doClose();
314 setErrorStatus(true);
315 }
316 }
317 else
318 {
319 if (pimpl->callback) pimpl->callback(pimpl->readBuffer, bytes_transferred);
320 doRead();
321 }
322}
323
324void AsyncSerial::doWrite()
325{
326 //If a write operation is already in progress, do nothing
327 if (pimpl->writeBuffer == 0)
328 {
329 boost::lock_guard<boost::mutex> l(pimpl->writeQueueMutex);
330 pimpl->writeBufferSize = pimpl->writeQueue.size();
331 pimpl->writeBuffer.reset(new char[pimpl->writeQueue.size()]);
332 copy(pimpl->writeQueue.begin(), pimpl->writeQueue.end(), pimpl->writeBuffer.get());
333 pimpl->writeQueue.clear();
334 async_write(pimpl->port, boost::asio::buffer(pimpl->writeBuffer.get(), pimpl->writeBufferSize), boost::bind(&AsyncSerial::writeEnd, this, boost::asio::placeholders::error));
335 }
336}
337
338void AsyncSerial::writeEnd(const boost::system::error_code& error)
339{
340 if (!error)
341 {
342 boost::lock_guard<boost::mutex> l(pimpl->writeQueueMutex);
343 if (pimpl->writeQueue.empty())
344 {
345 pimpl->writeBuffer.reset();
346 pimpl->writeBufferSize = 0;
347
348 return;
349 }
350 pimpl->writeBufferSize = pimpl->writeQueue.size();
351 pimpl->writeBuffer.reset(new char[pimpl->writeQueue.size()]);
352 copy(pimpl->writeQueue.begin(), pimpl->writeQueue.end(), pimpl->writeBuffer.get());
353 pimpl->writeQueue.clear();
354 async_write(pimpl->port, boost::asio::buffer(pimpl->writeBuffer.get(), pimpl->writeBufferSize), boost::bind(&AsyncSerial::writeEnd, this, boost::asio::placeholders::error));
355 }
356 else
357 {
358 setErrorStatus(true);
359 doClose();
360 }
361}
362
363void AsyncSerial::doClose()
364{
365 boost::system::error_code ec;
366 pimpl->port.cancel(ec);
367
368 if (ec)
369 setErrorStatus(true);
370
371 pimpl->port.close(ec);
372 pimpl->open = false;
373 pimpl->writeQueue.clear();
374
375 if (ec)
376 setErrorStatus(true);
377
378 if (!quitStatus())
379 {
380 retry();
381 }
382}
383
385{
386 boost::lock_guard<boost::mutex> l(pimpl->errorMutex);
387 pimpl->error = e;
388}
389
390void AsyncSerial::setReadCallback(const boost::function<void(const char*, size_t)>& callback)
391{
392 pimpl->callback = callback;
393}
394
396{
397 pimpl->callback.clear();
398}
399
400//
401//Class CallbackAsyncSerial
402//
403
404CallbackAsyncSerial::CallbackAsyncSerial() : AsyncSerial()
405{
406
407}
408
409CallbackAsyncSerial::CallbackAsyncSerial(const std::string& devname,
410 unsigned int baud_rate,
411 boost::asio::serial_port_base::parity opt_parity,
412 boost::asio::serial_port_base::character_size opt_csize,
413 boost::asio::serial_port_base::flow_control opt_flow,
414 boost::asio::serial_port_base::stop_bits opt_stop)
415 : AsyncSerial(devname, baud_rate, opt_parity, opt_csize, opt_flow, opt_stop)
416{
417
418}
419
421 boost::function<void(const char*, size_t)>& callback)
422{
423 setReadCallback(callback);
424}
425
430
431CallbackAsyncSerial::~CallbackAsyncSerial()
432{
434}
void write(const char *data, size_t size)
void setReadCallback(const boost::function< void(const char *, size_t)> &callback)
void clearReadCallback()
void writeString(const std::string &s)
bool errorStatus() const
void open(const std::string &devname, unsigned int baud_rate, boost::asio::serial_port_base::parity opt_parity=boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none), boost::asio::serial_port_base::character_size opt_csize=boost::asio::serial_port_base::character_size(8), boost::asio::serial_port_base::flow_control opt_flow=boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none), boost::asio::serial_port_base::stop_bits opt_stop=boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one))
bool isOpen() const
static const int readBufferSize
void setErrorStatus(bool e)
boost::thread backgroundThread
Thread that runs read/write operations.
boost::shared_array< char > writeBuffer
Data being written.
char readBuffer[AsyncSerial::readBufferSize]
data being read
boost::mutex quitMutex
Mutex for access to error.
boost::asio::io_service io
Io service object.
boost::function< void(const char *, size_t)> callback
Read complete callback.
boost::mutex writeQueueMutex
Mutex for access to writeQueue.
bool error
Error flag.
bool open
True if port open.
boost::asio::serial_port port
Serial port object.
boost::mutex errorMutex
Mutex for access to error.
std::vector< char > writeQueue
Data are queued here before they go in writeBuffer.
bool quit
Error flag.
size_t writeBufferSize
Size of writeBuffer.
void setCallback(const boost::function< void(const char *, size_t)> &callback)