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