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