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