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
)
385 /* if the controller reported that the command was rejected, and we didn't send the command
386 to set controlled mode, then the controller probably switched to auto mode. set controlled
388 CLibCEC::AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
390 if (SetControlledMode(true))
391 return SendCommand(msgCode
, params
, true);
398 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = 10000 */)
401 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
403 /* try to ping the adapter */
405 unsigned iPingTry(0);
406 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
408 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
412 /* try to read the firmware version */
413 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
415 /* try to set controlled mode for v2+ firmwares */
416 unsigned iControlledTry(0);
417 bool bControlled(false);
418 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
420 CLibCEC::AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
423 bReturn
= bControlled
;
428 SetInitialised(bReturn
);
432 bool CUSBCECAdapterCommunication::IsOpen(void)
434 /* thread is not being stopped, the port is open and the thread is running */
435 return !IsStopped() && m_port
->IsOpen() && IsRunning();
438 CStdString
CUSBCECAdapterCommunication::GetError(void) const
440 return m_port
->GetError();
443 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
445 CLockObject
lock(m_mutex
);
446 m_bInitialised
= bSetTo
;
449 bool CUSBCECAdapterCommunication::IsInitialised(void)
451 CLockObject
lock(m_mutex
);
452 return m_bInitialised
;
455 bool CUSBCECAdapterCommunication::StartBootloader(void)
457 return m_port
->IsOpen() ? m_commands
->StartBootloader() : false;
460 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
462 return m_port
->IsOpen() ? m_commands
->SetAckMask(iMask
) : false;
465 bool CUSBCECAdapterCommunication::PingAdapter(void)
467 return m_port
->IsOpen() ? m_commands
->PingAdapter() : false;
470 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
472 return m_commands
->GetFirmwareVersion();
475 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
477 return m_commands
->RequestBuildDate();
480 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration
*configuration
)
482 return m_port
->IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
485 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
*configuration
)
487 return m_port
->IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
490 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
492 return m_port
->GetName();
495 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
497 return m_port
->IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
500 void *CAdapterPingThread::Process(void)
504 if (m_timeout
.TimeLeft() == 0)
506 /* reinit the timeout */
507 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
509 /* send a ping to the adapter */
511 int iFailedCounter(0);
512 while (!bPinged
&& iFailedCounter
< 3)
514 if (!m_com
->PingAdapter())
516 /* sleep 1 second and retry */
526 if (iFailedCounter
== 3)
528 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
529 CLibCEC::AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
530 m_com
->StopThread(false);