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
)
96 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
97 m_adapterMessageQueue
->CreateThread();
100 /* try to open the connection */
102 CTimeout
timeout(iTimeoutMs
);
103 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
105 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
107 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
110 /* and retry every 250ms until the timeout passed */
113 /* return false when we couldn't connect */
114 if (!bConnectionOpened
)
116 CLibCEC::AddLog(CEC_LOG_ERROR
, strError
);
120 CLibCEC::AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
126 bConnectionOpened
= false;
127 CLibCEC::AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
129 else if (!bSkipChecks
&& !CheckAdapter())
131 bConnectionOpened
= false;
132 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
134 else if (bStartListening
)
136 /* start a ping thread, that will ping the adapter every 15 seconds
137 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
138 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
139 if (m_pingThread
->CreateThread())
141 bConnectionOpened
= true;
145 bConnectionOpened
= false;
146 CLibCEC::AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
150 if (!bConnectionOpened
|| !bStartListening
)
153 return bConnectionOpened
;
156 void CUSBCECAdapterCommunication::Close(void)
158 /* stop the reader thread */
161 CLockObject
lock(m_mutex
);
163 /* set the ackmask to 0 before closing the connection */
164 if (IsRunning() && m_port
->IsOpen() && m_port
->GetErrorNumber() == 0)
166 CLibCEC::AddLog(CEC_LOG_DEBUG
, "%s - closing the connection", __FUNCTION__
);
168 if (m_commands
->GetFirmwareVersion() >= 2)
169 SetControlledMode(false);
172 m_adapterMessageQueue
->Clear();
174 /* stop and delete the ping thread */
176 m_pingThread
->StopThread(0);
180 /* close and delete the com port connection */
184 libcec_parameter param
;
185 CLibCEC::Alert(CEC_ALERT_CONNECTION_LOST
, param
);
188 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
)
190 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
194 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
196 /* mark as waiting for an ack from the destination */
197 MarkAsWaiting(data
.destination
);
199 /* send the message */
200 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
202 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
203 retVal
= output
->state
;
209 void *CUSBCECAdapterCommunication::Process(void)
211 CCECAdapterMessage msg
;
212 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread started");
216 /* read from the serial port */
217 if (!ReadFromDevice(50, 5))
220 /* TODO sleep 5 ms so other threads can get a lock */
224 m_adapterMessageQueue
->Clear();
225 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread ended");
229 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
231 bool bIsError(msg
.IsError());
232 cec_adapter_messagecode
messageCode(msg
.Message());
233 CLockObject
lock(m_mutex
);
235 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
237 m_lastPollDestination
= msg
.Destination();
238 if (msg
.Destination() < CECDEVICE_BROADCAST
)
240 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
243 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
246 m_bWaitingForAck
[msg
.Destination()] = false;
249 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
251 /* hack to suppress warnings when an LG is polling */
252 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
253 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
259 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
261 /* mark as waiting for an ack from the destination */
262 if (dest
< CECDEVICE_BROADCAST
)
264 CLockObject
lock(m_mutex
);
265 m_bWaitingForAck
[dest
] = true;
269 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = 1000 */)
271 CTimeout
timeout(iTimeout
);
273 ssize_t
iBytesRead(0);
274 bool bGotMsgEnd(true);
276 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
279 /* if something was received, wait for MSGEND */
280 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
281 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
285 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
288 bool bChanged(false);
290 /* only send the command if the timeout changed */
292 CLockObject
lock(m_mutex
);
293 bChanged
= (m_iLineTimeout
!= iTimeout
);
294 m_iLineTimeout
= iTimeout
;
298 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
303 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
305 CLockObject
adapterLock(m_mutex
);
306 if (!m_port
->IsOpen())
308 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());
309 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
313 /* write the message */
314 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
316 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());
317 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
322 CLibCEC::AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
323 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
327 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
329 ssize_t
iBytesRead(0);
334 /* read from the serial port */
336 CLockObject
lock(m_mutex
);
337 if (!m_port
|| !m_port
->IsOpen())
340 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
342 if (m_port
->GetErrorNumber())
344 CLibCEC::AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
350 if (iBytesRead
< 0 || iBytesRead
> 256)
352 else if (iBytesRead
> 0)
354 /* add the data to the current frame */
355 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
361 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
363 if (!m_port
|| !m_port
->IsOpen() ||
364 !m_adapterMessageQueue
)
367 /* create the adapter message for this command */
368 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
369 output
->PushBack(MSGSTART
);
370 output
->PushEscaped((uint8_t)msgCode
);
371 output
->Append(params
);
372 output
->PushBack(MSGEND
);
374 /* write the command */
375 if (!m_adapterMessageQueue
->Write(output
))
377 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
383 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
384 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
386 /* if the controller reported that the command was rejected, and we didn't send the command
387 to set controlled mode, then the controller probably switched to auto mode. set controlled
389 CLibCEC::AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
391 if (SetControlledMode(true))
392 return SendCommand(msgCode
, params
, true);
399 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = 10000 */)
402 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
404 /* try to ping the adapter */
406 unsigned iPingTry(0);
407 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
409 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
413 /* try to read the firmware version */
414 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
416 /* try to set controlled mode for v2+ firmwares */
417 unsigned iControlledTry(0);
418 bool bControlled(false);
419 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
421 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
424 bReturn
= bControlled
;
429 SetInitialised(bReturn
);
433 bool CUSBCECAdapterCommunication::IsOpen(void)
435 /* thread is not being stopped, the port is open and the thread is running */
436 return !IsStopped() && m_port
->IsOpen() && IsRunning();
439 CStdString
CUSBCECAdapterCommunication::GetError(void) const
441 return m_port
->GetError();
444 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
446 CLockObject
lock(m_mutex
);
447 m_bInitialised
= bSetTo
;
450 bool CUSBCECAdapterCommunication::IsInitialised(void)
452 CLockObject
lock(m_mutex
);
453 return m_bInitialised
;
456 bool CUSBCECAdapterCommunication::StartBootloader(void)
458 return m_port
->IsOpen() ? m_commands
->StartBootloader() : false;
461 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
463 return m_port
->IsOpen() ? m_commands
->SetAckMask(iMask
) : false;
466 bool CUSBCECAdapterCommunication::PingAdapter(void)
468 return m_port
->IsOpen() ? m_commands
->PingAdapter() : false;
471 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
473 return m_commands
->GetFirmwareVersion();
476 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
478 return m_commands
->RequestBuildDate();
481 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration
*configuration
)
483 return m_port
->IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
486 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
*configuration
)
488 return m_port
->IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
491 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
493 return m_port
->GetName();
496 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
498 return m_port
->IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
501 void *CAdapterPingThread::Process(void)
505 if (m_timeout
.TimeLeft() == 0)
507 /* reinit the timeout */
508 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
510 /* send a ping to the adapter */
512 int iFailedCounter(0);
513 while (!bPinged
&& iFailedCounter
< 3)
515 if (!m_com
->PingAdapter())
517 /* sleep 1 second and retry */
527 if (iFailedCounter
== 3)
529 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
530 CLibCEC::AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
531 m_com
->StopThread(false);