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
, bool &bRetry
, uint8_t iLineTimeout
)
179 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
183 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
185 /* mark as waiting for an ack from the destination */
186 MarkAsWaiting(data
.destination
);
188 /* send the message */
189 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
191 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
192 retVal
= output
->state
;
198 void *CUSBCECAdapterCommunication::Process(void)
200 CCECAdapterMessage msg
;
201 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread started");
205 /* read from the serial port */
206 if (!ReadFromDevice(50, 5))
209 /* TODO sleep 5 ms so other threads can get a lock */
213 m_adapterMessageQueue
->Clear();
214 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread ended");
218 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
220 bool bIsError(msg
.IsError());
221 cec_adapter_messagecode
messageCode(msg
.Message());
222 CLockObject
lock(m_mutex
);
224 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
226 m_lastPollDestination
= msg
.Destination();
227 if (msg
.Destination() < CECDEVICE_BROADCAST
)
229 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
232 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
235 m_bWaitingForAck
[msg
.Destination()] = false;
238 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
240 /* hack to suppress warnings when an LG is polling */
241 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
242 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
248 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
250 /* mark as waiting for an ack from the destination */
251 if (dest
< CECDEVICE_BROADCAST
)
253 CLockObject
lock(m_mutex
);
254 m_bWaitingForAck
[dest
] = true;
258 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = 1000 */)
260 CTimeout
timeout(iTimeout
);
262 ssize_t
iBytesRead(0);
263 bool bGotMsgEnd(true);
265 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
268 /* if something was received, wait for MSGEND */
269 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
270 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
274 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
277 bool bChanged(false);
279 /* only send the command if the timeout changed */
281 CLockObject
lock(m_mutex
);
282 bChanged
= (m_iLineTimeout
!= iTimeout
);
283 m_iLineTimeout
= iTimeout
;
287 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
292 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
294 CLockObject
adapterLock(m_mutex
);
295 if (!m_port
->IsOpen())
297 CLibCEC::AddLog(CEC_LOG_DEBUG
, "error writing command '%s' to the serial port: the connection is closed", CCECAdapterMessage::ToString(message
->Message()));
298 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
302 /* write the message */
303 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
305 CLibCEC::AddLog(CEC_LOG_DEBUG
, "error writing command '%s' to the serial port: %s", CCECAdapterMessage::ToString(message
->Message()), m_port
->GetError().c_str());
306 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
310 CLibCEC::AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
311 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
315 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
317 ssize_t
iBytesRead(0);
322 /* read from the serial port */
324 CLockObject
lock(m_mutex
);
327 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
330 if (iBytesRead
< 0 || iBytesRead
> 256)
332 CLibCEC::AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
336 else if (iBytesRead
> 0)
338 /* add the data to the current frame */
339 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
345 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
347 if (!m_port
|| !m_port
->IsOpen() ||
348 !m_adapterMessageQueue
)
351 /* create the adapter message for this command */
352 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
353 output
->PushBack(MSGSTART
);
354 output
->PushEscaped((uint8_t)msgCode
);
355 output
->Append(params
);
356 output
->PushBack(MSGEND
);
358 /* write the command */
359 if (!m_adapterMessageQueue
->Write(output
))
366 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
)
368 /* if the controller reported that the command was rejected, and we didn't send the command
369 to set controlled mode, then the controller probably switched to auto mode. set controlled
371 CLibCEC::AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
373 if (SetControlledMode(true))
374 return SendCommand(msgCode
, params
, true);
381 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = 10000 */)
384 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
386 /* try to ping the adapter */
388 unsigned iPingTry(0);
389 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
391 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
395 /* try to read the firmware version */
396 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
398 /* try to set controlled mode for v2+ firmwares */
399 unsigned iControlledTry(0);
400 bool bControlled(false);
401 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
403 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
406 bReturn
= bControlled
;
411 SetInitialised(bReturn
);
415 bool CUSBCECAdapterCommunication::IsOpen(void)
417 /* thread is not being stopped, the port is open and the thread is running */
418 return !IsStopped() && m_port
->IsOpen() && IsRunning();
421 CStdString
CUSBCECAdapterCommunication::GetError(void) const
423 return m_port
->GetError();
426 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
428 CLockObject
lock(m_mutex
);
429 m_bInitialised
= bSetTo
;
432 bool CUSBCECAdapterCommunication::IsInitialised(void)
434 CLockObject
lock(m_mutex
);
435 return m_bInitialised
;
438 bool CUSBCECAdapterCommunication::StartBootloader(void)
443 return m_commands
->StartBootloader();
446 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
448 return m_commands
->SetAckMask(iMask
);
451 bool CUSBCECAdapterCommunication::PingAdapter(void)
453 return m_commands
->PingAdapter();
456 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
458 return m_commands
->GetFirmwareVersion();
461 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration
*configuration
)
463 return m_commands
->PersistConfiguration(configuration
);
466 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
*configuration
)
468 return m_commands
->GetConfiguration(configuration
);
471 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
473 return m_port
->GetName();
476 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
478 return m_commands
->SetControlledMode(controlled
);
481 void *CAdapterPingThread::Process(void)
485 if (m_timeout
.TimeLeft() == 0)
487 /* reinit the timeout */
488 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
490 /* send a ping to the adapter */
492 int iFailedCounter(0);
493 while (!bPinged
&& iFailedCounter
< 3)
495 if (!m_com
->PingAdapter())
497 /* sleep 1 second and retry */
507 if (iFailedCounter
== 3)
509 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
510 CLibCEC::AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
511 m_com
->StopThread(false);