Merge branch 'adaptercomm'. bugzid: 654
[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 }
68
69 bool 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
157 void 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
181 cec_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
206 void *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
226 bool 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
256 void 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
266 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = 1000 */)
267 {
268 CTimeout timeout(iTimeout);
269 uint8_t buff[1024];
270 ssize_t iBytesRead(0);
271 bool bGotMsgEnd(true);
272
273 while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd))
274 {
275 bGotMsgEnd = false;
276 /* if something was received, wait for MSGEND */
277 for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++)
278 bGotMsgEnd = buff[iPtr] == MSGEND;
279 }
280 }
281
282 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
283 {
284 bool bReturn(true);
285 bool bChanged(false);
286
287 /* only send the command if the timeout changed */
288 {
289 CLockObject lock(m_mutex);
290 bChanged = (m_iLineTimeout != iTimeout);
291 m_iLineTimeout = iTimeout;
292 }
293
294 if (bChanged)
295 bReturn = m_commands->SetLineTimeout(iTimeout);
296
297 return bReturn;
298 }
299
300 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
301 {
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 }
309
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;
315 return false;
316 }
317
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;
321 }
322
323 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */)
324 {
325 ssize_t iBytesRead(0);
326 uint8_t buff[256];
327 if (iSize > 256)
328 iSize = 256;
329
330 /* read from the serial port */
331 {
332 CLockObject lock(m_mutex);
333 if (!m_port)
334 return false;
335 iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
336 }
337
338 if (iBytesRead < 0 || iBytesRead > 256)
339 {
340 CLibCEC::AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
341 StopThread(false);
342 return false;
343 }
344 else if (iBytesRead > 0)
345 {
346 /* add the data to the current frame */
347 m_adapterMessageQueue->AddData(buff, iBytesRead);
348 }
349
350 return true;
351 }
352
353 CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage &params, bool bIsRetry /* = false */)
354 {
355 if (!m_port || !m_port->IsOpen() ||
356 !m_adapterMessageQueue)
357 return NULL;
358
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);
365
366 /* write the command */
367 if (!m_adapterMessageQueue->Write(output))
368 {
369 // timed out
370 return output;
371 }
372 else
373 {
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 }
384 }
385
386 return output;
387 }
388
389 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */)
390 {
391 bool bReturn(false);
392 CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT);
393
394 /* try to ping the adapter */
395 bool bPinged(false);
396 unsigned iPingTry(0);
397 while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false)
398 {
399 CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
400 CEvent::Sleep(500);
401 }
402
403 /* try to read the firmware version */
404 if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2)
405 {
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;
415 }
416 else
417 bReturn = true;
418
419 SetInitialised(bReturn);
420 return bReturn;
421 }
422
423 bool CUSBCECAdapterCommunication::IsOpen(void)
424 {
425 /* thread is not being stopped, the port is open and the thread is running */
426 return !IsStopped() && m_port->IsOpen() && IsRunning();
427 }
428
429 CStdString CUSBCECAdapterCommunication::GetError(void) const
430 {
431 return m_port->GetError();
432 }
433
434 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */)
435 {
436 CLockObject lock(m_mutex);
437 m_bInitialised = bSetTo;
438 }
439
440 bool CUSBCECAdapterCommunication::IsInitialised(void)
441 {
442 CLockObject lock(m_mutex);
443 return m_bInitialised;
444 }
445
446 bool CUSBCECAdapterCommunication::StartBootloader(void)
447 {
448 if (!IsRunning())
449 return false;
450
451 return m_commands->StartBootloader();
452 }
453
454 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
455 {
456 return m_commands->SetAckMask(iMask);
457 }
458
459 bool CUSBCECAdapterCommunication::PingAdapter(void)
460 {
461 return m_commands->PingAdapter();
462 }
463
464 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
465 {
466 return m_commands->GetFirmwareVersion();
467 }
468
469 bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration)
470 {
471 return m_commands->PersistConfiguration(configuration);
472 }
473
474 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration)
475 {
476 return m_commands->GetConfiguration(configuration);
477 }
478
479 CStdString CUSBCECAdapterCommunication::GetPortName(void)
480 {
481 return m_port->GetName();
482 }
483
484 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
485 {
486 return m_commands->SetControlledMode(controlled);
487 }
488
489 void *CAdapterPingThread::Process(void)
490 {
491 while (!IsStopped())
492 {
493 if (m_timeout.TimeLeft() == 0)
494 {
495 /* reinit the timeout */
496 m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT);
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 }
522 }
523
524 Sleep(500);
525 }
526 return NULL;
527 }