2 * This file is part of the libCEC(R) library.
4 * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited. All rights reserved.
5 * libCEC(R) is an original work, containing original code.
7 * libCEC(R) is a trademark of Pulse-Eight Limited.
9 * This program is dual-licensed; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 * Alternatively, you can license this library under a commercial license,
25 * please contact Pulse-Eight Licensing for more information.
27 * For more information contact:
28 * Pulse-Eight Licensing <license@pulse-eight.com>
29 * http://www.pulse-eight.com/
30 * http://www.pulse-eight.net/
33 #include "USBCECAdapterCommunication.h"
34 #include "USBCECAdapterCommands.h"
35 #include "USBCECAdapterMessageQueue.h"
36 #include "../platform/sockets/serialport.h"
37 #include "../platform/util/timeutils.h"
38 #include "../platform/util/util.h"
39 #include "../LibCEC.h"
40 #include "../CECProcessor.h"
44 using namespace PLATFORM
;
46 #define CEC_ADAPTER_PING_TIMEOUT 15000
49 #define CEC_LATEST_ADAPTER_FW_VERSION 2
50 // firmware date Thu Apr 26 20:14:49 2012 +0000
51 #define CEC_LATEST_ADAPTER_FW_DATE 0x4F99ACB9
53 #define LIB_CEC m_callback->GetLib()
55 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback
*callback
, const char *strPort
, uint16_t iBaudRate
/* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
56 IAdapterCommunication(callback
),
59 m_lastPollDestination(CECDEVICE_UNKNOWN
),
60 m_bInitialised(false),
63 m_adapterMessageQueue(NULL
),
66 for (unsigned int iPtr
= CECDEVICE_TV
; iPtr
< CECDEVICE_BROADCAST
; iPtr
++)
67 m_bWaitingForAck
[iPtr
] = false;
68 m_port
= new CSerialPort(strPort
, iBaudRate
);
69 m_commands
= new CUSBCECAdapterCommands(this);
72 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
75 DELETE_AND_NULL(m_commands
);
76 DELETE_AND_NULL(m_adapterMessageQueue
);
77 DELETE_AND_NULL(m_port
);
80 void CUSBCECAdapterCommunication::ResetMessageQueue(void)
82 DELETE_AND_NULL(m_adapterMessageQueue
);
83 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
84 m_adapterMessageQueue
->CreateThread();
87 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
89 bool bConnectionOpened(false);
91 CLockObject
lock(m_mutex
);
93 /* we need the port settings here */
96 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "port is NULL");
97 return bConnectionOpened
;
100 /* return true when the port is already open */
103 LIB_CEC
->AddLog(CEC_LOG_WARNING
, "port is already open");
109 /* try to open the connection */
111 CTimeout
timeout(iTimeoutMs
);
112 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
114 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
116 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
119 /* and retry every 250ms until the timeout passed */
122 /* return false when we couldn't connect */
123 if (!bConnectionOpened
)
125 LIB_CEC
->AddLog(CEC_LOG_ERROR
, strError
);
127 if (m_port
->GetErrorNumber() == EACCES
)
129 libcec_parameter param
;
130 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
131 param
.paramData
= (void*)"No permission to open the device";
132 LIB_CEC
->Alert(CEC_ALERT_PERMISSION_ERROR
, param
);
134 else if (m_port
->GetErrorNumber() == EBUSY
)
136 libcec_parameter param
;
137 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
138 param
.paramData
= (void*)"The serial port is busy. Only one program can access the device directly.";
139 LIB_CEC
->Alert(CEC_ALERT_PORT_BUSY
, param
);
144 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
148 // always start by setting the ackmask to 0, to clear previous values
153 bConnectionOpened
= false;
154 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
156 else if (!bSkipChecks
&& !CheckAdapter())
158 bConnectionOpened
= false;
159 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
161 else if (bStartListening
)
163 /* start a ping thread, that will ping the adapter every 15 seconds
164 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
165 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
166 if (m_pingThread
->CreateThread())
168 bConnectionOpened
= true;
172 bConnectionOpened
= false;
173 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
177 if (!bConnectionOpened
|| !bStartListening
)
180 return bConnectionOpened
;
183 void CUSBCECAdapterCommunication::Close(void)
185 /* stop the reader thread */
188 CLockObject
lock(m_mutex
);
190 /* set the ackmask to 0 before closing the connection */
191 if (IsOpen() && m_port
->GetErrorNumber() == 0)
193 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - closing the connection", __FUNCTION__
);
195 if (m_commands
->GetFirmwareVersion() >= 2)
196 SetControlledMode(false);
199 m_adapterMessageQueue
->Clear();
201 /* stop and delete the ping thread */
202 DELETE_AND_NULL(m_pingThread
);
204 /* close and delete the com port connection */
209 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
)
211 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
215 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
217 /* mark as waiting for an ack from the destination */
218 MarkAsWaiting(data
.destination
);
220 /* send the message */
221 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
223 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
224 retVal
= output
->state
;
230 void *CUSBCECAdapterCommunication::Process(void)
232 CCECAdapterMessage msg
;
233 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread started");
237 /* read from the serial port */
238 if (!ReadFromDevice(50, 5))
240 libcec_parameter param
;
241 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
242 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
247 /* TODO sleep 5 ms so other threads can get a lock */
251 m_adapterMessageQueue
->Clear();
252 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread ended");
256 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
258 bool bIsError(msg
.IsError());
259 cec_adapter_messagecode
messageCode(msg
.Message());
260 CLockObject
lock(m_mutex
);
262 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
264 m_lastPollDestination
= msg
.Destination();
265 if (msg
.Destination() < CECDEVICE_BROADCAST
)
267 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
270 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
273 m_bWaitingForAck
[msg
.Destination()] = false;
276 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
278 /* hack to suppress warnings when an LG is polling */
279 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
280 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
286 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
288 /* mark as waiting for an ack from the destination */
289 if (dest
< CECDEVICE_BROADCAST
)
291 CLockObject
lock(m_mutex
);
292 m_bWaitingForAck
[dest
] = true;
296 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
298 CTimeout
timeout(iTimeout
);
300 ssize_t
iBytesRead(0);
301 bool bGotMsgEnd(true);
303 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
306 /* if something was received, wait for MSGEND */
307 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
308 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
312 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
315 bool bChanged(false);
317 /* only send the command if the timeout changed */
319 CLockObject
lock(m_mutex
);
320 bChanged
= (m_iLineTimeout
!= iTimeout
);
321 m_iLineTimeout
= iTimeout
;
325 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
330 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
332 CLockObject
adapterLock(m_mutex
);
335 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());
336 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
340 /* write the message */
341 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
343 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());
344 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
345 // this will trigger an alert in the reader thread
350 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
351 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
355 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
357 ssize_t
iBytesRead(0);
362 /* read from the serial port */
364 CLockObject
lock(m_mutex
);
368 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
370 if (m_port
->GetErrorNumber())
372 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
378 if (iBytesRead
< 0 || iBytesRead
> 256)
380 else if (iBytesRead
> 0)
382 /* add the data to the current frame */
383 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
389 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
391 if (!IsOpen() || !m_adapterMessageQueue
)
394 /* create the adapter message for this command */
395 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
396 output
->PushBack(MSGSTART
);
397 output
->PushEscaped((uint8_t)msgCode
);
398 output
->Append(params
);
399 output
->PushBack(MSGEND
);
401 /* write the command */
402 if (!m_adapterMessageQueue
->Write(output
))
404 // this will trigger an alert in the reader thread
405 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
411 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
412 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
414 /* if the controller reported that the command was rejected, and we didn't send the command
415 to set controlled mode, then the controller probably switched to auto mode. set controlled
417 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
419 if (SetControlledMode(true))
420 return SendCommand(msgCode
, params
, true);
427 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
430 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
432 /* try to ping the adapter */
434 unsigned iPingTry(0);
435 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
437 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
441 /* try to read the firmware version */
442 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
444 /* try to set controlled mode for v2+ firmwares */
445 unsigned iControlledTry(0);
446 bool bControlled(false);
447 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
449 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
452 bReturn
= bControlled
;
457 /* try to read the build date */
458 m_commands
->RequestBuildDate();
460 SetInitialised(bReturn
);
464 bool CUSBCECAdapterCommunication::IsOpen(void)
466 /* thread is not being stopped, the port is open and the thread is running */
467 return !IsStopped() && m_port
->IsOpen() && IsRunning();
470 CStdString
CUSBCECAdapterCommunication::GetError(void) const
472 return m_port
->GetError();
475 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
477 CLockObject
lock(m_mutex
);
478 m_bInitialised
= bSetTo
;
481 bool CUSBCECAdapterCommunication::IsInitialised(void)
483 CLockObject
lock(m_mutex
);
484 return m_bInitialised
;
487 bool CUSBCECAdapterCommunication::StartBootloader(void)
489 if (m_port
->IsOpen() && m_commands
->StartBootloader())
497 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
500 CLockObject
lock(m_mutex
);
501 if (m_iAckMask
== iMask
)
505 if (IsOpen() && m_commands
->SetAckMask(iMask
))
507 CLockObject
lock(m_mutex
);
512 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "couldn't change the ackmask: the connection is closed");
516 uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
518 CLockObject
lock(m_mutex
);
522 bool CUSBCECAdapterCommunication::PingAdapter(void)
524 return IsOpen() ? m_commands
->PingAdapter() : false;
527 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
529 return IsOpen() ? m_commands
->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN
;
532 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
534 return IsOpen() ? m_commands
->RequestBuildDate() : 0;
537 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
539 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
&&
540 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
;
543 bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration
&configuration
)
545 return IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
548 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
&configuration
)
550 return IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
553 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
555 return m_port
->GetName();
558 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
560 return IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
563 void *CAdapterPingThread::Process(void)
567 if (m_timeout
.TimeLeft() == 0)
569 /* reinit the timeout */
570 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
572 /* send a ping to the adapter */
574 int iFailedCounter(0);
575 while (!bPinged
&& iFailedCounter
< 3)
577 if (!m_com
->PingAdapter())
579 /* sleep and retry */
580 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
589 if (iFailedCounter
== 3)
591 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
592 m_com
->LIB_CEC
->AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
593 m_com
->StopThread(false);