cec: some optimisations/cosmetics in CCECCommandHandler after the latest changes.
[deb_libcec.git] / src / lib / adapter / USBCECAdapterCommunication.cpp
index a74194369029665969f01de31ab8783b70b997d5..08ae62c701e681250a4948d59b16ccd908269ddc 100644 (file)
@@ -44,7 +44,14 @@ using namespace PLATFORM;
 
 #define CEC_ADAPTER_PING_TIMEOUT 15000
 
-CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate /* = 38400 */) :
+// firmware version 2
+#define CEC_LATEST_ADAPTER_FW_VERSION 2
+// firmware date Thu Apr 26 20:14:49 2012 +0000
+#define CEC_LATEST_ADAPTER_FW_DATE    0x4F99ACB9
+
+#define LIB_CEC m_callback->GetLib()
+
+CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate /* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
     IAdapterCommunication(callback),
     m_port(NULL),
     m_iLineTimeout(0),
@@ -52,9 +59,10 @@ CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCa
     m_bInitialised(false),
     m_pingThread(NULL),
     m_commands(NULL),
-    m_adapterMessageQueue(NULL)
+    m_adapterMessageQueue(NULL),
+    m_iAckMask(0xFFFF)
 {
-  for (unsigned int iPtr = 0; iPtr < 15; iPtr++)
+  for (unsigned int iPtr = CECDEVICE_TV; iPtr < CECDEVICE_BROADCAST; iPtr++)
     m_bWaitingForAck[iPtr] = false;
   m_port = new CSerialPort(strPort, iBaudRate);
 }
@@ -64,9 +72,10 @@ CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
   Close();
   delete m_commands;
   delete m_adapterMessageQueue;
+  delete m_port;
 }
 
-bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */, bool bSkipChecks /* = false */, bool bStartListening /* = true */)
+bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks /* = false */, bool bStartListening /* = true */)
 {
   bool bConnectionOpened(false);
   {
@@ -75,14 +84,14 @@ bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */, bool b
     /* we need the port settings here */
     if (!m_port)
     {
-      CLibCEC::AddLog(CEC_LOG_ERROR, "port is NULL");
+      LIB_CEC->AddLog(CEC_LOG_ERROR, "port is NULL");
       return bConnectionOpened;
     }
 
     /* return true when the port is already open */
     if (IsOpen())
     {
-      CLibCEC::AddLog(CEC_LOG_WARNING, "port is already open");
+      LIB_CEC->AddLog(CEC_LOG_WARNING, "port is already open");
       return true;
     }
 
@@ -91,7 +100,10 @@ bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */, bool b
       m_commands = new CUSBCECAdapterCommands(this);
 
     if (!m_adapterMessageQueue)
+    {
       m_adapterMessageQueue = new CCECAdapterMessageQueue(this);
+      m_adapterMessageQueue->CreateThread();
+    }
 
     /* try to open the connection */
     CStdString strError;
@@ -109,23 +121,38 @@ bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */, bool b
     /* return false when we couldn't connect */
     if (!bConnectionOpened)
     {
-      CLibCEC::AddLog(CEC_LOG_ERROR, strError);
+      LIB_CEC->AddLog(CEC_LOG_ERROR, strError);
+
+      if (m_port->GetErrorNumber() == EACCES)
+      {
+        libcec_parameter param;
+        param.paramType = CEC_PARAMETER_TYPE_STRING;
+        param.paramData = (void*)"No permission to open the device";
+        LIB_CEC->Alert(CEC_ALERT_PERMISSION_ERROR, param);
+      }
+      else if (m_port->GetErrorNumber() == EBUSY)
+      {
+        libcec_parameter param;
+        param.paramType = CEC_PARAMETER_TYPE_STRING;
+        param.paramData = (void*)"The serial port is busy. Only one program can access the device directly.";
+        LIB_CEC->Alert(CEC_ALERT_PORT_BUSY, param);
+      }
       return false;
     }
 
-    CLibCEC::AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
+    LIB_CEC->AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
     ClearInputBytes();
   }
 
   if (!CreateThread())
   {
     bConnectionOpened = false;
-    CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a communication thread");
+    LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a communication thread");
   }
   else if (!bSkipChecks && !CheckAdapter())
   {
     bConnectionOpened = false;
-    CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks");
+    LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks");
   }
   else if (bStartListening)
   {
@@ -139,64 +166,60 @@ bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */, bool b
     else
     {
       bConnectionOpened = false;
-      CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a ping thread");
+      LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a ping thread");
     }
   }
 
   if (!bConnectionOpened || !bStartListening)
     StopThread(0);
-  if (!bConnectionOpened)
-  {
-    delete m_port;
-    m_port = NULL;
-  }
 
   return bConnectionOpened;
 }
 
 void CUSBCECAdapterCommunication::Close(void)
 {
+  /* stop the reader thread */
+  StopThread(0);
+
+  CLockObject lock(m_mutex);
+
   /* set the ackmask to 0 before closing the connection */
-  if (IsRunning())
+  if (IsOpen() && m_port->GetErrorNumber() == 0)
   {
+    LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - closing the connection", __FUNCTION__);
     SetAckMask(0);
     if (m_commands->GetFirmwareVersion() >= 2)
       SetControlledMode(false);
   }
 
+  m_adapterMessageQueue->Clear();
+
   /* stop and delete the ping thread */
   if (m_pingThread)
     m_pingThread->StopThread(0);
   delete m_pingThread;
   m_pingThread = NULL;
 
-  /* stop the reader thread */
-  StopThread(0);
-
   /* close and delete the com port connection */
-  delete m_port;
-  m_port = NULL;
+  if (m_port)
+    m_port->Close();
 }
 
-cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, uint8_t iMaxTries, uint8_t iLineTimeout /* = 3 */, uint8_t iRetryLineTimeout /* = 3 */)
+cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, bool &bRetry, uint8_t iLineTimeout)
 {
   cec_adapter_message_state retVal(ADAPTER_MESSAGE_STATE_UNKNOWN);
   if (!IsRunning())
     return retVal;
 
-  CCECAdapterMessage *output = new CCECAdapterMessage(data, iMaxTries, iLineTimeout, iRetryLineTimeout);
+  CCECAdapterMessage *output = new CCECAdapterMessage(data, iLineTimeout);
 
   /* mark as waiting for an ack from the destination */
   MarkAsWaiting(data.destination);
 
   /* send the message */
-  bool bRetry(true);
-  while (bRetry && ++output->tries < output->maxTries)
-  {
-    bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
-    if (bRetry)
-      Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
-  }
+  bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
+  if (bRetry)
+    Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
   retVal = output->state;
 
   delete output;
@@ -206,20 +229,26 @@ cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &
 void *CUSBCECAdapterCommunication::Process(void)
 {
   CCECAdapterMessage msg;
-  CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread started");
+  LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread started");
 
   while (!IsStopped())
   {
     /* read from the serial port */
     if (!ReadFromDevice(50, 5))
+    {
+      libcec_parameter param;
+      param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN;
+      LIB_CEC->Alert(CEC_ALERT_CONNECTION_LOST, param);
+
       break;
+    }
 
     /* TODO sleep 5 ms so other threads can get a lock */
     Sleep(5);
   }
 
   m_adapterMessageQueue->Clear();
-  CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread ended");
+  LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread ended");
   return NULL;
 }
 
@@ -263,15 +292,16 @@ void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest)
   }
 }
 
-void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = 1000 */)
+void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
 {
   CTimeout timeout(iTimeout);
   uint8_t buff[1024];
   ssize_t iBytesRead(0);
-  bool bGotMsgEnd(false);
+  bool bGotMsgEnd(true);
 
   while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd))
   {
+    bGotMsgEnd = false;
     /* if something was received, wait for MSGEND */
     for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++)
       bGotMsgEnd = buff[iPtr] == MSGEND;
@@ -299,9 +329,9 @@ bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
 {
   CLockObject adapterLock(m_mutex);
-  if (!m_port->IsOpen())
+  if (!IsOpen())
   {
-    CLibCEC::AddLog(CEC_LOG_DEBUG, "error writing command '%s' to the serial port: the connection is closed", CCECAdapterMessage::ToString(message->Message()));
+    LIB_CEC->AddLog(CEC_LOG_DEBUG, "error writing command '%s' to serial port '%s': the connection is closed", CCECAdapterMessage::ToString(message->Message()), m_port->GetName().c_str());
     message->state = ADAPTER_MESSAGE_STATE_ERROR;
     return false;
   }
@@ -309,12 +339,13 @@ bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
   /* write the message */
   if (m_port->Write(message->packet.data, message->Size()) != (ssize_t) message->Size())
   {
-    CLibCEC::AddLog(CEC_LOG_DEBUG, "error writing command '%s' to the serial port: %s", CCECAdapterMessage::ToString(message->Message()), m_port->GetError().c_str());
+    LIB_CEC->AddLog(CEC_LOG_DEBUG, "error writing command '%s' to serial port '%s': %s", CCECAdapterMessage::ToString(message->Message()), m_port->GetName().c_str(), m_port->GetError().c_str());
     message->state = ADAPTER_MESSAGE_STATE_ERROR;
+    Close();
     return false;
   }
 
-  CLibCEC::AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message()));
+  LIB_CEC->AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message()));
   message->state = ADAPTER_MESSAGE_STATE_SENT;
   return true;
 }
@@ -329,17 +360,21 @@ bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize
   /* read from the serial port */
   {
     CLockObject lock(m_mutex);
-    if (!m_port)
+    if (!IsOpen())
       return false;
+
     iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
+
+    if (m_port->GetErrorNumber())
+    {
+      LIB_CEC->AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
+      m_port->Close();
+      return false;
+    }
   }
 
   if (iBytesRead < 0 || iBytesRead > 256)
-  {
-    CLibCEC::AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
-    StopThread(false);
     return false;
-  }
   else if (iBytesRead > 0)
   {
     /* add the data to the current frame */
@@ -351,8 +386,7 @@ bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize
 
 CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage &params, bool bIsRetry /* = false */)
 {
-  if (!m_port || !m_port->IsOpen() ||
-      !m_adapterMessageQueue)
+  if (!m_port->IsOpen() || !m_adapterMessageQueue)
     return NULL;
 
   /* create the adapter message for this command */
@@ -365,17 +399,25 @@ CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_message
   /* write the command */
   if (!m_adapterMessageQueue->Write(output))
   {
-    // timed out
+    if (output->state == ADAPTER_MESSAGE_STATE_ERROR)
+    {
+      libcec_parameter param;
+      param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN;
+      LIB_CEC->Alert(CEC_ALERT_CONNECTION_LOST, param);
+
+      Close();
+    }
     return output;
   }
   else
   {
-    if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED)
+    if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED &&
+        msgCode != MSGCODE_GET_BUILDDATE /* same messagecode value had a different meaning in older fw builds */)
     {
       /* if the controller reported that the command was rejected, and we didn't send the command
          to set controlled mode, then the controller probably switched to auto mode. set controlled
          mode and retry */
-      CLibCEC::AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying");
+      LIB_CEC->AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying");
       delete output;
       if (SetControlledMode(true))
         return SendCommand(msgCode, params, true);
@@ -385,7 +427,7 @@ CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_message
   return output;
 }
 
-bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */)
+bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */)
 {
   bool bReturn(false);
   CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT);
@@ -395,7 +437,7 @@ bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */
   unsigned iPingTry(0);
   while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false)
   {
-    CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
+    LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
     CEvent::Sleep(500);
   }
 
@@ -407,7 +449,7 @@ bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */
     bool bControlled(false);
     while (timeout.TimeLeft() > 0 && (bControlled = SetControlledMode(true)) == false)
     {
-      CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry);
+      LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry);
       CEvent::Sleep(500);
     }
     bReturn = bControlled;
@@ -415,6 +457,9 @@ bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */
   else
     bReturn = true;
 
+  /* try to read the build date */
+  m_commands->RequestBuildDate();
+
   SetInitialised(bReturn);
   return bReturn;
 }
@@ -444,35 +489,63 @@ bool CUSBCECAdapterCommunication::IsInitialised(void)
 
 bool CUSBCECAdapterCommunication::StartBootloader(void)
 {
-  if (!IsRunning())
-    return false;
-
-  return m_commands->StartBootloader();
+  if (m_port->IsOpen() && m_commands->StartBootloader())
+  {
+    Close();
+    return true;
+  }
+  return false;
 }
 
 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
 {
-  return m_commands->SetAckMask(iMask);
+  if (m_iAckMask == iMask)
+    return true;
+
+  if (IsOpen() && m_commands->SetAckMask(iMask))
+  {
+    m_iAckMask = iMask;
+    return true;
+  }
+
+  LIB_CEC->AddLog(CEC_LOG_ERROR, "couldn't change the ackmask: the connection is closed");
+  return false;
+}
+
+uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
+{
+  return m_iAckMask;
 }
 
 bool CUSBCECAdapterCommunication::PingAdapter(void)
 {
-  return m_commands->PingAdapter();
+  return IsOpen() ? m_commands->PingAdapter() : false;
 }
 
 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
 {
-  return m_commands->GetFirmwareVersion();
+  return IsOpen() ? m_commands->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN;
+}
+
+uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
+{
+  return IsOpen() ? m_commands->RequestBuildDate() : 0;
+}
+
+bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
+{
+  return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION &&
+      GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE;
 }
 
-bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration)
+bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration &configuration)
 {
-  return m_commands->PersistConfiguration(configuration);
+  return IsOpen() ? m_commands->PersistConfiguration(configuration) : false;
 }
 
-bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration)
+bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration &configuration)
 {
-  return m_commands->GetConfiguration(configuration);
+  return IsOpen() ? m_commands->GetConfiguration(configuration) : false;
 }
 
 CStdString CUSBCECAdapterCommunication::GetPortName(void)
@@ -482,7 +555,7 @@ CStdString CUSBCECAdapterCommunication::GetPortName(void)
 
 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
 {
-  return m_commands->SetControlledMode(controlled);
+  return IsOpen() ? m_commands->SetControlledMode(controlled) : false;
 }
 
 void *CAdapterPingThread::Process(void)
@@ -501,8 +574,8 @@ void *CAdapterPingThread::Process(void)
       {
         if (!m_com->PingAdapter())
         {
-          /* sleep 1 second and retry */
-          Sleep(1000);
+          /* sleep and retry */
+          Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
           ++iFailedCounter;
         }
         else
@@ -514,7 +587,7 @@ void *CAdapterPingThread::Process(void)
       if (iFailedCounter == 3)
       {
         /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
-        CLibCEC::AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection.");
+        m_com->LIB_CEC->AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection.");
         m_com->StopThread(false);
         break;
       }