|
solarpowerlog trunk
|
00001 /* ---------------------------------------------------------------------------- 00002 solarpowerlog 00003 Copyright (C) 2009 Tobias Frost 00004 00005 This file is part of solarpowerlog. 00006 00007 Solarpowerlog is free software; However, it is dual-licenced 00008 as described in the file "COPYING". 00009 00010 For this file (ConnectionTCP.cpp), the license terms are: 00011 00012 You can redistribute it and/or modify it under the terms of the GNU 00013 General Public License as published by the Free Software Foundation; either 00014 version 3 of the License, or (at your option) any later version. 00015 00016 This program is distributed in the hope that it will be useful, but 00017 WITHOUT ANY WARRANTY; without even the implied warranty of 00018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00019 Lesser General Public License for more details. 00020 00021 You should have received a copy of the GNU Library General Public 00022 License along with this proramm; if not, see 00023 <http://www.gnu.org/licenses/>. 00024 ---------------------------------------------------------------------------- 00025 */ 00026 00039 #ifdef HAVE_CONFIG_H 00040 #include "config.h" 00041 #include "porting.h" 00042 #endif 00043 00044 #ifdef HAVE_COMMS_ASIOSERIAL 00045 00046 #define DEBUG_SERIALASIO 00047 00048 #include "interfaces/IConnect.h" 00049 #include "Connections/CConnectSerialAsio.h" 00050 00051 #include <iostream> 00052 #include <string> 00053 00054 #include "configuration/CConfigHelper.h" 00055 00056 #include <boost/asio/write.hpp> 00057 #include <boost/asio/io_service.hpp> 00058 #include <boost/asio/ip/tcp.hpp> 00059 #include <boost/asio/streambuf.hpp> 00060 00061 #include <boost/algorithm/string.hpp> 00062 00063 #include "interfaces/CMutexHelper.h" 00064 00065 #include <errno.h> 00066 #include <boost/asio/deadline_timer.hpp> 00067 #include <boost/asio/placeholders.hpp> 00068 #include <boost/date_time/posix_time/posix_time_config.hpp> 00069 #include <boost/bind.hpp> 00070 00071 using namespace boost::posix_time; 00072 00073 using namespace std; 00074 using namespace boost::asio; 00075 using namespace boost; 00076 using namespace libconfig; 00077 00078 struct asyncASIOCompletionHandler 00079 { 00080 asyncASIOCompletionHandler(size_t *b, boost::system::error_code *ec) 00081 { 00082 bytes = b; 00083 this->ec = ec; 00084 } 00085 00086 void operator()(const boost::system::error_code& e, 00087 std::size_t bytes_transferred) 00088 { 00089 *bytes = bytes_transferred; 00090 *ec = e; 00091 } 00092 00093 // note, we need pointer as boost seems to make a copy of our handler... 00094 size_t *bytes; 00095 boost::system::error_code *ec; 00096 }; 00097 00100 static void boosthelper_set_result(int* store, int value) 00101 { 00102 if (store) 00103 *store = value; 00104 } 00105 00106 CConnectSerialAsio::CConnectSerialAsio(const string &configurationname) : 00107 IConnect(configurationname) 00108 { 00109 // Generate our own asio ioservice 00110 // TODO check if one central would do that too... 00111 ioservice = new io_service; 00112 port = new boost::asio::serial_port(*ioservice); 00113 sem_init(&cmdsemaphore, 0, 0); 00114 } 00115 00116 CConnectSerialAsio::~CConnectSerialAsio() 00117 { 00118 if (IsConnected()) { 00119 Disconnect(NULL); 00120 } 00121 00122 if (port) 00123 delete port; 00124 if (ioservice) 00125 delete ioservice; 00126 00127 } 00128 00129 // TODO Extract to common base class (duplicate code here!!) 00130 bool CConnectSerialAsio::Connect(ICommand *callback) 00131 { 00132 sem_t semaphore; 00133 00134 CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::CONNECT, 00135 callback); 00136 00137 // if callback is NUll, fallback to synchronous operation. 00138 if (!callback) { 00139 sem_init(&semaphore, 0, 0); 00140 commando->SetSemaphore(&semaphore); 00141 } 00142 00143 PushWork(commando); 00144 00145 if (!callback) { 00146 sem_wait(&semaphore); 00147 LOGTRACE(logger, "destroying CAsyncCommando " << commando ); 00148 delete commando; 00149 return IsConnected(); 00150 } 00151 00152 return true; 00153 } 00154 00155 /* Disconnect 00156 * 00157 * The disconnection is done by the async task. 00158 * 00159 * (If to be done synchronous, it is also dispatched to the worker thread and 00160 * directly waited for completion.) 00161 * */ 00162 bool CConnectSerialAsio::Disconnect(ICommand *callback) 00163 { 00164 sem_t semaphore; 00165 00166 CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::DISCONNECT, 00167 callback); 00168 // if callback is NULL, fallback to synchronous operation. 00169 // (we will do the job asynchronous, but wait for completion here) 00170 if (!callback) { 00171 sem_init(&semaphore, 0, 0); 00172 commando->SetSemaphore(&semaphore); 00173 } 00174 00175 PushWork(commando); 00176 00177 if (!callback) { 00178 // wait for async job completion 00179 sem_wait(&semaphore); 00180 LOGTRACE(logger, "destroying CAsyncCommando " << commando ); 00181 delete commando; 00182 } 00183 return true; 00184 } 00185 00186 // Send kept SYNC for the moment 00187 bool CConnectSerialAsio::Send(const char *tosend, unsigned int len, 00188 ICommand *callback) 00189 { 00190 sem_t semaphore; 00191 bool ret; 00192 std::string s(tosend, len); 00193 s.assign(tosend, len); 00194 00195 CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::SEND, callback); 00196 callback->addData(ICONN_TOKEN_SEND_STRING, s); 00197 00198 if (callback) { 00199 sem_init(&semaphore, 0, 0); 00200 commando->SetSemaphore(&semaphore); 00201 } 00202 00203 PushWork(commando); 00204 00205 if (!callback) { 00206 int err; 00207 // sync: wait for async job completion and check result. 00208 sem_wait(&semaphore); 00209 try { 00210 err 00211 = boost::any_cast<int>(commando->callback->findData( 00212 ICMD_ERRNO)); 00213 if (err < 0) { 00214 ret = false; 00215 } else { 00216 ret = true; 00217 } 00218 } catch (std::invalid_argument e) { 00219 LOGDEBUG(logger,"ERR: unexpected exception while " 00220 "receive-handling: Invalid argument " << e.what()); 00221 ret = false; 00222 } catch (boost::bad_any_cast e) { 00223 LOGDEBUG(logger,"ERR: unexpected exception while " 00224 "receive-handling: Bad cast " << e.what()); 00225 ret = false; 00226 } 00227 00228 LOGTRACE(logger, "destroying CAsyncCommando " << commando ); 00229 delete commando; 00230 return ret; 00231 } 00232 return true; 00233 } 00234 00235 // Send kept SYNC for the moment 00236 bool CConnectSerialAsio::Send(const string & tosend, ICommand *callback) 00237 { 00238 return Send(tosend.c_str(), tosend.length(), callback); 00239 } 00240 00241 /* Receive bytes from the stream -- asynced. 00242 * 00243 * As with all other methods, will be done by the worker thread, even if 00244 * synchronous operation is requested. In this case, we'll just wait for 00245 * completion.*/ 00246 bool CConnectSerialAsio::Receive( ICommand *callback) 00247 { 00248 // RECEIVE async Command: 00249 // auxdata: pointer to std::string, where to place received data 00250 00251 sem_t semaphore; 00252 bool ret; 00253 00254 CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::RECEIVE, 00255 callback); 00256 00257 #if 0 00258 if (!callback) { 00259 sem_init(&semaphore, 0, 0); 00260 commando->SetSemaphore(&semaphore); 00261 } 00262 #endif 00263 00264 PushWork(commando); 00265 00266 #if 0 00267 if (!callback) { 00268 int err; 00269 // sync: wait for async job completion and check result. 00270 sem_wait(&semaphore); 00271 try { 00272 err 00273 = boost::any_cast<int>(commando->callback->findData( 00274 ICMD_ERRNO)); 00275 if (err < 0) { 00276 ret = false; 00277 goto out; 00278 } else { 00279 ret = true; 00280 } 00281 00282 wheretoplace = boost::any_cast<string>( 00283 commando->callback->findData(ICONN_TOKEN_RECEIVE_STRING)); 00284 00285 } catch (std::invalid_argument e) { 00286 LOGDEBUG(logger,"ERR: unexpected exception while " 00287 "receive-handling: Invalid arguement " << e.what()); 00288 ret = false; 00289 goto out; 00290 } catch (boost::bad_any_cast e) { 00291 LOGDEBUG(logger,"ERR: unexpected exception while " 00292 "receive-handling: Bad cast " << e.what()); 00293 ret = false; 00294 goto out; 00295 } 00296 00297 out: 00298 LOGTRACE(logger, "destroying CAsyncCommando " << commando ); 00299 delete commando; 00300 return ret; 00301 } 00302 #endif 00303 return true; 00304 } 00305 00306 bool CConnectSerialAsio::IsConnected(void) 00307 { 00308 mutex.lock(); 00309 bool ret = port->is_open(); 00310 mutex.unlock(); 00311 return ret; 00312 } 00313 00314 bool CConnectSerialAsio::CheckConfig(void) 00315 { 00316 string setting; 00317 int tmp; 00318 bool fail = false; 00319 bool portsetting_parseerr = false; 00320 00321 CConfigHelper cfghelper(ConfigurationPath); 00322 00323 fail |= !cfghelper.CheckConfig("serial_serialportname", 00324 libconfig::Setting::TypeString, false); 00325 fail |= !cfghelper.CheckConfig("serial_baudrate", 00326 libconfig::Setting::TypeInt, false); 00327 fail |= !cfghelper.CheckConfig("serial_portparameters", 00328 libconfig::Setting::TypeString); 00329 fail |= !cfghelper.CheckConfig("serial_flowcontrol", 00330 libconfig::Setting::TypeString); 00331 fail |= !cfghelper.CheckConfig("serial_timeout", 00332 libconfig::Setting::TypeInt); 00333 fail |= !cfghelper.CheckConfig("serial_interbytetimeout", 00334 libconfig::Setting::TypeInt); 00335 00336 cfghelper.GetConfig("", setting, std::string("none")); 00337 boost::algorithm::to_lower(setting); 00338 if (setting == "none") { 00339 flowctrl = boost::asio::serial_port_base::flow_control( 00340 boost::asio::serial_port_base::flow_control::none); 00341 } else if (setting == "hardware") { 00342 flowctrl = boost::asio::serial_port_base::flow_control( 00343 boost::asio::serial_port_base::flow_control::hardware); 00344 } else if (setting == "software") { 00345 flowctrl = boost::asio::serial_port_base::flow_control( 00346 boost::asio::serial_port_base::flow_control::software); 00347 } else { 00348 fail = true; 00349 LOGERROR(logger,"serial_flowcontrol must be \"none\", \"hardware\" or \"software\"."); 00350 } 00351 00352 cfghelper.GetConfig("serial_portparameters", setting, std::string("8N1")); 00353 00354 if (setting.size() != 3) { 00355 LOGERROR(logger,"serial_portparameters: Must be exactly three " 00356 "character long)" ); 00357 } else { 00358 00359 if (setting[0] >= '5' || setting[0] <= '9') { 00360 this->characterlen = setting[0] - '0'; 00361 } else { 00362 fail = true; 00363 portsetting_parseerr = true; 00364 LOGERROR(logger,"serial_portparameters: Number of bits per " 00365 "byte must be between 5 and 9" ); 00366 } 00367 00368 switch (setting[1]) 00369 { 00370 case 'E': 00371 case 'e': 00372 parity = boost::asio::serial_port_base::parity( 00373 boost::asio::serial_port_base::parity::even); 00374 break; 00375 00376 case 'O': 00377 case 'o': 00378 parity = boost::asio::serial_port_base::parity( 00379 boost::asio::serial_port_base::parity::odd); 00380 break; 00381 00382 case 'N': 00383 case 'n': 00384 parity = boost::asio::serial_port_base::parity( 00385 boost::asio::serial_port_base::parity::none); 00386 break; 00387 00388 default: 00389 portsetting_parseerr = true; 00390 LOGERROR(logger, "serial_portparameter: Invalid parity. Your " 00391 "choices are 'E'ven ,'O'dd or 'N'one"); 00392 fail = true; 00393 } 00394 00395 // If you are bored, you could implement 1.5 stop bits ;-) 00396 if (setting[2] == '1') { 00397 stopbits = boost::asio::serial_port_base::stop_bits( 00398 boost::asio::serial_port_base::stop_bits::one); 00399 } else if (setting[2] == '2') { 00400 stopbits = boost::asio::serial_port_base::stop_bits( 00401 boost::asio::serial_port_base::stop_bits::two); 00402 } else 00403 portsetting_parseerr = true; 00404 fail = true; 00405 LOGERROR(logger, "serial_portparameter: Invalid number of stop bits."); 00406 } 00407 00408 if (portsetting_parseerr) { 00409 // print some explanations... 00410 LOGERROR(logger, "serial_portparameter must be of the format like" 00411 " \"8N1\" to specify symbol length(e.g for example , parity " 00412 "and number of stopbits.)" ); 00413 } 00414 00415 00416 if (!fail) { 00417 StartWorkerThread(); 00418 return true; 00419 } 00420 00421 return false; 00422 } 00423 00424 void CConnectSerialAsio::_main(void) 00425 { 00426 LOGTRACE(logger, "Starting helper thread"); 00427 00428 while (!IsTermRequested()) { 00429 int syscallret; 00430 00431 // wait for work or signals. 00432 // FIXME Test this if this works ;-) 00433 LOGTRACE(logger, "Waiting for work"); 00434 syscallret = sem_wait(&cmdsemaphore); 00435 if (syscallret == 0) { 00436 // semaphore had work for us. process it. 00437 // safety check: really some work? 00438 mutex.lock(); 00439 if (!cmds.empty()) { 00440 bool delete_cmd; 00441 CAsyncCommand *donow = cmds.front(); 00442 // cache info if to delete the objet later, 00443 // as later it might be already gone. 00444 delete_cmd = donow->IsAsynchronous(); 00445 00446 mutex.unlock(); 00447 00448 LOGTRACE(logger, "Received command " << donow << " with callback " << donow->callback ); 00449 00450 switch (donow->c) 00451 { 00452 case CAsyncCommand::CONNECT: 00453 if (HandleConnect(donow)) { 00454 LOGTRACE(logger, "Check command " << donow << " with callback " << donow->callback ); 00455 mutex.lock(); 00456 LOGTRACE(logger, "Front is " <<cmds.front() ); 00457 cmds.pop_front(); 00458 // check if we have to delete the object 00459 // or -- in case of sync operation -- 00460 // the caller will do that for us. 00461 // the "sign" is, that donow->callback is non NULL 00462 // (as the object can be already gone, if 00463 // the sync command had already deleted it) 00464 if (delete_cmd) { 00465 LOGTRACE(logger, "Deleting " << donow); 00466 delete donow; 00467 } 00468 mutex.unlock(); 00469 } 00470 break; 00471 00472 case CAsyncCommand::DISCONNECT: 00473 if (HandleDisConnect(donow)) { 00474 mutex.lock(); 00475 cmds.pop_front(); 00476 if (delete_cmd) { 00477 LOGTRACE(logger, "Deleting " << donow); 00478 delete donow; 00479 } 00480 mutex.unlock(); 00481 } 00482 break; 00483 00484 case CAsyncCommand::RECEIVE: 00485 if (HandleReceive(donow)) { 00486 mutex.lock(); 00487 cmds.pop_front(); 00488 if (delete_cmd) { 00489 LOGTRACE(logger, "Deleting " << donow); 00490 delete donow; 00491 } 00492 mutex.unlock(); 00493 } 00494 break; 00495 00496 case CAsyncCommand::SEND: 00497 { 00498 if (HandleSend(donow)) { 00499 mutex.lock(); 00500 cmds.pop_front(); 00501 if (delete_cmd) { 00502 LOGTRACE(logger, "Deleting " << donow); 00503 delete donow; 00504 } 00505 mutex.unlock(); 00506 } 00507 } 00508 break; 00509 00510 default: 00511 { 00512 LOGFATAL(logger, "Unknown command received."); 00513 abort(); 00514 break; 00515 } 00516 } 00517 00518 } else { 00519 mutex.unlock(); 00520 } 00521 00522 } 00523 } 00524 00525 IConnect::_main(); 00526 } 00527 00528 bool CConnectSerialAsio::PushWork(CAsyncCommand *cmd) 00529 { 00530 LOGTRACE(logger, "Pushing command " << cmd << " with callback " << cmd->callback ); 00531 mutex.lock(); 00532 cmds.push_back(cmd); 00533 mutex.unlock(); 00534 sem_post(&cmdsemaphore); 00535 workerthread.interrupt(); 00536 return true; 00537 } 00538 00539 bool CConnectSerialAsio::HandleConnect(CAsyncCommand *cmd) 00540 { 00541 // most likely, the inverter's check config did not call CheckConfig 00542 // of this connection method. 00543 assert(baudrate); 00544 00545 string portname; 00546 unsigned long timeout = -1; 00547 boost::system::error_code ec; 00548 00549 // if connected, ignore the commmand, pretend success. 00550 if (IsConnected()) { 00551 cmd->callback->addData(ICMD_ERRNO, 0); 00552 cmd->HandleCompletion(); 00553 return true; 00554 } 00555 00556 CConfigHelper cfghelper(ConfigurationPath); 00557 00558 cfghelper.GetConfig("serial_serialportname", portname); 00559 00560 // we need first to open the port, before applying the settings to it. 00561 ec = port->open(portname, ec); 00562 00563 if (ec) { 00564 // retrieve error code out of ec object. (With luck this works... After testing we'll know more) 00565 // Boost doc won't tell if it is negative, so we better make sure 00566 // (most likely it is, as it is done by an enum) 00567 int eint = ec.value(); 00568 if (eint > 0) 00569 eint = -eint; 00570 cmd->callback->addData(ICMD_ERRNO, eint); 00571 if (!ec.message().empty()) { 00572 cmd->callback->addData(ICMD_ERRNO_STR, ec.message()); 00573 } 00574 cmd->HandleCompletion(); 00575 return true; 00576 } 00577 00578 port->set_option(boost::asio::serial_port_base::baud_rate(baudrate)); 00579 port->set_option( 00580 boost::asio::serial_port_base::character_size(characterlen)); 00581 port->set_option(stopbits); 00582 port->set_option(parity); 00583 port->set_option(flowctrl); 00584 00585 LOGDEBUG(logger, "Opened " << portname ); 00586 // Signal success. 00587 00588 // Saftey check -- could also be an assert... 00589 if (!port->is_open()) { 00590 cmd->callback->addData(ICMD_ERRNO, -EIO); 00591 cmd->callback->addData(ICMD_ERRNO_STR, std::string( 00592 "Error: Open succeeded but port not open. WTF?")); 00593 cmd->HandleCompletion(); 00594 return true; 00595 } 00596 00597 cmd->callback->addData(ICMD_ERRNO, 0); 00598 cmd->HandleCompletion(); 00599 return true; 00600 00601 } 00602 00603 bool CConnectSerialAsio::HandleDisConnect(CAsyncCommand *cmd) 00604 { 00605 boost::system::error_code ec, ec2; 00606 std::string message; 00607 int error = 0; 00608 00609 if (!IsConnected()) { 00610 cmd->callback->addData(ICMD_ERRNO, 0); 00611 cmd->HandleCompletion(); 00612 return true; 00613 } 00614 00615 ec = port->cancel(ec); 00616 ec2 = port->close(ec2); 00617 00618 if (ec) { 00619 error = -EIO; 00620 message = ec.message(); 00621 } 00622 00623 if (ec2) { 00624 error = -EIO; 00625 if (!message.empty()) 00626 message = message + " "; 00627 message = message + ec2.message(); 00628 } 00629 00630 cmd->callback->addData(ICMD_ERRNO, error); 00631 if (!message.empty()) { 00632 cmd->callback->addData(ICMD_ERRNO_STR, ec.message()); 00633 } 00634 00635 cmd->HandleCompletion(); 00636 return true; 00637 } 00638 00652 bool CConnectSerialAsio::HandleReceive(CAsyncCommand *cmd) 00653 { 00654 boost::system::error_code ec, handlerec; 00655 00656 volatile int result_timer = 0; 00657 size_t bytes; 00658 unsigned long timeout; 00659 char buf[2]; 00660 struct asyncASIOCompletionHandler read_handler(&bytes, &handlerec); 00661 // timeout setup 00662 ioservice->reset(); 00663 00664 try { 00665 timeout = boost::any_cast<unsigned long>(cmd->callback->findData( 00666 ICONN_TOKEN_TIMEOUT)); 00667 } catch (std::invalid_argument e) { 00668 CConfigHelper cfghelper(ConfigurationPath); 00669 cfghelper.GetConfig("serial_timeout", timeout, TCP_ASIO_DEFAULT_TIMEOUT); 00670 } catch (boost::bad_any_cast e) { 00671 LOGDEBUG(logger, "Unexpected exception in HandleReceive: Bad cast" << e.what()); 00672 timeout = TCP_ASIO_DEFAULT_TIMEOUT; 00673 } 00674 00675 deadline_timer timer(*(this->ioservice)); 00676 boost::posix_time::time_duration td = boost::posix_time::millisec(timeout); 00677 timer.expires_from_now(td); 00678 timer.async_wait(boost::bind(&boosthelper_set_result, (int*) &result_timer, 00679 1)); 00680 00681 // socket preparation 00682 // async_read. However, boost:asio seems not to allow auto-buffers, 00683 // so we will just read one byte and when this is available, we'll 00684 // check for if there are some others left 00685 port->async_read_some(boost::asio::buffer(&buf, 1), read_handler); 00686 00687 size_t num = ioservice->run_one(ec); 00688 00689 // ioservice error or timeout 00690 if (num == 0 || result_timer) { 00691 timer.cancel(ec); 00692 port->cancel(ec); 00693 LOGTRACE(logger,"Async read timeout"); 00694 cmd->callback->addData(ICMD_ERRNO, -ETIMEDOUT); 00695 cmd->HandleCompletion(); 00696 ioservice->poll(); 00697 return true; 00698 } 00699 00700 timer.cancel(); 00701 ioservice->poll(ec); 00702 00703 if (*read_handler.ec) { 00704 if (*read_handler.ec != boost::asio::error::eof) { 00705 LOGDEBUG(logger,"Async read failed with ec=" << *read_handler.ec 00706 << " msg="<< read_handler.ec->message()); 00707 cmd->callback->addData(ICMD_ERRNO, -EIO); 00708 cmd->callback->addData(ICMD_ERRNO_STR, read_handler.ec->message()); 00709 00710 } else { 00711 cmd->callback->addData(ICMD_ERRNO, -ENOTCONN); 00712 LOGTRACE(logger, "Received eof on socket read"); 00713 } 00714 cmd->HandleCompletion(); 00715 return true; 00716 } 00717 00718 /* now one byte is read -- we apply the byte-timeout here to read the 00719 * remaining bytes. */ 00720 00721 try { 00722 timeout = boost::any_cast<unsigned long>(cmd->callback->findData( 00723 ICONN_TOKEN_INTERBYTETIMEOUT)); 00724 } catch (std::invalid_argument e) { 00725 CConfigHelper cfghelper(ConfigurationPath); 00726 cfghelper.GetConfig("serial_interbytetimeout", timeout, 0UL); 00727 if (timeout == 0) { 00728 // default interbyte timeout is 10 times the time for one byte. 00729 // (we allow the inaccurary and assume 10 bits per byte, which is 00730 // valid for 8N1) 00731 // however, we ensure a minimum time of 40 ms. 00732 // (which still might be tough as our OS might idle around for even longer) 00733 timeout = (1000 * 10 * 10) / baudrate; 00734 if (timeout <= TCP_ASIO_DEFAULT_INTERBYTETIMEOUT) timeout = TCP_ASIO_DEFAULT_INTERBYTETIMEOUT; 00735 } 00736 00737 } catch (boost::bad_any_cast e) { 00738 LOGDEBUG(logger, "Unexpected exception in HandleReceive: Bad cast" << e.what()); 00739 timeout = TCP_ASIO_DEFAULT_TIMEOUT; 00740 } 00741 00742 std::string received; 00743 received[0] = buf[0]; 00744 00745 while (1) { 00746 // prepare timer 00747 bytes = 0; 00748 result_timer = 0; 00749 ec.clear(); 00750 handlerec.clear(); 00751 td = boost::posix_time::millisec(timeout); 00752 timer.expires_from_now(td); 00753 timer.async_wait(boost::bind(&boosthelper_set_result, (int*) &result_timer, 00754 1)); 00755 00756 // read a byte 00757 port->async_read_some(boost::asio::buffer(&buf, 1), read_handler); 00758 00759 size_t num = ioservice->run_one(ec); 00760 00761 // ioservice error or timeout 00762 if (num == 0 || result_timer) { 00763 timer.cancel(ec); 00764 port->cancel(ec); 00765 ioservice->poll(); 00766 break; 00767 } 00768 00769 timer.cancel(); 00770 ioservice->poll(ec); 00771 00772 // asio async read completed -- check for read error 00773 if (*read_handler.ec) { 00774 if (*read_handler.ec != boost::asio::error::eof) { 00775 // read error occured, which is not timeout. 00776 LOGDEBUG(logger,"Async read failed with ec=" << *read_handler.ec 00777 << " msg="<< read_handler.ec->message()); 00778 cmd->callback->addData(ICMD_ERRNO, -EIO); 00779 cmd->callback->addData(ICMD_ERRNO_STR, read_handler.ec->message()); 00780 cmd->HandleCompletion(); 00781 return true; 00782 } else { 00783 // other end closed connection -- treat that as timeout 00784 break; 00785 } 00786 } 00787 00788 // append read byte to string. 00789 received.push_back(buf[0]); 00790 } 00791 00792 // we got at least one byte -- assemble answer. 00793 LOGTRACE(logger,"Serial read " << received.length() << " bytes" ); 00794 00795 cmd->callback->addData(ICONN_TOKEN_RECEIVE_STRING, received); 00796 cmd->callback->addData(ICMD_ERRNO, 0); 00797 cmd->HandleCompletion(); 00798 00799 return true; 00800 } 00801 00803 bool CConnectSerialAsio::HandleSend(CAsyncCommand *cmd) 00804 { 00805 00806 boost::system::error_code ec, handlerec; 00807 volatile int result_timer = 0; 00808 size_t bytes; 00809 unsigned long timeout; 00810 struct asyncASIOCompletionHandler write_handler(&bytes, &handlerec); 00811 // timeout setup 00812 ioservice->reset(); 00813 std::string s; 00814 00815 try { 00816 s = boost::any_cast<std::string>(cmd->callback->findData( 00817 ICONN_TOKEN_SEND_STRING)); 00818 } 00819 #ifdef DEBUG_SERIALASIO 00820 catch (std::invalid_argument e) { 00821 LOGDEBUG(logger, "BUG: required " << ICONN_TOKEN_SEND_STRING << " argument not set"); 00822 00823 } catch (boost::bad_any_cast e) { 00824 LOGDEBUG(logger, "Unexpected exception in HandleSend: Bad cast" << e.what()); 00825 } 00826 #else 00827 catch (...); 00828 #endif 00829 00830 try { 00831 timeout = boost::any_cast<unsigned long>(cmd->callback->findData( 00832 ICONN_TOKEN_TIMEOUT)); 00833 } 00834 #ifdef DEBUG_SERIALASIO 00835 catch (std::invalid_argument e) { 00836 CConfigHelper cfghelper(ConfigurationPath); 00837 cfghelper.GetConfig("tcptimeout", timeout, 3000UL); 00838 } catch (boost::bad_any_cast e) { 00839 LOGDEBUG(logger, "Unexpected exception in HandleSend: Bad cast" << e.what()); 00840 timeout = TCP_ASIO_DEFAULT_TIMEOUT; 00841 } 00842 #else 00843 catch (...) { 00844 CConfigHelper cfghelper(ConfigurationPath); 00845 cfghelper.GetConfig("tcptimeout", timeout, 3000UL); 00846 } 00847 #endif 00848 00849 deadline_timer timer(*(this->ioservice)); 00850 boost::posix_time::time_duration td = boost::posix_time::millisec(timeout); 00851 timer.expires_from_now(td); 00852 timer.async_wait(boost::bind(&boosthelper_set_result, (int*) &result_timer, 00853 1)); 00854 00855 // socket preparation 00856 // async_write the whole std::string 00857 boost::asio::async_write(*port, boost::asio::buffer(s), write_handler); 00858 00859 // run one of the scheduled services 00860 size_t num = ioservice->run_one(ec); 00861 00862 // ioservice error or timeout 00863 if (num == 0 || result_timer) { 00864 timer.cancel(ec); 00865 port->cancel(ec); 00866 LOGTRACE(logger,"Async write timeout"); 00867 cmd->callback->addData(ICMD_ERRNO, -ETIMEDOUT); 00868 cmd->HandleCompletion(); 00869 ioservice->poll(); 00870 return true; 00871 } 00872 00873 // cancel the timer, and catch the completion handler 00874 timer.cancel(); 00875 ioservice->poll(ec); 00876 00877 if (*write_handler.ec) { 00878 if (*write_handler.ec != boost::asio::error::eof) { 00879 LOGDEBUG(logger,"Async write failed with ec=" << *write_handler.ec 00880 << " msg="<< write_handler.ec->message()); 00881 cmd->callback->addData(ICMD_ERRNO, -EIO); 00882 cmd->callback->addData(ICMD_ERRNO_STR, write_handler.ec->message()); 00883 } else { 00884 cmd->callback->addData(ICMD_ERRNO, -ENOTCONN); 00885 LOGTRACE(logger, "Received eof on socket write"); 00886 } 00887 cmd->HandleCompletion(); 00888 return true; 00889 } 00890 00891 if (s.length() != *write_handler.bytes) { 00892 LOGDEBUG(logger,"Sent " 00893 << *write_handler.bytes << " but expected "<< s.length() ); 00894 cmd->callback->addData(ICMD_ERRNO, -EIO); 00895 cmd->HandleCompletion(); 00896 return true; 00897 } 00898 00899 cmd->callback->addData(ICMD_ERRNO, 0); 00900 cmd->HandleCompletion(); 00901 return true; 00902 } 00903 00904 #endif /* HAVE_COMMS_ASIOSERIAL */