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