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