cec: refactor USB adapter communication. less locks, shorter locks, added documentati...
[deb_libcec.git] / src / lib / adapter / USBCECAdapterCommunication.cpp
... / ...
CommitLineData
1/*
2 * This file is part of the libCEC(R) library.
3 *
4 * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited. All rights reserved.
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
33#include "USBCECAdapterCommunication.h"
34#include "USBCECAdapterCommands.h"
35#include "USBCECAdapterMessageQueue.h"
36#include "../platform/sockets/serialport.h"
37#include "../platform/util/timeutils.h"
38#include "../LibCEC.h"
39#include "../CECProcessor.h"
40
41using namespace std;
42using namespace CEC;
43using namespace PLATFORM;
44
45#define CEC_ADAPTER_PING_TIMEOUT 15000
46
47CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate /* = 38400 */) :
48 IAdapterCommunication(callback),
49 m_port(NULL),
50 m_iLineTimeout(0),
51 m_lastPollDestination(CECDEVICE_UNKNOWN),
52 m_bInitialised(false),
53 m_pingThread(NULL),
54 m_commands(NULL),
55 m_adapterMessageQueue(NULL)
56{
57 for (unsigned int iPtr = 0; iPtr < 15; iPtr++)
58 m_bWaitingForAck[iPtr] = false;
59 m_port = new CSerialPort(strPort, iBaudRate);
60}
61
62CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
63{
64 Close();
65 delete m_commands;
66 delete m_adapterMessageQueue;
67}
68
69bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */, bool bSkipChecks /* = false */, bool bStartListening /* = true */)
70{
71 bool bConnectionOpened(false);
72 {
73 CLockObject lock(m_mutex);
74
75 /* we need the port settings here */
76 if (!m_port)
77 {
78 CLibCEC::AddLog(CEC_LOG_ERROR, "port is NULL");
79 return bConnectionOpened;
80 }
81
82 /* return true when the port is already open */
83 if (IsOpen())
84 {
85 CLibCEC::AddLog(CEC_LOG_WARNING, "port is already open");
86 return true;
87 }
88
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 */
97 CStdString strError;
98 CTimeout timeout(iTimeoutMs);
99 while (!bConnectionOpened && timeout.TimeLeft() > 0)
100 {
101 if ((bConnectionOpened = m_port->Open(timeout.TimeLeft())) == false)
102 {
103 strError.Format("error opening serial port '%s': %s", m_port->GetName().c_str(), m_port->GetError().c_str());
104 Sleep(250);
105 }
106 /* and retry every 250ms until the timeout passed */
107 }
108
109 /* return false when we couldn't connect */
110 if (!bConnectionOpened)
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");
117 ClearInputBytes();
118 }
119
120 if (!CreateThread())
121 {
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;
128 CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks");
129 }
130 else if (bStartListening)
131 {
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 */
134 m_pingThread = new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT);
135 if (m_pingThread->CreateThread())
136 {
137 bConnectionOpened = true;
138 }
139 else
140 {
141 bConnectionOpened = false;
142 CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a ping thread");
143 }
144 }
145
146 if (!bConnectionOpened || !bStartListening)
147 StopThread(0);
148 if (!bConnectionOpened)
149 {
150 delete m_port;
151 m_port = NULL;
152 }
153
154 return bConnectionOpened;
155}
156
157void CUSBCECAdapterCommunication::Close(void)
158{
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 */
168 if (m_pingThread)
169 m_pingThread->StopThread(0);
170 delete m_pingThread;
171 m_pingThread = NULL;
172
173 /* stop the reader thread */
174 StopThread(0);
175
176 /* close and delete the com port connection */
177 delete m_port;
178 m_port = NULL;
179}
180
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);
184 if (!IsRunning())
185 return retVal;
186
187 CCECAdapterMessage *output = new CCECAdapterMessage(data, iMaxTries, iLineTimeout, iRetryLineTimeout);
188
189 /* mark as waiting for an ack from the destination */
190 MarkAsWaiting(data.destination);
191
192 /* send the message */
193 bool bRetry(true);
194 while (bRetry && ++output->tries < output->maxTries)
195 {
196 bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
197 if (bRetry)
198 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
199 }
200 retVal = output->state;
201
202 delete output;
203 return retVal;
204}
205
206void *CUSBCECAdapterCommunication::Process(void)
207{
208 CCECAdapterMessage msg;
209 CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread started");
210
211 while (!IsStopped())
212 {
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);
219 }
220
221 m_adapterMessageQueue->Clear();
222 CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread ended");
223 return NULL;
224}
225
226bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage &msg)
227{
228 bool bIsError(msg.IsError());
229 cec_adapter_messagecode messageCode(msg.Message());
230 CLockObject lock(m_mutex);
231
232 if (messageCode == MSGCODE_FRAME_START && msg.IsACK())
233 {
234 m_lastPollDestination = msg.Destination();
235 if (msg.Destination() < CECDEVICE_BROADCAST)
236 {
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;
244 }
245 }
246 else if (messageCode == MSGCODE_RECEIVE_FAILED)
247 {
248 /* hack to suppress warnings when an LG is polling */
249 if (m_lastPollDestination != CECDEVICE_UNKNOWN)
250 bIsError = m_callback->HandleReceiveFailed(m_lastPollDestination);
251 }
252
253 return bIsError;
254}
255
256void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest)
257{
258 /* mark as waiting for an ack from the destination */
259 if (dest < CECDEVICE_BROADCAST)
260 {
261 CLockObject lock(m_mutex);
262 m_bWaitingForAck[dest] = true;
263 }
264}
265
266void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = 1000 */)
267{
268 CTimeout timeout(iTimeout);
269 uint8_t buff[1024];
270 ssize_t iBytesRead(0);
271 bool bGotMsgEnd(false);
272
273 while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd))
274 {
275 /* if something was received, wait for MSGEND */
276 for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++)
277 bGotMsgEnd = buff[iPtr] == MSGEND;
278 }
279}
280
281bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
282{
283 bool bReturn(true);
284 bool bChanged(false);
285
286 /* only send the command if the timeout changed */
287 {
288 CLockObject lock(m_mutex);
289 bChanged = (m_iLineTimeout != iTimeout);
290 m_iLineTimeout = iTimeout;
291 }
292
293 if (bChanged)
294 bReturn = m_commands->SetLineTimeout(iTimeout);
295
296 return bReturn;
297}
298
299bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
300{
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 }
308
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;
314 return false;
315 }
316
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;
320}
321
322bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */)
323{
324 ssize_t iBytesRead(0);
325 uint8_t buff[256];
326 if (iSize > 256)
327 iSize = 256;
328
329 /* read from the serial port */
330 {
331 CLockObject lock(m_mutex);
332 if (!m_port)
333 return false;
334 iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
335 }
336
337 if (iBytesRead < 0 || iBytesRead > 256)
338 {
339 CLibCEC::AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
340 StopThread(false);
341 return false;
342 }
343 else if (iBytesRead > 0)
344 {
345 /* add the data to the current frame */
346 m_adapterMessageQueue->AddData(buff, iBytesRead);
347 }
348
349 return true;
350}
351
352CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage &params, bool bIsRetry /* = false */)
353{
354 if (!m_port || !m_port->IsOpen() ||
355 !m_adapterMessageQueue)
356 return NULL;
357
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);
364
365 /* write the command */
366 if (!m_adapterMessageQueue->Write(output))
367 {
368 // timed out
369 return output;
370 }
371 else
372 {
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 }
383 }
384
385 return output;
386}
387
388bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */)
389{
390 bool bReturn(false);
391 CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT);
392
393 /* try to ping the adapter */
394 bool bPinged(false);
395 unsigned iPingTry(0);
396 while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false)
397 {
398 CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
399 CEvent::Sleep(500);
400 }
401
402 /* try to read the firmware version */
403 if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2)
404 {
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;
414 }
415 else
416 bReturn = true;
417
418 SetInitialised(bReturn);
419 return bReturn;
420}
421
422bool CUSBCECAdapterCommunication::IsOpen(void)
423{
424 /* thread is not being stopped, the port is open and the thread is running */
425 return !IsStopped() && m_port->IsOpen() && IsRunning();
426}
427
428CStdString CUSBCECAdapterCommunication::GetError(void) const
429{
430 return m_port->GetError();
431}
432
433void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */)
434{
435 CLockObject lock(m_mutex);
436 m_bInitialised = bSetTo;
437}
438
439bool CUSBCECAdapterCommunication::IsInitialised(void)
440{
441 CLockObject lock(m_mutex);
442 return m_bInitialised;
443}
444
445bool CUSBCECAdapterCommunication::StartBootloader(void)
446{
447 if (!IsRunning())
448 return false;
449
450 return m_commands->StartBootloader();
451}
452
453bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
454{
455 return m_commands->SetAckMask(iMask);
456}
457
458bool CUSBCECAdapterCommunication::PingAdapter(void)
459{
460 return m_commands->PingAdapter();
461}
462
463uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
464{
465 return m_commands->GetFirmwareVersion();
466}
467
468bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration)
469{
470 return m_commands->PersistConfiguration(configuration);
471}
472
473bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration)
474{
475 return m_commands->GetConfiguration(configuration);
476}
477
478CStdString CUSBCECAdapterCommunication::GetPortName(void)
479{
480 return m_port->GetName();
481}
482
483bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
484{
485 return m_commands->SetControlledMode(controlled);
486}
487
488void *CAdapterPingThread::Process(void)
489{
490 while (!IsStopped())
491 {
492 if (m_timeout.TimeLeft() == 0)
493 {
494 /* reinit the timeout */
495 m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT);
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 }
521 }
522
523 Sleep(500);
524 }
525 return NULL;
526}