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
47 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback
*callback
, const char *strPort
, uint16_t iBaudRate
/* = 38400 */) :
48 IAdapterCommunication(callback
),
51 m_lastPollDestination(CECDEVICE_UNKNOWN
),
52 m_bInitialised(false),
55 m_adapterMessageQueue(NULL
)
57 for (unsigned int iPtr
= 0; iPtr
< 15; iPtr
++)
58 m_bWaitingForAck
[iPtr
] = false;
59 m_port
= new CSerialPort(strPort
, iBaudRate
);
62 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
66 delete m_adapterMessageQueue
;
70 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = 10000 */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
72 bool bConnectionOpened(false);
74 CLockObject
lock(m_mutex
);
76 /* we need the port settings here */
79 CLibCEC::AddLog(CEC_LOG_ERROR
, "port is NULL");
80 return bConnectionOpened
;
83 /* return true when the port is already open */
86 CLibCEC::AddLog(CEC_LOG_WARNING
, "port is already open");
90 /* adapter commands */
92 m_commands
= new CUSBCECAdapterCommands(this);
94 if (!m_adapterMessageQueue
)
95 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
97 /* try to open the connection */
99 CTimeout
timeout(iTimeoutMs
);
100 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
102 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
104 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
107 /* and retry every 250ms until the timeout passed */
110 /* return false when we couldn't connect */
111 if (!bConnectionOpened
)
113 CLibCEC::AddLog(CEC_LOG_ERROR
, strError
);
117 CLibCEC::AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
123 bConnectionOpened
= false;
124 CLibCEC::AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
126 else if (!bSkipChecks
&& !CheckAdapter())
128 bConnectionOpened
= false;
129 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
131 else if (bStartListening
)
133 /* start a ping thread, that will ping the adapter every 15 seconds
134 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
135 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
136 if (m_pingThread
->CreateThread())
138 bConnectionOpened
= true;
142 bConnectionOpened
= false;
143 CLibCEC::AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
147 if (!bConnectionOpened
|| !bStartListening
)
150 return bConnectionOpened
;
153 void CUSBCECAdapterCommunication::Close(void)
155 /* set the ackmask to 0 before closing the connection */
159 if (m_commands
->GetFirmwareVersion() >= 2)
160 SetControlledMode(false);
163 /* stop and delete the ping thread */
165 m_pingThread
->StopThread(0);
169 /* stop the reader thread */
172 /* close and delete the com port connection */
177 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, uint8_t iMaxTries
, uint8_t iLineTimeout
/* = 3 */, uint8_t iRetryLineTimeout
/* = 3 */)
179 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
183 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iMaxTries
, iLineTimeout
, iRetryLineTimeout
);
185 /* mark as waiting for an ack from the destination */
186 MarkAsWaiting(data
.destination
);
188 /* send the message */
190 while (bRetry
&& ++output
->tries
< output
->maxTries
)
192 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
194 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
196 retVal
= output
->state
;
202 void *CUSBCECAdapterCommunication::Process(void)
204 CCECAdapterMessage msg
;
205 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread started");
209 /* read from the serial port */
210 if (!ReadFromDevice(50, 5))
213 /* TODO sleep 5 ms so other threads can get a lock */
217 m_adapterMessageQueue
->Clear();
218 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread ended");
222 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
224 bool bIsError(msg
.IsError());
225 cec_adapter_messagecode
messageCode(msg
.Message());
226 CLockObject
lock(m_mutex
);
228 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
230 m_lastPollDestination
= msg
.Destination();
231 if (msg
.Destination() < CECDEVICE_BROADCAST
)
233 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
236 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
239 m_bWaitingForAck
[msg
.Destination()] = false;
242 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
244 /* hack to suppress warnings when an LG is polling */
245 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
246 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
252 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
254 /* mark as waiting for an ack from the destination */
255 if (dest
< CECDEVICE_BROADCAST
)
257 CLockObject
lock(m_mutex
);
258 m_bWaitingForAck
[dest
] = true;
262 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = 1000 */)
264 CTimeout
timeout(iTimeout
);
266 ssize_t
iBytesRead(0);
267 bool bGotMsgEnd(true);
269 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
272 /* if something was received, wait for MSGEND */
273 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
274 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
278 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
281 bool bChanged(false);
283 /* only send the command if the timeout changed */
285 CLockObject
lock(m_mutex
);
286 bChanged
= (m_iLineTimeout
!= iTimeout
);
287 m_iLineTimeout
= iTimeout
;
291 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
296 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
298 CLockObject
adapterLock(m_mutex
);
299 if (!m_port
->IsOpen())
301 CLibCEC::AddLog(CEC_LOG_DEBUG
, "error writing command '%s' to the serial port: the connection is closed", CCECAdapterMessage::ToString(message
->Message()));
302 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
306 /* write the message */
307 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
309 CLibCEC::AddLog(CEC_LOG_DEBUG
, "error writing command '%s' to the serial port: %s", CCECAdapterMessage::ToString(message
->Message()), m_port
->GetError().c_str());
310 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
314 CLibCEC::AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
315 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
319 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
321 ssize_t
iBytesRead(0);
326 /* read from the serial port */
328 CLockObject
lock(m_mutex
);
331 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
334 if (iBytesRead
< 0 || iBytesRead
> 256)
336 CLibCEC::AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
340 else if (iBytesRead
> 0)
342 /* add the data to the current frame */
343 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
349 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
351 if (!m_port
|| !m_port
->IsOpen() ||
352 !m_adapterMessageQueue
)
355 /* create the adapter message for this command */
356 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
357 output
->PushBack(MSGSTART
);
358 output
->PushEscaped((uint8_t)msgCode
);
359 output
->Append(params
);
360 output
->PushBack(MSGEND
);
362 /* write the command */
363 if (!m_adapterMessageQueue
->Write(output
))
370 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
)
372 /* if the controller reported that the command was rejected, and we didn't send the command
373 to set controlled mode, then the controller probably switched to auto mode. set controlled
375 CLibCEC::AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
377 if (SetControlledMode(true))
378 return SendCommand(msgCode
, params
, true);
385 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = 10000 */)
388 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
390 /* try to ping the adapter */
392 unsigned iPingTry(0);
393 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
395 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
399 /* try to read the firmware version */
400 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
402 /* try to set controlled mode for v2+ firmwares */
403 unsigned iControlledTry(0);
404 bool bControlled(false);
405 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
407 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
410 bReturn
= bControlled
;
415 SetInitialised(bReturn
);
419 bool CUSBCECAdapterCommunication::IsOpen(void)
421 /* thread is not being stopped, the port is open and the thread is running */
422 return !IsStopped() && m_port
->IsOpen() && IsRunning();
425 CStdString
CUSBCECAdapterCommunication::GetError(void) const
427 return m_port
->GetError();
430 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
432 CLockObject
lock(m_mutex
);
433 m_bInitialised
= bSetTo
;
436 bool CUSBCECAdapterCommunication::IsInitialised(void)
438 CLockObject
lock(m_mutex
);
439 return m_bInitialised
;
442 bool CUSBCECAdapterCommunication::StartBootloader(void)
447 return m_commands
->StartBootloader();
450 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
452 return m_commands
->SetAckMask(iMask
);
455 bool CUSBCECAdapterCommunication::PingAdapter(void)
457 return m_commands
->PingAdapter();
460 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
462 return m_commands
->GetFirmwareVersion();
465 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration
*configuration
)
467 return m_commands
->PersistConfiguration(configuration
);
470 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
*configuration
)
472 return m_commands
->GetConfiguration(configuration
);
475 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
477 return m_port
->GetName();
480 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
482 return m_commands
->SetControlledMode(controlled
);
485 void *CAdapterPingThread::Process(void)
489 if (m_timeout
.TimeLeft() == 0)
491 /* reinit the timeout */
492 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
494 /* send a ping to the adapter */
496 int iFailedCounter(0);
497 while (!bPinged
&& iFailedCounter
< 3)
499 if (!m_com
->PingAdapter())
501 /* sleep 1 second and retry */
511 if (iFailedCounter
== 3)
513 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
514 CLibCEC::AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
515 m_com
->StopThread(false);