Merge branch 'adaptercomm'. bugzid: 654
[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);
a4d657c7 271 bool bGotMsgEnd(true);
1fc16cfd 272
a75e3a5a 273 while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd))
1fc16cfd 274 {
a4d657c7 275 bGotMsgEnd = false;
a75e3a5a
LOK
276 /* if something was received, wait for MSGEND */
277 for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++)
278 bGotMsgEnd = buff[iPtr] == MSGEND;
1fc16cfd 279 }
1fc16cfd
LOK
280}
281
7bb4ed43 282bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
a171d2fd 283{
16459df9 284 bool bReturn(true);
a75e3a5a 285 bool bChanged(false);
089f0e9d 286
a75e3a5a 287 /* only send the command if the timeout changed */
089f0e9d 288 {
a75e3a5a
LOK
289 CLockObject lock(m_mutex);
290 bChanged = (m_iLineTimeout != iTimeout);
291 m_iLineTimeout = iTimeout;
089f0e9d
LOK
292 }
293
a75e3a5a
LOK
294 if (bChanged)
295 bReturn = m_commands->SetLineTimeout(iTimeout);
a171d2fd 296
a75e3a5a 297 return bReturn;
f9e01dac
LOK
298}
299
a75e3a5a 300bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
5dcf9f25 301{
a75e3a5a
LOK
302 CLockObject adapterLock(m_mutex);
303 if (!m_port->IsOpen())
304 {
305 CLibCEC::AddLog(CEC_LOG_DEBUG, "error writing command '%s' to the serial port: the connection is closed", CCECAdapterMessage::ToString(message->Message()));
306 message->state = ADAPTER_MESSAGE_STATE_ERROR;
307 return false;
308 }
5dcf9f25 309
a75e3a5a
LOK
310 /* write the message */
311 if (m_port->Write(message->packet.data, message->Size()) != (ssize_t) message->Size())
312 {
313 CLibCEC::AddLog(CEC_LOG_DEBUG, "error writing command '%s' to the serial port: %s", CCECAdapterMessage::ToString(message->Message()), m_port->GetError().c_str());
314 message->state = ADAPTER_MESSAGE_STATE_ERROR;
cb4ee028 315 return false;
a75e3a5a 316 }
cb4ee028 317
a75e3a5a
LOK
318 CLibCEC::AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message()));
319 message->state = ADAPTER_MESSAGE_STATE_SENT;
320 return true;
c214d197 321}
b057edad 322
a75e3a5a 323bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */)
12a36be9 324{
a75e3a5a
LOK
325 ssize_t iBytesRead(0);
326 uint8_t buff[256];
327 if (iSize > 256)
328 iSize = 256;
12a36be9 329
a75e3a5a 330 /* read from the serial port */
12a36be9 331 {
a75e3a5a
LOK
332 CLockObject lock(m_mutex);
333 if (!m_port)
334 return false;
335 iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
12a36be9
LOK
336 }
337
a75e3a5a 338 if (iBytesRead < 0 || iBytesRead > 256)
12a36be9 339 {
a75e3a5a
LOK
340 CLibCEC::AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
341 StopThread(false);
342 return false;
12a36be9 343 }
a75e3a5a 344 else if (iBytesRead > 0)
12a36be9 345 {
a75e3a5a
LOK
346 /* add the data to the current frame */
347 m_adapterMessageQueue->AddData(buff, iBytesRead);
12a36be9
LOK
348 }
349
a75e3a5a 350 return true;
b057edad
BL
351}
352
a75e3a5a 353CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage &params, bool bIsRetry /* = false */)
c214d197 354{
a75e3a5a
LOK
355 if (!m_port || !m_port->IsOpen() ||
356 !m_adapterMessageQueue)
357 return NULL;
c214d197 358
a75e3a5a
LOK
359 /* create the adapter message for this command */
360 CCECAdapterMessage *output = new CCECAdapterMessage;
361 output->PushBack(MSGSTART);
362 output->PushEscaped((uint8_t)msgCode);
363 output->Append(params);
364 output->PushBack(MSGEND);
12a36be9 365
a75e3a5a
LOK
366 /* write the command */
367 if (!m_adapterMessageQueue->Write(output))
12a36be9 368 {
a75e3a5a
LOK
369 // timed out
370 return output;
12a36be9 371 }
a75e3a5a 372 else
12a36be9 373 {
a75e3a5a
LOK
374 if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED)
375 {
376 /* if the controller reported that the command was rejected, and we didn't send the command
377 to set controlled mode, then the controller probably switched to auto mode. set controlled
378 mode and retry */
379 CLibCEC::AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying");
380 delete output;
381 if (SetControlledMode(true))
382 return SendCommand(msgCode, params, true);
383 }
12a36be9 384 }
12a36be9 385
a75e3a5a 386 return output;
c214d197
LOK
387}
388
a75e3a5a 389bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */)
12a36be9 390{
a75e3a5a
LOK
391 bool bReturn(false);
392 CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT);
12a36be9 393
a75e3a5a
LOK
394 /* try to ping the adapter */
395 bool bPinged(false);
396 unsigned iPingTry(0);
397 while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false)
12a36be9 398 {
a75e3a5a
LOK
399 CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
400 CEvent::Sleep(500);
12a36be9 401 }
c214d197 402
a75e3a5a
LOK
403 /* try to read the firmware version */
404 if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2)
12a36be9 405 {
a75e3a5a
LOK
406 /* try to set controlled mode for v2+ firmwares */
407 unsigned iControlledTry(0);
408 bool bControlled(false);
409 while (timeout.TimeLeft() > 0 && (bControlled = SetControlledMode(true)) == false)
410 {
411 CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry);
412 CEvent::Sleep(500);
413 }
414 bReturn = bControlled;
12a36be9 415 }
a75e3a5a
LOK
416 else
417 bReturn = true;
c214d197 418
a75e3a5a
LOK
419 SetInitialised(bReturn);
420 return bReturn;
c214d197
LOK
421}
422
a75e3a5a 423bool CUSBCECAdapterCommunication::IsOpen(void)
12a36be9 424{
a75e3a5a
LOK
425 /* thread is not being stopped, the port is open and the thread is running */
426 return !IsStopped() && m_port->IsOpen() && IsRunning();
12a36be9
LOK
427}
428
a75e3a5a 429CStdString CUSBCECAdapterCommunication::GetError(void) const
c214d197 430{
a75e3a5a 431 return m_port->GetError();
c214d197
LOK
432}
433
a75e3a5a 434void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */)
12a36be9
LOK
435{
436 CLockObject lock(m_mutex);
a75e3a5a 437 m_bInitialised = bSetTo;
12a36be9
LOK
438}
439
a75e3a5a 440bool CUSBCECAdapterCommunication::IsInitialised(void)
c214d197
LOK
441{
442 CLockObject lock(m_mutex);
a75e3a5a 443 return m_bInitialised;
c214d197
LOK
444}
445
a75e3a5a 446bool CUSBCECAdapterCommunication::StartBootloader(void)
12a36be9 447{
a75e3a5a 448 if (!IsRunning())
12a36be9
LOK
449 return false;
450
a75e3a5a 451 return m_commands->StartBootloader();
12a36be9
LOK
452}
453
a75e3a5a 454bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
13fd6a66 455{
a75e3a5a 456 return m_commands->SetAckMask(iMask);
13fd6a66 457}
ef7696f5 458
a75e3a5a 459bool CUSBCECAdapterCommunication::PingAdapter(void)
6729ac71 460{
a75e3a5a 461 return m_commands->PingAdapter();
6729ac71
LOK
462}
463
a75e3a5a 464uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
ef7696f5 465{
a75e3a5a 466 return m_commands->GetFirmwareVersion();
ef7696f5
LOK
467}
468
a75e3a5a 469bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration)
ef7696f5 470{
a75e3a5a 471 return m_commands->PersistConfiguration(configuration);
ef7696f5
LOK
472}
473
a75e3a5a 474bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration)
ef7696f5 475{
a75e3a5a 476 return m_commands->GetConfiguration(configuration);
ef7696f5
LOK
477}
478
cba904a6
LOK
479CStdString CUSBCECAdapterCommunication::GetPortName(void)
480{
a75e3a5a 481 return m_port->GetName();
c9c282a4 482}
d4db0c6f 483
a75e3a5a 484bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
d4db0c6f 485{
a75e3a5a 486 return m_commands->SetControlledMode(controlled);
d4db0c6f 487}
56e53c14
LOK
488
489void *CAdapterPingThread::Process(void)
490{
491 while (!IsStopped())
492 {
493 if (m_timeout.TimeLeft() == 0)
494 {
a75e3a5a 495 /* reinit the timeout */
56e53c14 496 m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT);
a75e3a5a
LOK
497
498 /* send a ping to the adapter */
499 bool bPinged(false);
500 int iFailedCounter(0);
501 while (!bPinged && iFailedCounter < 3)
502 {
503 if (!m_com->PingAdapter())
504 {
505 /* sleep 1 second and retry */
506 Sleep(1000);
507 ++iFailedCounter;
508 }
509 else
510 {
511 bPinged = true;
512 }
513 }
514
515 if (iFailedCounter == 3)
516 {
517 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
518 CLibCEC::AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection.");
519 m_com->StopThread(false);
520 break;
521 }
56e53c14
LOK
522 }
523
524 Sleep(500);
525 }
526 return NULL;
527}