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
;
69 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = 10000 */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
71 bool bConnectionOpened(false);
73 CLockObject
lock(m_mutex
);
75 /* we need the port settings here */
78 CLibCEC::AddLog(CEC_LOG_ERROR
, "port is NULL");
79 return bConnectionOpened
;
82 /* return true when the port is already open */
85 CLibCEC::AddLog(CEC_LOG_WARNING
, "port is already open");
89 /* adapter commands */
91 m_commands
= new CUSBCECAdapterCommands(this);
93 if (!m_adapterMessageQueue
)
94 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
96 /* try to open the connection */
98 CTimeout
timeout(iTimeoutMs
);
99 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
101 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
103 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
106 /* and retry every 250ms until the timeout passed */
109 /* return false when we couldn't connect */
110 if (!bConnectionOpened
)
112 CLibCEC::AddLog(CEC_LOG_ERROR
, strError
);
116 CLibCEC::AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
122 bConnectionOpened
= false;
123 CLibCEC::AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
125 else if (!bSkipChecks
&& !CheckAdapter())
127 bConnectionOpened
= false;
128 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
130 else if (bStartListening
)
132 /* start a ping thread, that will ping the adapter every 15 seconds
133 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
134 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
135 if (m_pingThread
->CreateThread())
137 bConnectionOpened
= true;
141 bConnectionOpened
= false;
142 CLibCEC::AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
146 if (!bConnectionOpened
|| !bStartListening
)
148 if (!bConnectionOpened
)
154 return bConnectionOpened
;
157 void CUSBCECAdapterCommunication::Close(void)
159 /* set the ackmask to 0 before closing the connection */
163 if (m_commands
->GetFirmwareVersion() >= 2)
164 SetControlledMode(false);
167 /* stop and delete the ping thread */
169 m_pingThread
->StopThread(0);
173 /* stop the reader thread */
176 /* close and delete the com port connection */
181 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, uint8_t iMaxTries
, uint8_t iLineTimeout
/* = 3 */, uint8_t iRetryLineTimeout
/* = 3 */)
183 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
187 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iMaxTries
, iLineTimeout
, iRetryLineTimeout
);
189 /* mark as waiting for an ack from the destination */
190 MarkAsWaiting(data
.destination
);
192 /* send the message */
194 while (bRetry
&& ++output
->tries
< output
->maxTries
)
196 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
198 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
200 retVal
= output
->state
;
206 void *CUSBCECAdapterCommunication::Process(void)
208 CCECAdapterMessage msg
;
209 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread started");
213 /* read from the serial port */
214 if (!ReadFromDevice(50, 5))
217 /* TODO sleep 5 ms so other threads can get a lock */
221 m_adapterMessageQueue
->Clear();
222 CLibCEC::AddLog(CEC_LOG_DEBUG
, "communication thread ended");
226 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
228 bool bIsError(msg
.IsError());
229 cec_adapter_messagecode
messageCode(msg
.Message());
230 CLockObject
lock(m_mutex
);
232 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
234 m_lastPollDestination
= msg
.Destination();
235 if (msg
.Destination() < CECDEVICE_BROADCAST
)
237 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
240 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
243 m_bWaitingForAck
[msg
.Destination()] = false;
246 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
248 /* hack to suppress warnings when an LG is polling */
249 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
250 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
256 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
258 /* mark as waiting for an ack from the destination */
259 if (dest
< CECDEVICE_BROADCAST
)
261 CLockObject
lock(m_mutex
);
262 m_bWaitingForAck
[dest
] = true;
266 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = 1000 */)
268 CTimeout
timeout(iTimeout
);
270 ssize_t
iBytesRead(0);
271 bool bGotMsgEnd(false);
273 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
275 /* if something was received, wait for MSGEND */
276 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
277 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
281 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
284 bool bChanged(false);
286 /* only send the command if the timeout changed */
288 CLockObject
lock(m_mutex
);
289 bChanged
= (m_iLineTimeout
!= iTimeout
);
290 m_iLineTimeout
= iTimeout
;
294 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
299 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
301 CLockObject
adapterLock(m_mutex
);
302 if (!m_port
->IsOpen())
304 CLibCEC::AddLog(CEC_LOG_DEBUG
, "error writing command '%s' to the serial port: the connection is closed", CCECAdapterMessage::ToString(message
->Message()));
305 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
309 /* write the message */
310 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
312 CLibCEC::AddLog(CEC_LOG_DEBUG
, "error writing command '%s' to the serial port: %s", CCECAdapterMessage::ToString(message
->Message()), m_port
->GetError().c_str());
313 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
317 CLibCEC::AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
318 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
322 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
324 ssize_t
iBytesRead(0);
329 /* read from the serial port */
331 CLockObject
lock(m_mutex
);
334 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
337 if (iBytesRead
< 0 || iBytesRead
> 256)
339 CLibCEC::AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
343 else if (iBytesRead
> 0)
345 /* add the data to the current frame */
346 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
352 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
354 if (!m_port
|| !m_port
->IsOpen() ||
355 !m_adapterMessageQueue
)
358 /* create the adapter message for this command */
359 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
360 output
->PushBack(MSGSTART
);
361 output
->PushEscaped((uint8_t)msgCode
);
362 output
->Append(params
);
363 output
->PushBack(MSGEND
);
365 /* write the command */
366 if (!m_adapterMessageQueue
->Write(output
))
373 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
)
375 /* if the controller reported that the command was rejected, and we didn't send the command
376 to set controlled mode, then the controller probably switched to auto mode. set controlled
378 CLibCEC::AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
380 if (SetControlledMode(true))
381 return SendCommand(msgCode
, params
, true);
388 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = 10000 */)
391 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
393 /* try to ping the adapter */
395 unsigned iPingTry(0);
396 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
398 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
402 /* try to read the firmware version */
403 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
405 /* try to set controlled mode for v2+ firmwares */
406 unsigned iControlledTry(0);
407 bool bControlled(false);
408 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
410 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
413 bReturn
= bControlled
;
418 SetInitialised(bReturn
);
422 bool CUSBCECAdapterCommunication::IsOpen(void)
424 /* thread is not being stopped, the port is open and the thread is running */
425 return !IsStopped() && m_port
->IsOpen() && IsRunning();
428 CStdString
CUSBCECAdapterCommunication::GetError(void) const
430 return m_port
->GetError();
433 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
435 CLockObject
lock(m_mutex
);
436 m_bInitialised
= bSetTo
;
439 bool CUSBCECAdapterCommunication::IsInitialised(void)
441 CLockObject
lock(m_mutex
);
442 return m_bInitialised
;
445 bool CUSBCECAdapterCommunication::StartBootloader(void)
450 return m_commands
->StartBootloader();
453 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
455 return m_commands
->SetAckMask(iMask
);
458 bool CUSBCECAdapterCommunication::PingAdapter(void)
460 return m_commands
->PingAdapter();
463 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
465 return m_commands
->GetFirmwareVersion();
468 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration
*configuration
)
470 return m_commands
->PersistConfiguration(configuration
);
473 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
*configuration
)
475 return m_commands
->GetConfiguration(configuration
);
478 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
480 return m_port
->GetName();
483 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
485 return m_commands
->SetControlledMode(controlled
);
488 void *CAdapterPingThread::Process(void)
492 if (m_timeout
.TimeLeft() == 0)
494 /* reinit the timeout */
495 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
497 /* send a ping to the adapter */
499 int iFailedCounter(0);
500 while (!bPinged
&& iFailedCounter
< 3)
502 if (!m_com
->PingAdapter())
504 /* sleep 1 second and retry */
514 if (iFailedCounter
== 3)
516 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
517 CLibCEC::AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
518 m_com
->StopThread(false);