cec: fixed abi
[deb_libcec.git] / src / lib / adapter / USBCECAdapterCommunication.cpp
... / ...
CommitLineData
1/*
2 * This file is part of the libCEC(R) library.
3 *
4 * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited. All rights reserved.
5 * libCEC(R) is an original work, containing original code.
6 *
7 * libCEC(R) is a trademark of Pulse-Eight Limited.
8 *
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.
13 *
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.
18 *
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.
22 *
23 *
24 * Alternatively, you can license this library under a commercial license,
25 * please contact Pulse-Eight Licensing for more information.
26 *
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/
31 */
32
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"
40
41using namespace std;
42using namespace CEC;
43using namespace PLATFORM;
44
45#define CEC_ADAPTER_PING_TIMEOUT 15000
46
47// firmware version 2
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
51
52#define LIB_CEC m_callback->GetLib()
53
54CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate /* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
55 IAdapterCommunication(callback),
56 m_port(NULL),
57 m_iLineTimeout(0),
58 m_lastPollDestination(CECDEVICE_UNKNOWN),
59 m_bInitialised(false),
60 m_pingThread(NULL),
61 m_commands(NULL),
62 m_adapterMessageQueue(NULL),
63 m_iAckMask(0xFFFF)
64{
65 for (unsigned int iPtr = CECDEVICE_TV; iPtr < CECDEVICE_BROADCAST; iPtr++)
66 m_bWaitingForAck[iPtr] = false;
67 m_port = new CSerialPort(strPort, iBaudRate);
68}
69
70CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
71{
72 Close();
73 delete m_commands;
74 delete m_adapterMessageQueue;
75 delete m_port;
76}
77
78bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks /* = false */, bool bStartListening /* = true */)
79{
80 bool bConnectionOpened(false);
81 {
82 CLockObject lock(m_mutex);
83
84 /* we need the port settings here */
85 if (!m_port)
86 {
87 LIB_CEC->AddLog(CEC_LOG_ERROR, "port is NULL");
88 return bConnectionOpened;
89 }
90
91 /* return true when the port is already open */
92 if (IsOpen())
93 {
94 LIB_CEC->AddLog(CEC_LOG_WARNING, "port is already open");
95 return true;
96 }
97
98 /* adapter commands */
99 if (!m_commands)
100 m_commands = new CUSBCECAdapterCommands(this);
101
102 if (!m_adapterMessageQueue)
103 {
104 m_adapterMessageQueue = new CCECAdapterMessageQueue(this);
105 m_adapterMessageQueue->CreateThread();
106 }
107
108 /* try to open the connection */
109 CStdString strError;
110 CTimeout timeout(iTimeoutMs);
111 while (!bConnectionOpened && timeout.TimeLeft() > 0)
112 {
113 if ((bConnectionOpened = m_port->Open(timeout.TimeLeft())) == false)
114 {
115 strError.Format("error opening serial port '%s': %s", m_port->GetName().c_str(), m_port->GetError().c_str());
116 Sleep(250);
117 }
118 /* and retry every 250ms until the timeout passed */
119 }
120
121 /* return false when we couldn't connect */
122 if (!bConnectionOpened)
123 {
124 LIB_CEC->AddLog(CEC_LOG_ERROR, strError);
125 return false;
126 }
127
128 LIB_CEC->AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
129 ClearInputBytes();
130 }
131
132 if (!CreateThread())
133 {
134 bConnectionOpened = false;
135 LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a communication thread");
136 }
137 else if (!bSkipChecks && !CheckAdapter())
138 {
139 bConnectionOpened = false;
140 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks");
141 }
142 else if (bStartListening)
143 {
144 /* start a ping thread, that will ping the adapter every 15 seconds
145 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
146 m_pingThread = new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT);
147 if (m_pingThread->CreateThread())
148 {
149 bConnectionOpened = true;
150 }
151 else
152 {
153 bConnectionOpened = false;
154 LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a ping thread");
155 }
156 }
157
158 if (!bConnectionOpened || !bStartListening)
159 StopThread(0);
160
161 return bConnectionOpened;
162}
163
164void CUSBCECAdapterCommunication::Close(void)
165{
166 /* stop the reader thread */
167 StopThread(0);
168
169 CLockObject lock(m_mutex);
170
171 /* set the ackmask to 0 before closing the connection */
172 if (IsRunning() && m_port->IsOpen() && m_port->GetErrorNumber() == 0)
173 {
174 LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - closing the connection", __FUNCTION__);
175 SetAckMask(0);
176 if (m_commands->GetFirmwareVersion() >= 2)
177 SetControlledMode(false);
178 }
179
180 m_adapterMessageQueue->Clear();
181
182 /* stop and delete the ping thread */
183 if (m_pingThread)
184 m_pingThread->StopThread(0);
185 delete m_pingThread;
186 m_pingThread = NULL;
187
188 /* close and delete the com port connection */
189 if (m_port)
190 m_port->Close();
191
192 libcec_parameter param;
193 param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN;
194 LIB_CEC->Alert(CEC_ALERT_CONNECTION_LOST, param);
195}
196
197cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, bool &bRetry, uint8_t iLineTimeout)
198{
199 cec_adapter_message_state retVal(ADAPTER_MESSAGE_STATE_UNKNOWN);
200 if (!IsRunning())
201 return retVal;
202
203 CCECAdapterMessage *output = new CCECAdapterMessage(data, iLineTimeout);
204
205 /* mark as waiting for an ack from the destination */
206 MarkAsWaiting(data.destination);
207
208 /* send the message */
209 bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
210 if (bRetry)
211 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
212 retVal = output->state;
213
214 delete output;
215 return retVal;
216}
217
218void *CUSBCECAdapterCommunication::Process(void)
219{
220 CCECAdapterMessage msg;
221 LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread started");
222
223 while (!IsStopped())
224 {
225 /* read from the serial port */
226 if (!ReadFromDevice(50, 5))
227 break;
228
229 /* TODO sleep 5 ms so other threads can get a lock */
230 Sleep(5);
231 }
232
233 m_adapterMessageQueue->Clear();
234 LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread ended");
235 return NULL;
236}
237
238bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage &msg)
239{
240 bool bIsError(msg.IsError());
241 cec_adapter_messagecode messageCode(msg.Message());
242 CLockObject lock(m_mutex);
243
244 if (messageCode == MSGCODE_FRAME_START && msg.IsACK())
245 {
246 m_lastPollDestination = msg.Destination();
247 if (msg.Destination() < CECDEVICE_BROADCAST)
248 {
249 if (!m_bWaitingForAck[msg.Destination()] && !msg.IsEOM())
250 {
251 if (m_callback)
252 m_callback->HandlePoll(msg.Initiator(), msg.Destination());
253 }
254 else
255 m_bWaitingForAck[msg.Destination()] = false;
256 }
257 }
258 else if (messageCode == MSGCODE_RECEIVE_FAILED)
259 {
260 /* hack to suppress warnings when an LG is polling */
261 if (m_lastPollDestination != CECDEVICE_UNKNOWN)
262 bIsError = m_callback->HandleReceiveFailed(m_lastPollDestination);
263 }
264
265 return bIsError;
266}
267
268void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest)
269{
270 /* mark as waiting for an ack from the destination */
271 if (dest < CECDEVICE_BROADCAST)
272 {
273 CLockObject lock(m_mutex);
274 m_bWaitingForAck[dest] = true;
275 }
276}
277
278void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
279{
280 CTimeout timeout(iTimeout);
281 uint8_t buff[1024];
282 ssize_t iBytesRead(0);
283 bool bGotMsgEnd(true);
284
285 while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd))
286 {
287 bGotMsgEnd = false;
288 /* if something was received, wait for MSGEND */
289 for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++)
290 bGotMsgEnd = buff[iPtr] == MSGEND;
291 }
292}
293
294bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
295{
296 bool bReturn(true);
297 bool bChanged(false);
298
299 /* only send the command if the timeout changed */
300 {
301 CLockObject lock(m_mutex);
302 bChanged = (m_iLineTimeout != iTimeout);
303 m_iLineTimeout = iTimeout;
304 }
305
306 if (bChanged)
307 bReturn = m_commands->SetLineTimeout(iTimeout);
308
309 return bReturn;
310}
311
312bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
313{
314 CLockObject adapterLock(m_mutex);
315 if (!m_port->IsOpen())
316 {
317 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());
318 message->state = ADAPTER_MESSAGE_STATE_ERROR;
319 return false;
320 }
321
322 /* write the message */
323 if (m_port->Write(message->packet.data, message->Size()) != (ssize_t) message->Size())
324 {
325 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());
326 message->state = ADAPTER_MESSAGE_STATE_ERROR;
327 Close();
328 return false;
329 }
330
331 LIB_CEC->AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message()));
332 message->state = ADAPTER_MESSAGE_STATE_SENT;
333 return true;
334}
335
336bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */)
337{
338 ssize_t iBytesRead(0);
339 uint8_t buff[256];
340 if (iSize > 256)
341 iSize = 256;
342
343 /* read from the serial port */
344 {
345 CLockObject lock(m_mutex);
346 if (!m_port || !m_port->IsOpen())
347 return false;
348
349 iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
350
351 if (m_port->GetErrorNumber())
352 {
353 LIB_CEC->AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
354 m_port->Close();
355 return false;
356 }
357 }
358
359 if (iBytesRead < 0 || iBytesRead > 256)
360 return false;
361 else if (iBytesRead > 0)
362 {
363 /* add the data to the current frame */
364 m_adapterMessageQueue->AddData(buff, iBytesRead);
365 }
366
367 return true;
368}
369
370CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage &params, bool bIsRetry /* = false */)
371{
372 if (!m_port || !m_port->IsOpen() ||
373 !m_adapterMessageQueue)
374 return NULL;
375
376 /* create the adapter message for this command */
377 CCECAdapterMessage *output = new CCECAdapterMessage;
378 output->PushBack(MSGSTART);
379 output->PushEscaped((uint8_t)msgCode);
380 output->Append(params);
381 output->PushBack(MSGEND);
382
383 /* write the command */
384 if (!m_adapterMessageQueue->Write(output))
385 {
386 if (output->state == ADAPTER_MESSAGE_STATE_ERROR)
387 Close();
388 return output;
389 }
390 else
391 {
392 if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED &&
393 msgCode != MSGCODE_GET_BUILDDATE /* same messagecode value had a different meaning in older fw builds */)
394 {
395 /* if the controller reported that the command was rejected, and we didn't send the command
396 to set controlled mode, then the controller probably switched to auto mode. set controlled
397 mode and retry */
398 LIB_CEC->AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying");
399 delete output;
400 if (SetControlledMode(true))
401 return SendCommand(msgCode, params, true);
402 }
403 }
404
405 return output;
406}
407
408bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */)
409{
410 bool bReturn(false);
411 CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT);
412
413 /* try to ping the adapter */
414 bool bPinged(false);
415 unsigned iPingTry(0);
416 while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false)
417 {
418 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
419 CEvent::Sleep(500);
420 }
421
422 /* try to read the firmware version */
423 if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2)
424 {
425 /* try to set controlled mode for v2+ firmwares */
426 unsigned iControlledTry(0);
427 bool bControlled(false);
428 while (timeout.TimeLeft() > 0 && (bControlled = SetControlledMode(true)) == false)
429 {
430 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry);
431 CEvent::Sleep(500);
432 }
433 bReturn = bControlled;
434 }
435 else
436 bReturn = true;
437
438 /* try to read the build date */
439 m_commands->RequestBuildDate();
440
441 SetInitialised(bReturn);
442 return bReturn;
443}
444
445bool CUSBCECAdapterCommunication::IsOpen(void)
446{
447 /* thread is not being stopped, the port is open and the thread is running */
448 return !IsStopped() && m_port->IsOpen() && IsRunning();
449}
450
451CStdString CUSBCECAdapterCommunication::GetError(void) const
452{
453 return m_port->GetError();
454}
455
456void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */)
457{
458 CLockObject lock(m_mutex);
459 m_bInitialised = bSetTo;
460}
461
462bool CUSBCECAdapterCommunication::IsInitialised(void)
463{
464 CLockObject lock(m_mutex);
465 return m_bInitialised;
466}
467
468bool CUSBCECAdapterCommunication::StartBootloader(void)
469{
470 if (m_port->IsOpen() && m_commands->StartBootloader())
471 {
472 Close();
473 return true;
474 }
475 return false;
476}
477
478bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
479{
480 if (m_iAckMask == iMask)
481 return true;
482
483 if (m_port && m_port->IsOpen() && m_commands->SetAckMask(iMask))
484 {
485 m_iAckMask = iMask;
486 return true;
487 }
488
489 return false;
490}
491
492uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
493{
494 return m_iAckMask;
495}
496
497bool CUSBCECAdapterCommunication::PingAdapter(void)
498{
499 return m_port->IsOpen() ? m_commands->PingAdapter() : false;
500}
501
502uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
503{
504 return m_commands->GetFirmwareVersion();
505}
506
507uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
508{
509 return m_commands->RequestBuildDate();
510}
511
512bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
513{
514 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION &&
515 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE;
516}
517
518bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration)
519{
520 return m_port->IsOpen() ? m_commands->PersistConfiguration(configuration) : false;
521}
522
523bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration)
524{
525 return m_port->IsOpen() ? m_commands->GetConfiguration(configuration) : false;
526}
527
528CStdString CUSBCECAdapterCommunication::GetPortName(void)
529{
530 return m_port->GetName();
531}
532
533bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
534{
535 return m_port->IsOpen() ? m_commands->SetControlledMode(controlled) : false;
536}
537
538void *CAdapterPingThread::Process(void)
539{
540 while (!IsStopped())
541 {
542 if (m_timeout.TimeLeft() == 0)
543 {
544 /* reinit the timeout */
545 m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT);
546
547 /* send a ping to the adapter */
548 bool bPinged(false);
549 int iFailedCounter(0);
550 while (!bPinged && iFailedCounter < 3)
551 {
552 if (!m_com->PingAdapter())
553 {
554 /* sleep and retry */
555 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
556 ++iFailedCounter;
557 }
558 else
559 {
560 bPinged = true;
561 }
562 }
563
564 if (iFailedCounter == 3)
565 {
566 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
567 m_com->LIB_CEC->AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection.");
568 m_com->StopThread(false);
569 break;
570 }
571 }
572
573 Sleep(500);
574 }
575 return NULL;
576}