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 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback
*callback
, const char *strPort
, uint16_t iBaudRate
/* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
53 IAdapterCommunication(callback
),
56 m_lastPollDestination(CECDEVICE_UNKNOWN
),
57 m_bInitialised(false),
60 m_adapterMessageQueue(NULL
)
62 for (unsigned int iPtr
= CECDEVICE_TV
; iPtr
< CECDEVICE_BROADCAST
; iPtr
++)
63 m_bWaitingForAck
[iPtr
] = false;
64 m_port
= new CSerialPort(strPort
, iBaudRate
);
67 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
71 delete m_adapterMessageQueue
;
75 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
77 bool bConnectionOpened(false);
79 CLockObject
lock(m_mutex
);
81 /* we need the port settings here */
84 CLibCEC::AddLog(CEC_LOG_ERROR
, "port is NULL");
85 return bConnectionOpened
;
88 /* return true when the port is already open */
91 CLibCEC::AddLog(CEC_LOG_WARNING
, "port is already open");
95 /* adapter commands */
97 m_commands
= new CUSBCECAdapterCommands(this);
99 if (!m_adapterMessageQueue
)
101 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
102 m_adapterMessageQueue
->CreateThread();
105 /* try to open the connection */
107 CTimeout
timeout(iTimeoutMs
);
108 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
110 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
112 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
115 /* and retry every 250ms until the timeout passed */
118 /* return false when we couldn't connect */
119 if (!bConnectionOpened
)
121 CLibCEC::AddLog(CEC_LOG_ERROR
, strError
);
125 CLibCEC::AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
131 bConnectionOpened
= false;
132 CLibCEC::AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
134 else if (!bSkipChecks
&& !CheckAdapter())
136 bConnectionOpened
= false;
137 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
139 else if (bStartListening
)
141 /* start a ping thread, that will ping the adapter every 15 seconds
142 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
143 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
144 if (m_pingThread
->CreateThread())
146 bConnectionOpened
= true;
150 bConnectionOpened
= false;
151 CLibCEC::AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
155 if (!bConnectionOpened
|| !bStartListening
)
158 return bConnectionOpened
;
161 void CUSBCECAdapterCommunication::Close(void)
163 /* stop the reader thread */
166 CLockObject
lock(m_mutex
);
168 /* set the ackmask to 0 before closing the connection */
169 if (IsRunning() && m_port
->IsOpen() && m_port
->GetErrorNumber() == 0)
171 CLibCEC::AddLog(CEC_LOG_DEBUG
, "%s - closing the connection", __FUNCTION__
);
173 if (m_commands
->GetFirmwareVersion() >= 2)
174 SetControlledMode(false);
177 m_adapterMessageQueue
->Clear();
179 /* stop and delete the ping thread */
181 m_pingThread
->StopThread(0);
185 /* close and delete the com port connection */
189 libcec_parameter param
;
190 CLibCEC::Alert(CEC_ALERT_CONNECTION_LOST
, param
);
193 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
)
195 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
199 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
201 /* mark as waiting for an ack from the destination */
202 MarkAsWaiting(data
.destination
);
204 /* send the message */
205 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
207 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
208 retVal
= output
->state
;
214 void *CUSBCECAdapterCommunication::Process(void)
216 CCECAdapterMessage msg
;
217 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread started");
221 /* read from the serial port */
222 if (!ReadFromDevice(50, 5))
225 /* TODO sleep 5 ms so other threads can get a lock */
229 m_adapterMessageQueue
->Clear();
230 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread ended");
234 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
236 bool bIsError(msg
.IsError());
237 cec_adapter_messagecode
messageCode(msg
.Message());
238 CLockObject
lock(m_mutex
);
240 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
242 m_lastPollDestination
= msg
.Destination();
243 if (msg
.Destination() < CECDEVICE_BROADCAST
)
245 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
248 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
251 m_bWaitingForAck
[msg
.Destination()] = false;
254 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
256 /* hack to suppress warnings when an LG is polling */
257 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
258 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
264 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
266 /* mark as waiting for an ack from the destination */
267 if (dest
< CECDEVICE_BROADCAST
)
269 CLockObject
lock(m_mutex
);
270 m_bWaitingForAck
[dest
] = true;
274 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
276 CTimeout
timeout(iTimeout
);
278 ssize_t
iBytesRead(0);
279 bool bGotMsgEnd(true);
281 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
284 /* if something was received, wait for MSGEND */
285 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
286 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
290 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
293 bool bChanged(false);
295 /* only send the command if the timeout changed */
297 CLockObject
lock(m_mutex
);
298 bChanged
= (m_iLineTimeout
!= iTimeout
);
299 m_iLineTimeout
= iTimeout
;
303 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
308 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
310 CLockObject
adapterLock(m_mutex
);
311 if (!m_port
->IsOpen())
313 CLibCEC::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());
314 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
318 /* write the message */
319 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
321 CLibCEC::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());
322 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
327 CLibCEC::AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
328 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
332 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
334 ssize_t
iBytesRead(0);
339 /* read from the serial port */
341 CLockObject
lock(m_mutex
);
342 if (!m_port
|| !m_port
->IsOpen())
345 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
347 if (m_port
->GetErrorNumber())
349 CLibCEC::AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
355 if (iBytesRead
< 0 || iBytesRead
> 256)
357 else if (iBytesRead
> 0)
359 /* add the data to the current frame */
360 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
366 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
368 if (!m_port
|| !m_port
->IsOpen() ||
369 !m_adapterMessageQueue
)
372 /* create the adapter message for this command */
373 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
374 output
->PushBack(MSGSTART
);
375 output
->PushEscaped((uint8_t)msgCode
);
376 output
->Append(params
);
377 output
->PushBack(MSGEND
);
379 /* write the command */
380 if (!m_adapterMessageQueue
->Write(output
))
382 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
388 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
389 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
391 /* if the controller reported that the command was rejected, and we didn't send the command
392 to set controlled mode, then the controller probably switched to auto mode. set controlled
394 CLibCEC::AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
396 if (SetControlledMode(true))
397 return SendCommand(msgCode
, params
, true);
404 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
407 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
409 /* try to ping the adapter */
411 unsigned iPingTry(0);
412 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
414 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
418 /* try to read the firmware version */
419 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
421 /* try to set controlled mode for v2+ firmwares */
422 unsigned iControlledTry(0);
423 bool bControlled(false);
424 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
426 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
429 bReturn
= bControlled
;
434 /* try to read the build date */
435 m_commands
->RequestBuildDate();
437 SetInitialised(bReturn
);
441 bool CUSBCECAdapterCommunication::IsOpen(void)
443 /* thread is not being stopped, the port is open and the thread is running */
444 return !IsStopped() && m_port
->IsOpen() && IsRunning();
447 CStdString
CUSBCECAdapterCommunication::GetError(void) const
449 return m_port
->GetError();
452 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
454 CLockObject
lock(m_mutex
);
455 m_bInitialised
= bSetTo
;
458 bool CUSBCECAdapterCommunication::IsInitialised(void)
460 CLockObject
lock(m_mutex
);
461 return m_bInitialised
;
464 bool CUSBCECAdapterCommunication::StartBootloader(void)
466 if (m_port
->IsOpen() && m_commands
->StartBootloader())
474 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
476 return m_port
->IsOpen() ? m_commands
->SetAckMask(iMask
) : false;
479 bool CUSBCECAdapterCommunication::PingAdapter(void)
481 return m_port
->IsOpen() ? m_commands
->PingAdapter() : false;
484 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
486 return m_commands
->GetFirmwareVersion();
489 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
491 return m_commands
->RequestBuildDate();
494 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
496 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
&&
497 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
;
500 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration
*configuration
)
502 return m_port
->IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
505 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
*configuration
)
507 return m_port
->IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
510 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
512 return m_port
->GetName();
515 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
517 return m_port
->IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
520 void *CAdapterPingThread::Process(void)
524 if (m_timeout
.TimeLeft() == 0)
526 /* reinit the timeout */
527 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
529 /* send a ping to the adapter */
531 int iFailedCounter(0);
532 while (!bPinged
&& iFailedCounter
< 3)
534 if (!m_com
->PingAdapter())
536 /* sleep and retry */
537 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
546 if (iFailedCounter
== 3)
548 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
549 CLibCEC::AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
550 m_com
->StopThread(false);