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