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 */
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 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread started");
221 /* read from the serial port */
222 if (!ReadFromDevice(50, 5))
224 libcec_parameter param
;
225 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
226 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
231 /* TODO sleep 5 ms so other threads can get a lock */
235 m_adapterMessageQueue
->Clear();
236 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread ended");
240 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
242 bool bIsError(msg
.IsError());
243 cec_adapter_messagecode
messageCode(msg
.Message());
244 CLockObject
lock(m_mutex
);
246 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
248 m_lastPollDestination
= msg
.Destination();
249 if (msg
.Destination() < CECDEVICE_BROADCAST
)
251 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
254 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
257 m_bWaitingForAck
[msg
.Destination()] = false;
260 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
262 /* hack to suppress warnings when an LG is polling */
263 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
264 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
270 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
272 /* mark as waiting for an ack from the destination */
273 if (dest
< CECDEVICE_BROADCAST
)
275 CLockObject
lock(m_mutex
);
276 m_bWaitingForAck
[dest
] = true;
280 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
282 CTimeout
timeout(iTimeout
);
284 ssize_t
iBytesRead(0);
285 bool bGotMsgEnd(true);
287 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
290 /* if something was received, wait for MSGEND */
291 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
292 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
296 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
299 bool bChanged(false);
301 /* only send the command if the timeout changed */
303 CLockObject
lock(m_mutex
);
304 bChanged
= (m_iLineTimeout
!= iTimeout
);
305 m_iLineTimeout
= iTimeout
;
309 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
314 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
316 CLockObject
adapterLock(m_mutex
);
317 if (!m_port
->IsOpen())
319 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());
320 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
324 /* write the message */
325 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
327 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());
328 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
333 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
334 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
338 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
340 ssize_t
iBytesRead(0);
345 /* read from the serial port */
347 CLockObject
lock(m_mutex
);
348 if (!m_port
|| !m_port
->IsOpen())
351 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
353 if (m_port
->GetErrorNumber())
355 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
361 if (iBytesRead
< 0 || iBytesRead
> 256)
363 else if (iBytesRead
> 0)
365 /* add the data to the current frame */
366 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
372 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
374 if (!m_port
|| !m_port
->IsOpen() ||
375 !m_adapterMessageQueue
)
378 /* create the adapter message for this command */
379 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
380 output
->PushBack(MSGSTART
);
381 output
->PushEscaped((uint8_t)msgCode
);
382 output
->Append(params
);
383 output
->PushBack(MSGEND
);
385 /* write the command */
386 if (!m_adapterMessageQueue
->Write(output
))
388 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
390 libcec_parameter param
;
391 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
392 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
400 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
401 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
403 /* if the controller reported that the command was rejected, and we didn't send the command
404 to set controlled mode, then the controller probably switched to auto mode. set controlled
406 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
408 if (SetControlledMode(true))
409 return SendCommand(msgCode
, params
, true);
416 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
419 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
421 /* try to ping the adapter */
423 unsigned iPingTry(0);
424 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
426 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
430 /* try to read the firmware version */
431 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
433 /* try to set controlled mode for v2+ firmwares */
434 unsigned iControlledTry(0);
435 bool bControlled(false);
436 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
438 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
441 bReturn
= bControlled
;
446 /* try to read the build date */
447 m_commands
->RequestBuildDate();
449 SetInitialised(bReturn
);
453 bool CUSBCECAdapterCommunication::IsOpen(void)
455 /* thread is not being stopped, the port is open and the thread is running */
456 return !IsStopped() && m_port
->IsOpen() && IsRunning();
459 CStdString
CUSBCECAdapterCommunication::GetError(void) const
461 return m_port
->GetError();
464 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
466 CLockObject
lock(m_mutex
);
467 m_bInitialised
= bSetTo
;
470 bool CUSBCECAdapterCommunication::IsInitialised(void)
472 CLockObject
lock(m_mutex
);
473 return m_bInitialised
;
476 bool CUSBCECAdapterCommunication::StartBootloader(void)
478 if (m_port
->IsOpen() && m_commands
->StartBootloader())
486 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
488 if (m_iAckMask
== iMask
)
491 if (m_port
&& m_port
->IsOpen() && m_commands
->SetAckMask(iMask
))
500 uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
505 bool CUSBCECAdapterCommunication::PingAdapter(void)
507 return m_port
->IsOpen() ? m_commands
->PingAdapter() : false;
510 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
512 return m_commands
->GetFirmwareVersion();
515 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
517 return m_commands
->RequestBuildDate();
520 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
522 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
&&
523 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
;
526 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration
*configuration
)
528 return m_port
->IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
531 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
*configuration
)
533 return m_port
->IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
536 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
538 return m_port
->GetName();
541 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
543 return m_port
->IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
546 void *CAdapterPingThread::Process(void)
550 if (m_timeout
.TimeLeft() == 0)
552 /* reinit the timeout */
553 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
555 /* send a ping to the adapter */
557 int iFailedCounter(0);
558 while (!bPinged
&& iFailedCounter
< 3)
560 if (!m_com
->PingAdapter())
562 /* sleep and retry */
563 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
572 if (iFailedCounter
== 3)
574 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
575 m_com
->LIB_CEC
->AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
576 m_com
->StopThread(false);