solarpowerlog trunk
/home/tobi/workspace/solarpowerlog/src/Connections/CConnectSerialAsio.cpp
Go to the documentation of this file.
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 */