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