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 "../LibCEC.h"
39 #include "../CECProcessor.h"
43 using namespace PLATFORM
;
45 #define CEC_ADAPTER_PING_TIMEOUT 15000
48 #define CEC_LATEST_ADAPTER_FW_VERSION 2
49 // firmware date Thu Apr 26 20:14:49 2012 +0000
50 #define CEC_LATEST_ADAPTER_FW_DATE 0x4F99ACB9
52 #define LIB_CEC m_callback->GetLib()
54 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback
*callback
, const char *strPort
, uint16_t iBaudRate
/* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
55 IAdapterCommunication(callback
),
58 m_lastPollDestination(CECDEVICE_UNKNOWN
),
59 m_bInitialised(false),
62 m_adapterMessageQueue(NULL
),
65 for (unsigned int iPtr
= CECDEVICE_TV
; iPtr
< CECDEVICE_BROADCAST
; iPtr
++)
66 m_bWaitingForAck
[iPtr
] = false;
67 m_port
= new CSerialPort(strPort
, iBaudRate
);
70 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
74 delete m_adapterMessageQueue
;
78 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
80 bool bConnectionOpened(false);
82 CLockObject
lock(m_mutex
);
84 /* we need the port settings here */
87 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "port is NULL");
88 return bConnectionOpened
;
91 /* return true when the port is already open */
94 LIB_CEC
->AddLog(CEC_LOG_WARNING
, "port is already open");
98 /* adapter commands */
100 m_commands
= new CUSBCECAdapterCommands(this);
102 if (!m_adapterMessageQueue
)
104 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
105 m_adapterMessageQueue
->CreateThread();
108 /* try to open the connection */
110 CTimeout
timeout(iTimeoutMs
);
111 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
113 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
115 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
118 /* and retry every 250ms until the timeout passed */
121 /* return false when we couldn't connect */
122 if (!bConnectionOpened
)
124 LIB_CEC
->AddLog(CEC_LOG_ERROR
, strError
);
128 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
134 bConnectionOpened
= false;
135 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
137 else if (!bSkipChecks
&& !CheckAdapter())
139 bConnectionOpened
= false;
140 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
142 else if (bStartListening
)
144 /* start a ping thread, that will ping the adapter every 15 seconds
145 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
146 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
147 if (m_pingThread
->CreateThread())
149 bConnectionOpened
= true;
153 bConnectionOpened
= false;
154 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
158 if (!bConnectionOpened
|| !bStartListening
)
161 return bConnectionOpened
;
164 void CUSBCECAdapterCommunication::Close(void)
166 /* stop the reader thread */
169 CLockObject
lock(m_mutex
);
171 /* set the ackmask to 0 before closing the connection */
172 if (IsRunning() && m_port
->IsOpen() && m_port
->GetErrorNumber() == 0)
174 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - closing the connection", __FUNCTION__
);
176 if (m_commands
->GetFirmwareVersion() >= 2)
177 SetControlledMode(false);
180 m_adapterMessageQueue
->Clear();
182 /* stop and delete the ping thread */
184 m_pingThread
->StopThread(0);
188 /* close and delete the com port connection */
192 libcec_parameter param
;
193 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
194 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
197 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
)
199 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
203 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
205 /* mark as waiting for an ack from the destination */
206 MarkAsWaiting(data
.destination
);
208 /* send the message */
209 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
211 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
212 retVal
= output
->state
;
218 void *CUSBCECAdapterCommunication::Process(void)
220 CCECAdapterMessage msg
;
221 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread started");
225 /* read from the serial port */
226 if (!ReadFromDevice(50, 5))
229 /* TODO sleep 5 ms so other threads can get a lock */
233 m_adapterMessageQueue
->Clear();
234 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread ended");
238 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
240 bool bIsError(msg
.IsError());
241 cec_adapter_messagecode
messageCode(msg
.Message());
242 CLockObject
lock(m_mutex
);
244 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
246 m_lastPollDestination
= msg
.Destination();
247 if (msg
.Destination() < CECDEVICE_BROADCAST
)
249 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
252 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
255 m_bWaitingForAck
[msg
.Destination()] = false;
258 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
260 /* hack to suppress warnings when an LG is polling */
261 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
262 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
268 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
270 /* mark as waiting for an ack from the destination */
271 if (dest
< CECDEVICE_BROADCAST
)
273 CLockObject
lock(m_mutex
);
274 m_bWaitingForAck
[dest
] = true;
278 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
280 CTimeout
timeout(iTimeout
);
282 ssize_t
iBytesRead(0);
283 bool bGotMsgEnd(true);
285 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
288 /* if something was received, wait for MSGEND */
289 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
290 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
294 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
297 bool bChanged(false);
299 /* only send the command if the timeout changed */
301 CLockObject
lock(m_mutex
);
302 bChanged
= (m_iLineTimeout
!= iTimeout
);
303 m_iLineTimeout
= iTimeout
;
307 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
312 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
314 CLockObject
adapterLock(m_mutex
);
315 if (!m_port
->IsOpen())
317 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());
318 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
322 /* write the message */
323 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
325 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());
326 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
331 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
332 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
336 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
338 ssize_t
iBytesRead(0);
343 /* read from the serial port */
345 CLockObject
lock(m_mutex
);
346 if (!m_port
|| !m_port
->IsOpen())
349 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
351 if (m_port
->GetErrorNumber())
353 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
359 if (iBytesRead
< 0 || iBytesRead
> 256)
361 else if (iBytesRead
> 0)
363 /* add the data to the current frame */
364 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
370 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
372 if (!m_port
|| !m_port
->IsOpen() ||
373 !m_adapterMessageQueue
)
376 /* create the adapter message for this command */
377 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
378 output
->PushBack(MSGSTART
);
379 output
->PushEscaped((uint8_t)msgCode
);
380 output
->Append(params
);
381 output
->PushBack(MSGEND
);
383 /* write the command */
384 if (!m_adapterMessageQueue
->Write(output
))
386 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
392 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
393 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
395 /* if the controller reported that the command was rejected, and we didn't send the command
396 to set controlled mode, then the controller probably switched to auto mode. set controlled
398 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
400 if (SetControlledMode(true))
401 return SendCommand(msgCode
, params
, true);
408 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
411 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
413 /* try to ping the adapter */
415 unsigned iPingTry(0);
416 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
418 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
422 /* try to read the firmware version */
423 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
425 /* try to set controlled mode for v2+ firmwares */
426 unsigned iControlledTry(0);
427 bool bControlled(false);
428 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
430 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
433 bReturn
= bControlled
;
438 /* try to read the build date */
439 m_commands
->RequestBuildDate();
441 SetInitialised(bReturn
);
445 bool CUSBCECAdapterCommunication::IsOpen(void)
447 /* thread is not being stopped, the port is open and the thread is running */
448 return !IsStopped() && m_port
->IsOpen() && IsRunning();
451 CStdString
CUSBCECAdapterCommunication::GetError(void) const
453 return m_port
->GetError();
456 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
458 CLockObject
lock(m_mutex
);
459 m_bInitialised
= bSetTo
;
462 bool CUSBCECAdapterCommunication::IsInitialised(void)
464 CLockObject
lock(m_mutex
);
465 return m_bInitialised
;
468 bool CUSBCECAdapterCommunication::StartBootloader(void)
470 if (m_port
->IsOpen() && m_commands
->StartBootloader())
478 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
480 if (m_iAckMask
== iMask
)
483 if (m_port
&& m_port
->IsOpen() && m_commands
->SetAckMask(iMask
))
492 uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
497 bool CUSBCECAdapterCommunication::PingAdapter(void)
499 return m_port
->IsOpen() ? m_commands
->PingAdapter() : false;
502 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
504 return m_commands
->GetFirmwareVersion();
507 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
509 return m_commands
->RequestBuildDate();
512 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
514 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
&&
515 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
;
518 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration
*configuration
)
520 return m_port
->IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
523 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
*configuration
)
525 return m_port
->IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
528 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
530 return m_port
->GetName();
533 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
535 return m_port
->IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
538 void *CAdapterPingThread::Process(void)
542 if (m_timeout
.TimeLeft() == 0)
544 /* reinit the timeout */
545 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
547 /* send a ping to the adapter */
549 int iFailedCounter(0);
550 while (!bPinged
&& iFailedCounter
< 3)
552 if (!m_com
->PingAdapter())
554 /* sleep and retry */
555 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
564 if (iFailedCounter
== 3)
566 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
567 m_com
->LIB_CEC
->AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
568 m_com
->StopThread(false);