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