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