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
48 #define CEC_LATEST_ADAPTER_FW_VERSION 2
49 // firmware date Thu Apr 26 20:14:49 2012 +0000
50 #define CEC_LATEST_ADAPTER_FW_DATE 0x4F99ACB9
52 #define LIB_CEC m_callback->GetLib()
54 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback
*callback
, const char *strPort
, uint16_t iBaudRate
/* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
55 IAdapterCommunication(callback
),
58 m_lastPollDestination(CECDEVICE_UNKNOWN
),
59 m_bInitialised(false),
62 m_adapterMessageQueue(NULL
),
65 for (unsigned int iPtr
= CECDEVICE_TV
; iPtr
< CECDEVICE_BROADCAST
; iPtr
++)
66 m_bWaitingForAck
[iPtr
] = false;
67 m_port
= new CSerialPort(strPort
, iBaudRate
);
70 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
74 delete m_adapterMessageQueue
;
78 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
80 bool bConnectionOpened(false);
82 CLockObject
lock(m_mutex
);
84 /* we need the port settings here */
87 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "port is NULL");
88 return bConnectionOpened
;
91 /* return true when the port is already open */
94 LIB_CEC
->AddLog(CEC_LOG_WARNING
, "port is already open");
98 /* adapter commands */
100 m_commands
= new CUSBCECAdapterCommands(this);
102 if (!m_adapterMessageQueue
)
104 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
105 m_adapterMessageQueue
->CreateThread();
108 /* try to open the connection */
110 CTimeout
timeout(iTimeoutMs
);
111 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
113 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
115 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
118 /* and retry every 250ms until the timeout passed */
121 /* return false when we couldn't connect */
122 if (!bConnectionOpened
)
124 LIB_CEC
->AddLog(CEC_LOG_ERROR
, strError
);
126 if (m_port
->GetErrorNumber() == EACCES
)
128 libcec_parameter param
;
129 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
130 param
.paramData
= (void*)"No permission to open the device";
131 LIB_CEC
->Alert(CEC_ALERT_PERMISSION_ERROR
, param
);
133 else if (m_port
->GetErrorNumber() == EBUSY
)
135 libcec_parameter param
;
136 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
137 param
.paramData
= (void*)"The serial port is busy. Only one program can access the device directly.";
138 LIB_CEC
->Alert(CEC_ALERT_PORT_BUSY
, param
);
143 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
149 bConnectionOpened
= false;
150 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
152 else if (!bSkipChecks
&& !CheckAdapter())
154 bConnectionOpened
= false;
155 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
157 else if (bStartListening
)
159 /* start a ping thread, that will ping the adapter every 15 seconds
160 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
161 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
162 if (m_pingThread
->CreateThread())
164 bConnectionOpened
= true;
168 bConnectionOpened
= false;
169 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
173 if (!bConnectionOpened
|| !bStartListening
)
176 return bConnectionOpened
;
179 void CUSBCECAdapterCommunication::Close(void)
181 /* stop the reader thread */
184 CLockObject
lock(m_mutex
);
186 /* set the ackmask to 0 before closing the connection */
187 if (IsOpen() && m_port
->GetErrorNumber() == 0)
189 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - closing the connection", __FUNCTION__
);
191 if (m_commands
->GetFirmwareVersion() >= 2)
192 SetControlledMode(false);
195 m_adapterMessageQueue
->Clear();
197 /* stop and delete the ping thread */
199 m_pingThread
->StopThread(0);
203 /* close and delete the com port connection */
208 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
)
210 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
214 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
216 /* mark as waiting for an ack from the destination */
217 MarkAsWaiting(data
.destination
);
219 /* send the message */
220 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
222 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
223 retVal
= output
->state
;
229 void *CUSBCECAdapterCommunication::Process(void)
231 CCECAdapterMessage msg
;
232 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread started");
236 /* read from the serial port */
237 if (!ReadFromDevice(50, 5))
239 libcec_parameter param
;
240 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
241 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
246 /* TODO sleep 5 ms so other threads can get a lock */
250 m_adapterMessageQueue
->Clear();
251 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread ended");
255 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
257 bool bIsError(msg
.IsError());
258 cec_adapter_messagecode
messageCode(msg
.Message());
259 CLockObject
lock(m_mutex
);
261 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
263 m_lastPollDestination
= msg
.Destination();
264 if (msg
.Destination() < CECDEVICE_BROADCAST
)
266 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
269 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
272 m_bWaitingForAck
[msg
.Destination()] = false;
275 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
277 /* hack to suppress warnings when an LG is polling */
278 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
279 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
285 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
287 /* mark as waiting for an ack from the destination */
288 if (dest
< CECDEVICE_BROADCAST
)
290 CLockObject
lock(m_mutex
);
291 m_bWaitingForAck
[dest
] = true;
295 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
297 CTimeout
timeout(iTimeout
);
299 ssize_t
iBytesRead(0);
300 bool bGotMsgEnd(true);
302 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
305 /* if something was received, wait for MSGEND */
306 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
307 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
311 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
314 bool bChanged(false);
316 /* only send the command if the timeout changed */
318 CLockObject
lock(m_mutex
);
319 bChanged
= (m_iLineTimeout
!= iTimeout
);
320 m_iLineTimeout
= iTimeout
;
324 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
329 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
331 CLockObject
adapterLock(m_mutex
);
334 LIB_CEC
->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());
335 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
339 /* write the message */
340 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
342 LIB_CEC
->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());
343 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
348 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
349 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
353 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
355 ssize_t
iBytesRead(0);
360 /* read from the serial port */
362 CLockObject
lock(m_mutex
);
366 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
368 if (m_port
->GetErrorNumber())
370 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
376 if (iBytesRead
< 0 || iBytesRead
> 256)
378 else if (iBytesRead
> 0)
380 /* add the data to the current frame */
381 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
387 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
389 if (!m_port
->IsOpen() || !m_adapterMessageQueue
)
392 /* create the adapter message for this command */
393 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
394 output
->PushBack(MSGSTART
);
395 output
->PushEscaped((uint8_t)msgCode
);
396 output
->Append(params
);
397 output
->PushBack(MSGEND
);
399 /* write the command */
400 if (!m_adapterMessageQueue
->Write(output
))
402 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
404 libcec_parameter param
;
405 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
406 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
414 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
415 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
417 /* if the controller reported that the command was rejected, and we didn't send the command
418 to set controlled mode, then the controller probably switched to auto mode. set controlled
420 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
422 if (SetControlledMode(true))
423 return SendCommand(msgCode
, params
, true);
430 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
433 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
435 /* try to ping the adapter */
437 unsigned iPingTry(0);
438 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
440 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
444 /* try to read the firmware version */
445 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
447 /* try to set controlled mode for v2+ firmwares */
448 unsigned iControlledTry(0);
449 bool bControlled(false);
450 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
452 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
455 bReturn
= bControlled
;
460 /* try to read the build date */
461 m_commands
->RequestBuildDate();
463 SetInitialised(bReturn
);
467 bool CUSBCECAdapterCommunication::IsOpen(void)
469 /* thread is not being stopped, the port is open and the thread is running */
470 return !IsStopped() && m_port
->IsOpen() && IsRunning();
473 CStdString
CUSBCECAdapterCommunication::GetError(void) const
475 return m_port
->GetError();
478 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
480 CLockObject
lock(m_mutex
);
481 m_bInitialised
= bSetTo
;
484 bool CUSBCECAdapterCommunication::IsInitialised(void)
486 CLockObject
lock(m_mutex
);
487 return m_bInitialised
;
490 bool CUSBCECAdapterCommunication::StartBootloader(void)
492 if (m_port
->IsOpen() && m_commands
->StartBootloader())
500 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
502 if (m_iAckMask
== iMask
)
505 if (IsOpen() && m_commands
->SetAckMask(iMask
))
511 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "couldn't change the ackmask: the connection is closed");
515 uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
520 bool CUSBCECAdapterCommunication::PingAdapter(void)
522 return IsOpen() ? m_commands
->PingAdapter() : false;
525 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
527 return IsOpen() ? m_commands
->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN
;
530 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
532 return IsOpen() ? m_commands
->RequestBuildDate() : 0;
535 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
537 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
&&
538 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
;
541 bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration
&configuration
)
543 return IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
546 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
&configuration
)
548 return IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
551 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
553 return m_port
->GetName();
556 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
558 return IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
561 void *CAdapterPingThread::Process(void)
565 if (m_timeout
.TimeLeft() == 0)
567 /* reinit the timeout */
568 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
570 /* send a ping to the adapter */
572 int iFailedCounter(0);
573 while (!bPinged
&& iFailedCounter
< 3)
575 if (!m_com
->PingAdapter())
577 /* sleep and retry */
578 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
587 if (iFailedCounter
== 3)
589 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
590 m_com
->LIB_CEC
->AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
591 m_com
->StopThread(false);