cec: delay the 'active source' command for panasonic, until the vendor command that...
[deb_libcec.git] / src / lib / adapter / USBCECAdapterCommunication.cpp
... / ...
CommitLineData
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
41using namespace std;
42using namespace CEC;
43using namespace PLATFORM;
44
45#define CEC_ADAPTER_PING_TIMEOUT 15000
46
47CUSBCECAdapterCommunication::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
62CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
63{
64 Close();
65 delete m_commands;
66 delete m_adapterMessageQueue;
67 delete m_port;
68}
69
70bool 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
153void CUSBCECAdapterCommunication::Close(void)
154{
155 /* stop the reader thread */
156 StopThread(0);
157
158 CLockObject lock(m_mutex);
159
160 /* set the ackmask to 0 before closing the connection */
161 if (IsRunning() && m_port->IsOpen() && m_port->GetErrorNumber() == 0)
162 {
163 CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - closing the connection", __FUNCTION__);
164 SetAckMask(0);
165 if (m_commands->GetFirmwareVersion() >= 2)
166 SetControlledMode(false);
167 }
168
169 /* stop and delete the ping thread */
170 if (m_pingThread)
171 m_pingThread->StopThread(0);
172 delete m_pingThread;
173 m_pingThread = NULL;
174
175 /* close and delete the com port connection */
176 if (m_port)
177 m_port->Close();
178
179 libcec_parameter param;
180 CLibCEC::Alert(CEC_ALERT_CONNECTION_LOST, param);
181}
182
183cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, bool &bRetry, uint8_t iLineTimeout)
184{
185 cec_adapter_message_state retVal(ADAPTER_MESSAGE_STATE_UNKNOWN);
186 if (!IsRunning())
187 return retVal;
188
189 CCECAdapterMessage *output = new CCECAdapterMessage(data, iLineTimeout);
190
191 /* mark as waiting for an ack from the destination */
192 MarkAsWaiting(data.destination);
193
194 /* send the message */
195 bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
196 if (output->state == ADAPTER_MESSAGE_STATE_ERROR)
197 Close();
198 else if (bRetry)
199 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
200 retVal = output->state;
201
202 delete output;
203 return retVal;
204}
205
206void *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
226bool 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
256void 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
266void 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
282bool 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
300bool 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 serial port '%s': the connection is closed", CCECAdapterMessage::ToString(message->Message()), m_port->GetName().c_str());
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 serial port '%s': %s", CCECAdapterMessage::ToString(message->Message()), m_port->GetName().c_str(), 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
323bool 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 || !m_port->IsOpen())
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 Close();
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
353CCECAdapterMessage *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 if (output->state == ADAPTER_MESSAGE_STATE_ERROR)
370 Close();
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
390bool 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
424bool 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
430CStdString CUSBCECAdapterCommunication::GetError(void) const
431{
432 return m_port->GetError();
433}
434
435void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */)
436{
437 CLockObject lock(m_mutex);
438 m_bInitialised = bSetTo;
439}
440
441bool CUSBCECAdapterCommunication::IsInitialised(void)
442{
443 CLockObject lock(m_mutex);
444 return m_bInitialised;
445}
446
447bool CUSBCECAdapterCommunication::StartBootloader(void)
448{
449 return m_port->IsOpen() ? m_commands->StartBootloader() : false;
450}
451
452bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
453{
454 return m_port->IsOpen() ? m_commands->SetAckMask(iMask) : false;
455}
456
457bool CUSBCECAdapterCommunication::PingAdapter(void)
458{
459 return m_port->IsOpen() ? m_commands->PingAdapter() : false;
460}
461
462uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
463{
464 return m_commands->GetFirmwareVersion();
465}
466
467bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration)
468{
469 return m_port->IsOpen() ? m_commands->PersistConfiguration(configuration) : false;
470}
471
472bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration)
473{
474 return m_port->IsOpen() ? m_commands->GetConfiguration(configuration) : false;
475}
476
477CStdString CUSBCECAdapterCommunication::GetPortName(void)
478{
479 return m_port->GetName();
480}
481
482bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
483{
484 return m_port->IsOpen() ? m_commands->SetControlledMode(controlled) : false;
485}
486
487void *CAdapterPingThread::Process(void)
488{
489 while (!IsStopped())
490 {
491 if (m_timeout.TimeLeft() == 0)
492 {
493 /* reinit the timeout */
494 m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT);
495
496 /* send a ping to the adapter */
497 bool bPinged(false);
498 int iFailedCounter(0);
499 while (!bPinged && iFailedCounter < 3)
500 {
501 if (!m_com->PingAdapter())
502 {
503 /* sleep 1 second and retry */
504 Sleep(1000);
505 ++iFailedCounter;
506 }
507 else
508 {
509 bPinged = true;
510 }
511 }
512
513 if (iFailedCounter == 3)
514 {
515 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
516 CLibCEC::AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection.");
517 m_com->StopThread(false);
518 break;
519 }
520 }
521
522 Sleep(500);
523 }
524 return NULL;
525}