cec: only delete m_port in CUSBCECAdapterCommunication's destructor. fixes potential...
[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);
149 if (!bConnectionOpened)
f80cd208
LOK
150 {
151 delete m_port;
f401a435 152 m_port = NULL;
f80cd208 153 }
a8f0bd18 154
a75e3a5a 155 return bConnectionOpened;
a8f0bd18
LOK
156}
157
7bb4ed43 158void CUSBCECAdapterCommunication::Close(void)
a8f0bd18 159{
a75e3a5a
LOK
160 /* set the ackmask to 0 before closing the connection */
161 if (IsRunning())
162 {
163 SetAckMask(0);
164 if (m_commands->GetFirmwareVersion() >= 2)
165 SetControlledMode(false);
166 }
167
168 /* stop and delete the ping thread */
56e53c14
LOK
169 if (m_pingThread)
170 m_pingThread->StopThread(0);
171 delete m_pingThread;
172 m_pingThread = NULL;
a8f0bd18 173
a75e3a5a
LOK
174 /* stop the reader thread */
175 StopThread(0);
9f9c8c82 176
a75e3a5a 177 /* close and delete the com port connection */
e4a53f8e
LOK
178 if (m_port)
179 m_port->Close();
a8f0bd18
LOK
180}
181
7bb4ed43
LOK
182cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, uint8_t iMaxTries, uint8_t iLineTimeout /* = 3 */, uint8_t iRetryLineTimeout /* = 3 */)
183{
184 cec_adapter_message_state retVal(ADAPTER_MESSAGE_STATE_UNKNOWN);
9f68cc28
LOK
185 if (!IsRunning())
186 return retVal;
7bb4ed43 187
a75e3a5a 188 CCECAdapterMessage *output = new CCECAdapterMessage(data, iMaxTries, iLineTimeout, iRetryLineTimeout);
7bb4ed43 189
a75e3a5a
LOK
190 /* mark as waiting for an ack from the destination */
191 MarkAsWaiting(data.destination);
4164923b 192
a75e3a5a 193 /* send the message */
7bb4ed43
LOK
194 bool bRetry(true);
195 while (bRetry && ++output->tries < output->maxTries)
196 {
a75e3a5a 197 bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
7bb4ed43
LOK
198 if (bRetry)
199 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
200 }
201 retVal = output->state;
202
203 delete output;
204 return retVal;
205}
206
a75e3a5a 207void *CUSBCECAdapterCommunication::Process(void)
3c53ac93 208{
a75e3a5a
LOK
209 CCECAdapterMessage msg;
210 CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread started");
5dcf9f25 211
a75e3a5a 212 while (!IsStopped())
5dcf9f25 213 {
a75e3a5a
LOK
214 /* read from the serial port */
215 if (!ReadFromDevice(50, 5))
216 break;
217
218 /* TODO sleep 5 ms so other threads can get a lock */
219 Sleep(5);
5dcf9f25
LOK
220 }
221
a75e3a5a
LOK
222 m_adapterMessageQueue->Clear();
223 CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread ended");
224 return NULL;
a8f0bd18
LOK
225}
226
a75e3a5a 227bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage &msg)
a8f0bd18 228{
a75e3a5a
LOK
229 bool bIsError(msg.IsError());
230 cec_adapter_messagecode messageCode(msg.Message());
231 CLockObject lock(m_mutex);
9f68cc28 232
a75e3a5a 233 if (messageCode == MSGCODE_FRAME_START && msg.IsACK())
a8f0bd18 234 {
a75e3a5a
LOK
235 m_lastPollDestination = msg.Destination();
236 if (msg.Destination() < CECDEVICE_BROADCAST)
a8f0bd18 237 {
a75e3a5a
LOK
238 if (!m_bWaitingForAck[msg.Destination()] && !msg.IsEOM())
239 {
240 if (m_callback)
241 m_callback->HandlePoll(msg.Initiator(), msg.Destination());
242 }
243 else
244 m_bWaitingForAck[msg.Destination()] = false;
a8f0bd18 245 }
7bb4ed43 246 }
a75e3a5a 247 else if (messageCode == MSGCODE_RECEIVE_FAILED)
7bb4ed43 248 {
a75e3a5a
LOK
249 /* hack to suppress warnings when an LG is polling */
250 if (m_lastPollDestination != CECDEVICE_UNKNOWN)
251 bIsError = m_callback->HandleReceiveFailed(m_lastPollDestination);
7bb4ed43 252 }
a8f0bd18 253
a75e3a5a 254 return bIsError;
a8f0bd18 255}
2abe74eb 256
a75e3a5a 257void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest)
2abe74eb 258{
a75e3a5a
LOK
259 /* mark as waiting for an ack from the destination */
260 if (dest < CECDEVICE_BROADCAST)
7bb4ed43 261 {
a75e3a5a
LOK
262 CLockObject lock(m_mutex);
263 m_bWaitingForAck[dest] = true;
7bb4ed43 264 }
7bb4ed43
LOK
265}
266
a75e3a5a 267void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = 1000 */)
1fc16cfd 268{
a75e3a5a
LOK
269 CTimeout timeout(iTimeout);
270 uint8_t buff[1024];
271 ssize_t iBytesRead(0);
a4d657c7 272 bool bGotMsgEnd(true);
1fc16cfd 273
a75e3a5a 274 while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd))
1fc16cfd 275 {
a4d657c7 276 bGotMsgEnd = false;
a75e3a5a
LOK
277 /* if something was received, wait for MSGEND */
278 for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++)
279 bGotMsgEnd = buff[iPtr] == MSGEND;
1fc16cfd 280 }
1fc16cfd
LOK
281}
282
7bb4ed43 283bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
a171d2fd 284{
16459df9 285 bool bReturn(true);
a75e3a5a 286 bool bChanged(false);
089f0e9d 287
a75e3a5a 288 /* only send the command if the timeout changed */
089f0e9d 289 {
a75e3a5a
LOK
290 CLockObject lock(m_mutex);
291 bChanged = (m_iLineTimeout != iTimeout);
292 m_iLineTimeout = iTimeout;
089f0e9d
LOK
293 }
294
a75e3a5a
LOK
295 if (bChanged)
296 bReturn = m_commands->SetLineTimeout(iTimeout);
a171d2fd 297
a75e3a5a 298 return bReturn;
f9e01dac
LOK
299}
300
a75e3a5a 301bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
5dcf9f25 302{
a75e3a5a
LOK
303 CLockObject adapterLock(m_mutex);
304 if (!m_port->IsOpen())
305 {
306 CLibCEC::AddLog(CEC_LOG_DEBUG, "error writing command '%s' to the serial port: the connection is closed", CCECAdapterMessage::ToString(message->Message()));
307 message->state = ADAPTER_MESSAGE_STATE_ERROR;
308 return false;
309 }
5dcf9f25 310
a75e3a5a
LOK
311 /* write the message */
312 if (m_port->Write(message->packet.data, message->Size()) != (ssize_t) message->Size())
313 {
314 CLibCEC::AddLog(CEC_LOG_DEBUG, "error writing command '%s' to the serial port: %s", CCECAdapterMessage::ToString(message->Message()), m_port->GetError().c_str());
315 message->state = ADAPTER_MESSAGE_STATE_ERROR;
cb4ee028 316 return false;
a75e3a5a 317 }
cb4ee028 318
a75e3a5a
LOK
319 CLibCEC::AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message()));
320 message->state = ADAPTER_MESSAGE_STATE_SENT;
321 return true;
c214d197 322}
b057edad 323
a75e3a5a 324bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */)
12a36be9 325{
a75e3a5a
LOK
326 ssize_t iBytesRead(0);
327 uint8_t buff[256];
328 if (iSize > 256)
329 iSize = 256;
12a36be9 330
a75e3a5a 331 /* read from the serial port */
12a36be9 332 {
a75e3a5a
LOK
333 CLockObject lock(m_mutex);
334 if (!m_port)
335 return false;
336 iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
12a36be9
LOK
337 }
338
a75e3a5a 339 if (iBytesRead < 0 || iBytesRead > 256)
12a36be9 340 {
a75e3a5a
LOK
341 CLibCEC::AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
342 StopThread(false);
343 return false;
12a36be9 344 }
a75e3a5a 345 else if (iBytesRead > 0)
12a36be9 346 {
a75e3a5a
LOK
347 /* add the data to the current frame */
348 m_adapterMessageQueue->AddData(buff, iBytesRead);
12a36be9
LOK
349 }
350
a75e3a5a 351 return true;
b057edad
BL
352}
353
a75e3a5a 354CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage &params, bool bIsRetry /* = false */)
c214d197 355{
a75e3a5a
LOK
356 if (!m_port || !m_port->IsOpen() ||
357 !m_adapterMessageQueue)
358 return NULL;
c214d197 359
a75e3a5a
LOK
360 /* create the adapter message for this command */
361 CCECAdapterMessage *output = new CCECAdapterMessage;
362 output->PushBack(MSGSTART);
363 output->PushEscaped((uint8_t)msgCode);
364 output->Append(params);
365 output->PushBack(MSGEND);
12a36be9 366
a75e3a5a
LOK
367 /* write the command */
368 if (!m_adapterMessageQueue->Write(output))
12a36be9 369 {
a75e3a5a
LOK
370 // timed out
371 return output;
12a36be9 372 }
a75e3a5a 373 else
12a36be9 374 {
a75e3a5a
LOK
375 if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED)
376 {
377 /* if the controller reported that the command was rejected, and we didn't send the command
378 to set controlled mode, then the controller probably switched to auto mode. set controlled
379 mode and retry */
380 CLibCEC::AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying");
381 delete output;
382 if (SetControlledMode(true))
383 return SendCommand(msgCode, params, true);
384 }
12a36be9 385 }
12a36be9 386
a75e3a5a 387 return output;
c214d197
LOK
388}
389
a75e3a5a 390bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */)
12a36be9 391{
a75e3a5a
LOK
392 bool bReturn(false);
393 CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT);
12a36be9 394
a75e3a5a
LOK
395 /* try to ping the adapter */
396 bool bPinged(false);
397 unsigned iPingTry(0);
398 while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false)
12a36be9 399 {
a75e3a5a
LOK
400 CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
401 CEvent::Sleep(500);
12a36be9 402 }
c214d197 403
a75e3a5a
LOK
404 /* try to read the firmware version */
405 if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2)
12a36be9 406 {
a75e3a5a
LOK
407 /* try to set controlled mode for v2+ firmwares */
408 unsigned iControlledTry(0);
409 bool bControlled(false);
410 while (timeout.TimeLeft() > 0 && (bControlled = SetControlledMode(true)) == false)
411 {
412 CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry);
413 CEvent::Sleep(500);
414 }
415 bReturn = bControlled;
12a36be9 416 }
a75e3a5a
LOK
417 else
418 bReturn = true;
c214d197 419
a75e3a5a
LOK
420 SetInitialised(bReturn);
421 return bReturn;
c214d197
LOK
422}
423
a75e3a5a 424bool CUSBCECAdapterCommunication::IsOpen(void)
12a36be9 425{
a75e3a5a
LOK
426 /* thread is not being stopped, the port is open and the thread is running */
427 return !IsStopped() && m_port->IsOpen() && IsRunning();
12a36be9
LOK
428}
429
a75e3a5a 430CStdString CUSBCECAdapterCommunication::GetError(void) const
c214d197 431{
a75e3a5a 432 return m_port->GetError();
c214d197
LOK
433}
434
a75e3a5a 435void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */)
12a36be9
LOK
436{
437 CLockObject lock(m_mutex);
a75e3a5a 438 m_bInitialised = bSetTo;
12a36be9
LOK
439}
440
a75e3a5a 441bool CUSBCECAdapterCommunication::IsInitialised(void)
c214d197
LOK
442{
443 CLockObject lock(m_mutex);
a75e3a5a 444 return m_bInitialised;
c214d197
LOK
445}
446
a75e3a5a 447bool CUSBCECAdapterCommunication::StartBootloader(void)
12a36be9 448{
a75e3a5a 449 if (!IsRunning())
12a36be9
LOK
450 return false;
451
a75e3a5a 452 return m_commands->StartBootloader();
12a36be9
LOK
453}
454
a75e3a5a 455bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
13fd6a66 456{
a75e3a5a 457 return m_commands->SetAckMask(iMask);
13fd6a66 458}
ef7696f5 459
a75e3a5a 460bool CUSBCECAdapterCommunication::PingAdapter(void)
6729ac71 461{
a75e3a5a 462 return m_commands->PingAdapter();
6729ac71
LOK
463}
464
a75e3a5a 465uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
ef7696f5 466{
a75e3a5a 467 return m_commands->GetFirmwareVersion();
ef7696f5
LOK
468}
469
a75e3a5a 470bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration)
ef7696f5 471{
a75e3a5a 472 return m_commands->PersistConfiguration(configuration);
ef7696f5
LOK
473}
474
a75e3a5a 475bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration)
ef7696f5 476{
a75e3a5a 477 return m_commands->GetConfiguration(configuration);
ef7696f5
LOK
478}
479
cba904a6
LOK
480CStdString CUSBCECAdapterCommunication::GetPortName(void)
481{
a75e3a5a 482 return m_port->GetName();
c9c282a4 483}
d4db0c6f 484
a75e3a5a 485bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
d4db0c6f 486{
a75e3a5a 487 return m_commands->SetControlledMode(controlled);
d4db0c6f 488}
56e53c14
LOK
489
490void *CAdapterPingThread::Process(void)
491{
492 while (!IsStopped())
493 {
494 if (m_timeout.TimeLeft() == 0)
495 {
a75e3a5a 496 /* reinit the timeout */
56e53c14 497 m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT);
a75e3a5a
LOK
498
499 /* send a ping to the adapter */
500 bool bPinged(false);
501 int iFailedCounter(0);
502 while (!bPinged && iFailedCounter < 3)
503 {
504 if (!m_com->PingAdapter())
505 {
506 /* sleep 1 second and retry */
507 Sleep(1000);
508 ++iFailedCounter;
509 }
510 else
511 {
512 bPinged = true;
513 }
514 }
515
516 if (iFailedCounter == 3)
517 {
518 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
519 CLibCEC::AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection.");
520 m_com->StopThread(false);
521 break;
522 }
56e53c14
LOK
523 }
524
525 Sleep(500);
526 }
527 return NULL;
528}