cec: added GetPortName() to CUSBCECAdapterCommunication
[deb_libcec.git] / src / lib / adapter / USBCECAdapterCommunication.cpp
index e2b03862d437cf75da03dda403a94e3da4e8f600..7037c78eff960357f2842e3f8cd5a154dfcb61cb 100644 (file)
@@ -43,6 +43,7 @@ using namespace PLATFORM;
 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(CCECProcessor *processor, const char *strPort, uint16_t iBaudRate /* = 38400 */) :
     m_port(NULL),
     m_processor(processor),
+    m_bHasData(false),
     m_iLineTimeout(0),
     m_iFirmwareVersion(CEC_FW_VERSION_UNKNOWN),
     m_lastInitiator(CECDEVICE_UNKNOWN),
@@ -55,93 +56,164 @@ CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(CCECProcessor *processo
 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
 {
   Close();
-
-  if (m_port)
-  {
-    delete m_port;
-    m_port = NULL;
-  }
 }
 
-bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */)
+bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */)
 {
+  bool bReturn(false);
   uint64_t iNow = GetTimeMs();
-  uint64_t iTimeout = iNow + iTimeoutMs;
-
-  CLockObject lock(m_mutex);
+  uint64_t iTarget = iTimeoutMs > 0 ? iNow + iTimeoutMs : iNow + CEC_DEFAULT_TRANSMIT_WAIT;
 
-  if (!m_port)
+  /* try to ping the adapter */
+  bool bPinged(false);
+  unsigned iPingTry(0);
+  while (iNow < iTarget && (bPinged = PingAdapter()) == false)
   {
-    CLibCEC::AddLog(CEC_LOG_ERROR, "port is NULL");
-    return false;
+    CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
+    Sleep(500);
+    iNow = GetTimeMs();
   }
 
-  if (IsOpen())
+  /* try to read the firmware version */
+  m_iFirmwareVersion = CEC_FW_VERSION_UNKNOWN;
+  unsigned iFwVersionTry(0);
+  while (bPinged && iNow < iTarget && (m_iFirmwareVersion = GetFirmwareVersion()) == CEC_FW_VERSION_UNKNOWN)
   {
-    CLibCEC::AddLog(CEC_LOG_ERROR, "port is already open");
-    return true;
+    CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond with a correct firmware version (try %d)", ++iFwVersionTry);
+    Sleep(500);
+    iNow = GetTimeMs();
   }
 
-  CStdString strError;
-  bool bConnected(false);
-  while (!bConnected && iNow < iTimeout)
+  if (m_iFirmwareVersion >= 2)
   {
-    if ((bConnected = m_port->Open(iTimeout)) == false)
+    /* try to set controlled mode */
+    unsigned iControlledTry(0);
+    bool bControlled(false);
+    while (iNow < iTarget && (bControlled = SetControlledMode(true)) == false)
     {
-      strError.Format("error opening serial port '%s': %s", m_port->GetName().c_str(), m_port->GetError().c_str());
-      Sleep(250);
+      CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry);
+      Sleep(500);
       iNow = GetTimeMs();
     }
+    bReturn = bControlled;
   }
+  else
+    bReturn = true;
 
-  if (!bConnected)
-  {
-    CLibCEC::AddLog(CEC_LOG_ERROR, strError);
-    return false;
-  }
+  return bReturn;
+}
 
-  CLibCEC::AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
+bool CUSBCECAdapterCommunication::Open(IAdapterCommunicationCallback *cb, uint32_t iTimeoutMs /* = 10000 */)
+{
+  uint64_t iNow = GetTimeMs();
+  uint64_t iTimeout = iNow + iTimeoutMs;
 
-  //clear any input bytes
-  uint8_t buff[1024];
-  while (m_port->Read(buff, 1024, 100) > 0)
   {
-    CLibCEC::AddLog(CEC_LOG_DEBUG, "data received, clearing it");
-    Sleep(250);
+    CLockObject lock(m_mutex);
+
+    if (!m_port)
+    {
+      CLibCEC::AddLog(CEC_LOG_ERROR, "port is NULL");
+      return false;
+    }
+
+    if (IsOpen())
+    {
+      CLibCEC::AddLog(CEC_LOG_ERROR, "port is already open");
+      return true;
+    }
+
+    m_callback = cb;
+    CStdString strError;
+    bool bConnected(false);
+    while (!bConnected && iNow < iTimeout)
+    {
+      if ((bConnected = m_port->Open(iTimeout)) == false)
+      {
+        strError.Format("error opening serial port '%s': %s", m_port->GetName().c_str(), m_port->GetError().c_str());
+        Sleep(250);
+        iNow = GetTimeMs();
+      }
+    }
+
+    if (!bConnected)
+    {
+      CLibCEC::AddLog(CEC_LOG_ERROR, strError);
+      return false;
+    }
+
+    CLibCEC::AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
+
+    //clear any input bytes
+    uint8_t buff[1024];
+    while (m_port->Read(buff, 1024, 100) > 0)
+    {
+      CLibCEC::AddLog(CEC_LOG_DEBUG, "data received, clearing it");
+      Sleep(250);
+    }
   }
 
   if (CreateThread())
   {
-    CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread started");
-    return true;
-  }
-  else
-  {
-    CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a communication thread");
+    if (!CheckAdapter())
+    {
+      StopThread();
+      CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks");
+    }
+    else
+    {
+      CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread started");
+      return true;
+    }
   }
+  CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a communication thread");
 
   return false;
 }
 
 void CUSBCECAdapterCommunication::Close(void)
 {
-  CLockObject lock(m_mutex);
-  m_rcvCondition.Broadcast();
+  SetAckMask(0);
+  {
+    CLockObject lock(m_mutex);
+    m_bHasData = true;
+    m_rcvCondition.Broadcast();
+  }
   StopThread();
 }
 
 void *CUSBCECAdapterCommunication::Process(void)
 {
+  cec_command command;
+  bool bCommandReceived(false);
   while (!IsStopped())
   {
-    ReadFromDevice(50);
-    Sleep(5);
-    WriteNextCommand();
+    {
+      CLockObject lock(m_mutex);
+      ReadFromDevice(50);
+      bCommandReceived = m_callback && Read(command, 0);
+    }
+
+    /* push the next command to the callback method if there is one */
+    if (!IsStopped() && bCommandReceived)
+      m_callback->OnCommandReceived(command);
+
+    if (!IsStopped())
+    {
+      Sleep(5);
+      WriteNextCommand();
+    }
   }
 
   CCECAdapterMessage *msg(NULL);
   if (m_outBuffer.Pop(msg))
-    msg->condition.Broadcast();
+    msg->event.Broadcast();
+
+  if (m_port)
+  {
+    delete m_port;
+    m_port = NULL;
+  }
 
   return NULL;
 }
@@ -149,6 +221,8 @@ void *CUSBCECAdapterCommunication::Process(void)
 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 retVal(ADAPTER_MESSAGE_STATE_UNKNOWN);
+  if (!IsRunning())
+    return retVal;
 
   CCECAdapterMessage *output = new CCECAdapterMessage(data);
 
@@ -177,41 +251,25 @@ cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &
 
 bool CUSBCECAdapterCommunication::Write(CCECAdapterMessage *data)
 {
-  bool bReturn(false);
-
-  CLockObject lock(data->mutex);
   data->state = ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT;
   m_outBuffer.Push(data);
-  data->condition.Wait(data->mutex);
+  data->event.Wait();
 
-  if (data->state != ADAPTER_MESSAGE_STATE_SENT)
-  {
-    CLibCEC::AddLog(CEC_LOG_ERROR, "command was not sent");
-  }
-  else if (data->expectControllerAck)
+  if ((data->expectControllerAck && data->state != ADAPTER_MESSAGE_STATE_SENT_ACKED) ||
+      (!data->expectControllerAck && data->state != ADAPTER_MESSAGE_STATE_SENT))
   {
-    bReturn = WaitForAck(*data);
-    if (bReturn)
-    {
-      if (data->isTransmission)
-        data->state = ADAPTER_MESSAGE_STATE_SENT_ACKED;
-    }
-    else
-    {
-      data->state = ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED;
-      CLibCEC::AddLog(CEC_LOG_DEBUG, "did not receive ack");
-    }
-  }
-  else
-  {
-    bReturn = true;
+    CLibCEC::AddLog(CEC_LOG_DEBUG, "command was not %s", data->state == ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED ? "acked" : "sent");
+    return false;
   }
 
-  return bReturn;
+  return true;
 }
 
 bool CUSBCECAdapterCommunication::Read(cec_command &command, uint32_t iTimeout)
 {
+  if (!IsRunning())
+    return false;
+
   CCECAdapterMessage msg;
   if (Read(msg, iTimeout))
   {
@@ -234,15 +292,16 @@ bool CUSBCECAdapterCommunication::Read(CCECAdapterMessage &msg, uint32_t iTimeou
 
   if (!m_inBuffer.Pop(buf))
   {
-    if (!m_rcvCondition.Wait(m_mutex, iTimeout))
+    if (iTimeout == 0 || !m_rcvCondition.Wait(m_mutex, m_bHasData, iTimeout))
       return false;
     m_inBuffer.Pop(buf);
+    m_bHasData = m_inBuffer.Size() > 0;
   }
 
   if (buf)
   {
     msg.packet = buf->packet;
-    msg.state = msg.state = ADAPTER_MESSAGE_STATE_INCOMING;
+    msg.state = ADAPTER_MESSAGE_STATE_INCOMING;
     delete buf;
     return true;
   }
@@ -359,6 +418,7 @@ uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
 
   if (iReturn == CEC_FW_VERSION_UNKNOWN)
   {
+    CLockObject lock(m_mutex);
     CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting the firmware version");
     CCECAdapterMessage *output = new CCECAdapterMessage;
 
@@ -369,15 +429,30 @@ uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
     output->expectControllerAck = false;
 
     SendMessageToAdapter(output);
+    bool bWriteOk = output->state == ADAPTER_MESSAGE_STATE_SENT;
     delete output;
+    if (!bWriteOk)
+    {
+      CLibCEC::AddLog(CEC_LOG_ERROR, "could not request the firmware version");
+      return iReturn;
+    }
 
+    Sleep(250); // TODO ReadFromDevice() isn't waiting for the timeout to pass on win32
+    ReadFromDevice(CEC_DEFAULT_TRANSMIT_WAIT, 5 /* start + msgcode + 2 bytes for fw version + end */);
     CCECAdapterMessage input;
-    if (!Read(input, CEC_DEFAULT_TRANSMIT_WAIT) || input.Message() != MSGCODE_FIRMWARE_VERSION || input.Size() != 3)
-      CLibCEC::AddLog(CEC_LOG_ERROR, "no or invalid firmware version (size = %d, message = %d)", input.Size(), input.Message());
+    if (Read(input, 0))
+    {
+      if (input.Message() != MSGCODE_FIRMWARE_VERSION || input.Size() != 3)
+        CLibCEC::AddLog(CEC_LOG_ERROR, "invalid firmware version (size = %d, message = %d)", input.Size(), input.Message());
+      else
+      {
+        m_iFirmwareVersion = (input[1] << 8 | input[2]);
+        iReturn = m_iFirmwareVersion;
+      }
+    }
     else
     {
-      m_iFirmwareVersion = (input[1] << 8 | input[2]);
-      iReturn = m_iFirmwareVersion;
+      CLibCEC::AddLog(CEC_LOG_ERROR, "no firmware version received");
     }
   }
 
@@ -412,9 +487,7 @@ bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
 {
   bool bReturn(false);
-  CStdString strLog;
-  strLog.Format("setting ackmask to %2x", iMask);
-  CLibCEC::AddLog(CEC_LOG_DEBUG, strLog.c_str());
+  CLibCEC::AddLog(CEC_LOG_DEBUG, "setting ackmask to %2x", iMask);
 
   CCECAdapterMessage *output = new CCECAdapterMessage;
 
@@ -436,9 +509,7 @@ bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
 {
   bool bReturn(false);
-  CStdString strLog;
-  strLog.Format("turning controlled mode %s", controlled ? "on" : "off");
-  CLibCEC::AddLog(CEC_LOG_DEBUG, strLog.c_str());
+  CLibCEC::AddLog(CEC_LOG_DEBUG, "turning controlled mode %s", controlled ? "on" : "off");
 
   CCECAdapterMessage *output = new CCECAdapterMessage;
 
@@ -467,16 +538,13 @@ bool CUSBCECAdapterCommunication::WaitForAck(CCECAdapterMessage &message)
   uint8_t iPacketsLeft(message.Size() / 4);
 
   int64_t iNow = GetTimeMs();
-  int64_t iTargetTime = iNow + message.transmit_timeout;
+  int64_t iTargetTime = iNow + (message.transmit_timeout <= 5 ? CEC_DEFAULT_TRANSMIT_WAIT : message.transmit_timeout);
 
-  while (!bTransmitSucceeded && !bError && (message.transmit_timeout == 0 || iNow < iTargetTime))
+  while (!bTransmitSucceeded && !bError && iNow < iTargetTime)
   {
+    ReadFromDevice(50);
     CCECAdapterMessage msg;
-    int32_t iWait = (int32_t)(iTargetTime - iNow);
-    if (iWait <= 5 || message.transmit_timeout <= 5)
-      iWait = CEC_DEFAULT_TRANSMIT_WAIT;
-
-    if (!Read(msg, iWait))
+    if (!Read(msg, 0))
     {
       iNow = GetTimeMs();
       continue;
@@ -530,6 +598,10 @@ bool CUSBCECAdapterCommunication::WaitForAck(CCECAdapterMessage &message)
     }
   }
 
+  message.state = bTransmitSucceeded && !bError ?
+      ADAPTER_MESSAGE_STATE_SENT_ACKED :
+      ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED;
+
   return bTransmitSucceeded && !bError;
 }
 
@@ -558,6 +630,7 @@ void CUSBCECAdapterCommunication::AddData(uint8_t *data, size_t iLen)
       m_currentAdapterMessage.Clear();
       m_bGotStart = false;
       m_bNextIsEscaped = false;
+      m_bHasData = true;
       m_rcvCondition.Signal();
     }
     else if (m_bNextIsEscaped)
@@ -576,18 +649,21 @@ void CUSBCECAdapterCommunication::AddData(uint8_t *data, size_t iLen)
   }
 }
 
-bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout)
+bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */)
 {
   ssize_t iBytesRead;
   uint8_t buff[256];
   if (!m_port)
     return false;
+  if (iSize > 256)
+    iSize = 256;
 
   CLockObject lock(m_mutex);
-  iBytesRead = m_port->Read(buff, sizeof(buff), iTimeout);
+  iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
   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)
@@ -601,7 +677,6 @@ bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout)
 void CUSBCECAdapterCommunication::SendMessageToAdapter(CCECAdapterMessage *msg)
 {
   CLockObject adapterLock(m_mutex);
-  CLockObject lock(msg->mutex);
   if (msg->tries == 1)
     SetLineTimeout(msg->lineTimeout);
   else
@@ -609,17 +684,21 @@ void CUSBCECAdapterCommunication::SendMessageToAdapter(CCECAdapterMessage *msg)
 
   if (m_port->Write(msg->packet.data, msg->Size()) != (ssize_t) msg->Size())
   {
-    CStdString strError;
-    strError.Format("error writing to serial port: %s", m_port->GetError().c_str());
-    CLibCEC::AddLog(CEC_LOG_ERROR, strError);
+    CLibCEC::AddLog(CEC_LOG_ERROR, "error writing to serial port: %s", m_port->GetError().c_str());
     msg->state = ADAPTER_MESSAGE_STATE_ERROR;
   }
   else
   {
     CLibCEC::AddLog(CEC_LOG_DEBUG, "command sent");
     msg->state = ADAPTER_MESSAGE_STATE_SENT;
+
+    if (msg->expectControllerAck)
+    {
+      if (!WaitForAck(*msg))
+        CLibCEC::AddLog(CEC_LOG_DEBUG, "did not receive ack");
+    }
   }
-  msg->condition.Signal();
+  msg->event.Signal();
 }
 
 void CUSBCECAdapterCommunication::WriteNextCommand(void)
@@ -628,3 +707,10 @@ void CUSBCECAdapterCommunication::WriteNextCommand(void)
   if (m_outBuffer.Pop(msg))
     SendMessageToAdapter(msg);
 }
+
+CStdString CUSBCECAdapterCommunication::GetPortName(void)
+{
+  CStdString strName;
+  strName = m_port->GetName();
+  return strName;
+}