-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlars_timeoutserial.cpp
242 lines (230 loc) · 8.49 KB
/
lars_timeoutserial.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*
* File: TimeoutSerial.cpp
* Author: Terraneo Federico
* Distributed under the Boost Software License, Version 1.0.
* Created on September 12, 2009, 3:47 PM
*
* v1.05: Fixed a bug regarding reading after a timeout (again).
*
* v1.04: Fixed bug with timeout set to zero
*
* v1.03: Fix for Mac OS X, now fully working on Mac.
*
* v1.02: Code cleanup, speed improvements, bug fixes.
*
* v1.01: Fixed a bug that caused errors while reading after a timeout.
*
* v1.00: First release.
*/
#include "lars_timeoutserial.h"
#include <string>
#include <algorithm>
#include <iostream>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
TimeoutSerial::TimeoutSerial( ) : io( ), port( io ), timer( io ),
timeout( posix_time::seconds( 0 ) ) { }
TimeoutSerial::TimeoutSerial( const std::string& devname, unsigned int baud_rate,
asio::serial_port_base::parity opt_parity,
asio::serial_port_base::character_size opt_csize,
asio::serial_port_base::flow_control opt_flow,
asio::serial_port_base::stop_bits opt_stop )
: io( ), port( io ), timer( io ), timeout( posix_time::seconds( 0 ) )
{
open(devname, baud_rate, opt_parity, opt_csize, opt_flow, opt_stop);
}
void TimeoutSerial::open( const std::string& devname, unsigned int baud_rate,
asio::serial_port_base::parity opt_parity,
asio::serial_port_base::character_size opt_csize,
asio::serial_port_base::flow_control opt_flow,
asio::serial_port_base::stop_bits opt_stop )
{
if ( isOpen() ) close();
port.open(devname);
port.set_option(asio::serial_port_base::baud_rate(baud_rate));
port.set_option(opt_parity);
port.set_option(opt_csize);
port.set_option(opt_flow);
port.set_option(opt_stop);
}
bool TimeoutSerial::isOpen( ) const
{
return port.is_open();
}
void TimeoutSerial::clearBuffer( )
{
istream is(&readData);
while ( readData.size() > 0 )//If there is some data from a previous read
is.read(NULL, readData.size());
}
void TimeoutSerial::close( )
{
if ( isOpen() == false ) return;
port.close();
}
void TimeoutSerial::setTimeout( const posix_time::time_duration& t )
{
timeout = t;
}
void TimeoutSerial::write( const char *data, size_t size )
{
asio::write(port, asio::buffer(data, size));
}
void TimeoutSerial::write( const std::vector<char>& data )
{
asio::write(port, asio::buffer(&data[0], data.size()));
}
void TimeoutSerial::writeString( const std::string& s )
{
asio::write(port, asio::buffer(s.c_str(), s.size()));
}
void TimeoutSerial::read( char *data, size_t size )
{
if ( readData.size() > 0 )//If there is some data from a previous read
{
istream is(&readData);
size_t toRead = min(readData.size(), size); //How many bytes to read?
is.read(data, toRead);
data += toRead;
size -= toRead;
if ( size == 0 ) return; //If read data was enough, just return
}
setupParameters = ReadSetupParameters(data, size);
performReadSetup(setupParameters);
//For this code to work, there should always be a timeout, so the
//request for no timeout is translated into a very long timeout
if ( timeout != posix_time::seconds(0) ) timer.expires_from_now(timeout);
else timer.expires_from_now(posix_time::hours(100000));
timer.async_wait(boost::bind(&TimeoutSerial::timeoutExpired, this,
asio::placeholders::error));
result = resultInProgress;
bytesTransferred = 0;
for ( ; ; )
{
io.run_one();
switch ( result )
{
case resultSuccess:
timer.cancel();
return;
case resultTimeoutExpired:
port.cancel();
throw (timeout_exception("Timeout expired") );
case resultError:
timer.cancel();
port.cancel();
throw (boost::system::system_error(boost::system::error_code(),
"Error while reading") );
case resultInProgress:
break;
default:
//if resultInProgress remain in the loop
break;
}
}
}
std::vector<char> TimeoutSerial::read( size_t size )
{
vector<char> result(size, '\0'); //Allocate a vector with the desired size
read(&result[0], size); //Fill it with values
return result;
}
std::string TimeoutSerial::readString( size_t size )
{
string result(size, '\0'); //Allocate a string with the desired size
read(&result[0], size); //Fill it with values
return result;
}
std::string TimeoutSerial::readStringUntil( const std::string& delim )
{
// Note: if readData contains some previously read data, the call to
// async_read_until (which is done in performReadSetup) correctly handles
// it. If the data is enough it will also immediately call readCompleted()
setupParameters = ReadSetupParameters(delim);
performReadSetup(setupParameters);
//For this code to work, there should always be a timeout, so the
//request for no timeout is translated into a very long timeout
if ( timeout != posix_time::seconds(0) ) timer.expires_from_now(timeout);
else timer.expires_from_now(posix_time::hours(100000));
timer.async_wait(boost::bind(&TimeoutSerial::timeoutExpired, this,
asio::placeholders::error));
result = resultInProgress;
bytesTransferred = 0;
for ( ; ; )
{
io.run_one();
switch ( result )
{
case resultSuccess:
{
timer.cancel();
bytesTransferred -= delim.size(); //Don't count delim
istream is(&readData);
string result(bytesTransferred, '\0'); //Alloc string
is.read(&result[0], bytesTransferred); //Fill values
is.ignore(delim.size()); //Remove delimiter from stream
return result;
}
case resultTimeoutExpired:
port.cancel();
throw (timeout_exception("Timeout expired") );
case resultError:
timer.cancel();
port.cancel();
throw (boost::system::system_error(boost::system::error_code(),
"Error while reading") );
case resultInProgress:
break;
default:
//if resultInProgress remain in the loop
break;
}
}
}
TimeoutSerial::~TimeoutSerial( ) { }
void TimeoutSerial::performReadSetup( const ReadSetupParameters & param )
{
if ( param.fixedSize )
{
asio::async_read(port, asio::buffer(param.data, param.size), boost::bind(
&TimeoutSerial::readCompleted, this, asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
else
{
asio::async_read_until(port, readData, param.delim, boost::bind(
&TimeoutSerial::readCompleted, this, asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
}
void TimeoutSerial::timeoutExpired( const boost::system::error_code & error )
{
if ( !error && result == resultInProgress ) result = resultTimeoutExpired;
}
void TimeoutSerial::readCompleted( const boost::system::error_code& error,
const size_t bytesTransferred )
{
if ( !error )
{
result = resultSuccess;
this->bytesTransferred = bytesTransferred;
return;
}
//In case a asynchronous operation is cancelled due to a timeout,
//each OS seems to have its way to react.
#ifdef _WIN32
if ( error.value() == 995 ) return; //Windows spits out error 995
#elif defined(__APPLE__)
if ( error.value() == 45 )
{
//Bug on OS X, it might be necessary to repeat the setup
//http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html
performReadSetup(setupParameters);
return;
}
#else //Linux
if ( error.value() == 125 ) return; //Linux outputs error 125
#endif
result = resultError;
}