cec: removed dupe m_bRunning properties. wait until a thread is started before return...
[deb_libcec.git] / src / lib / CECProcessor.cpp
1 /*
2 * This file is part of the libCEC(R) library.
3 *
4 * libCEC(R) is Copyright (C) 2011 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 "CECProcessor.h"
34
35 #include "AdapterCommunication.h"
36 #include "LibCEC.h"
37 #include "util/StdString.h"
38 #include "platform/timeutils.h"
39
40 using namespace CEC;
41 using namespace std;
42
43 CCECProcessor::CCECProcessor(CLibCEC *controller, CAdapterCommunication *serComm, const char *strDeviceName, cec_logical_address iLogicalAddress /* = CECDEVICE_PLAYBACKDEVICE1 */, uint16_t iPhysicalAddress /* = CEC_DEFAULT_PHYSICAL_ADDRESS*/) :
44 m_physicaladdress(iPhysicalAddress),
45 m_iLogicalAddress(iLogicalAddress),
46 m_strDeviceName(strDeviceName),
47 m_communication(serComm),
48 m_controller(controller)
49 {
50 }
51
52 CCECProcessor::~CCECProcessor(void)
53 {
54 StopThread();
55 m_communication = NULL;
56 m_controller = NULL;
57 }
58
59 bool CCECProcessor::Start(void)
60 {
61 if (!m_communication || !m_communication->IsOpen())
62 {
63 m_controller->AddLog(CEC_LOG_ERROR, "connection is closed");
64 return false;
65 }
66
67 if (!SetLogicalAddress(m_iLogicalAddress))
68 {
69 m_controller->AddLog(CEC_LOG_ERROR, "could not set the logical address");
70 return false;
71 }
72
73 if (CreateThread())
74 return true;
75 else
76 m_controller->AddLog(CEC_LOG_ERROR, "could not create a processor thread");
77
78 return false;
79 }
80
81 void *CCECProcessor::Process(void)
82 {
83 m_controller->AddLog(CEC_LOG_DEBUG, "processor thread started");
84
85 while (!IsStopped())
86 {
87 bool bParseFrame(false);
88 {
89 CLockObject lock(&m_mutex);
90 cec_frame msg;
91 msg.clear();
92
93 if (m_communication->IsOpen() && m_communication->Read(msg, CEC_BUTTON_TIMEOUT))
94 bParseFrame = ParseMessage(msg) && !IsStopped();
95 }
96
97 if (bParseFrame)
98 ParseCurrentFrame();
99
100 m_controller->CheckKeypressTimeout();
101
102 if (!IsStopped())
103 Sleep(50);
104 }
105
106 return NULL;
107 }
108
109 bool CCECProcessor::PowerOnDevices(cec_logical_address address /* = CECDEVICE_TV */)
110 {
111 if (!IsRunning())
112 return false;
113
114 CStdString strLog;
115 strLog.Format("powering on devices with logical address %d", (int8_t)address);
116 m_controller->AddLog(CEC_LOG_DEBUG, strLog.c_str());
117 cec_frame frame;
118 frame.clear();
119
120 frame.push_back(GetSourceDestination(address));
121 frame.push_back((uint8_t) CEC_OPCODE_TEXT_VIEW_ON);
122 return Transmit(frame);
123 }
124
125 bool CCECProcessor::StandbyDevices(cec_logical_address address /* = CECDEVICE_BROADCAST */)
126 {
127 if (!IsRunning())
128 return false;
129
130 CStdString strLog;
131 strLog.Format("putting all devices with logical address %d in standby mode", (int8_t)address);
132 m_controller->AddLog(CEC_LOG_DEBUG, strLog.c_str());
133 cec_frame frame;
134 frame.clear();
135
136 frame.push_back(GetSourceDestination(address));
137 frame.push_back((uint8_t) CEC_OPCODE_STANDBY);
138 return Transmit(frame);
139 }
140
141 bool CCECProcessor::SetActiveView(void)
142 {
143 if (!IsRunning())
144 return false;
145
146 m_controller->AddLog(CEC_LOG_DEBUG, "setting active view");
147 cec_frame frame;
148 frame.clear();
149
150 frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST));
151 frame.push_back((uint8_t) CEC_OPCODE_ACTIVE_SOURCE);
152 frame.push_back((m_physicaladdress >> 8) & 0xFF);
153 frame.push_back(m_physicaladdress & 0xFF);
154 return Transmit(frame);
155 }
156
157 bool CCECProcessor::SetInactiveView(void)
158 {
159 if (!IsRunning())
160 return false;
161
162 m_controller->AddLog(CEC_LOG_DEBUG, "setting inactive view");
163 cec_frame frame;
164 frame.clear();
165
166 frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST));
167 frame.push_back((uint8_t) CEC_OPCODE_INACTIVE_SOURCE);
168 frame.push_back((m_physicaladdress >> 8) & 0xFF);
169 frame.push_back(m_physicaladdress & 0xFF);
170 return Transmit(frame);
171 }
172
173 bool CCECProcessor::Transmit(const cec_frame &data, bool bWaitForAck /* = true */)
174 {
175 CStdString txStr = "transmit ";
176 for (unsigned int i = 0; i < data.size; i++)
177 txStr.AppendFormat(" %02x", data.data[i]);
178 m_controller->AddLog(CEC_LOG_DEBUG, txStr.c_str());
179
180 if (data.size == 0)
181 {
182 m_controller->AddLog(CEC_LOG_WARNING, "transmit buffer is empty");
183 return false;
184 }
185
186 cec_frame output;
187 output.clear();
188
189 //set ack polarity to high when transmitting to the broadcast address
190 //set ack polarity low when transmitting to any other address
191 output.push_back(MSGSTART);
192 CAdapterCommunication::PushEscaped(output, MSGCODE_TRANSMIT_ACK_POLARITY);
193
194 if ((data.data[0] & 0xF) == 0xF)
195 CAdapterCommunication::PushEscaped(output, CEC_TRUE);
196 else
197 CAdapterCommunication::PushEscaped(output, CEC_FALSE);
198
199 output.push_back(MSGEND);
200
201 for (int8_t i = 0; i < data.size; i++)
202 {
203 output.push_back(MSGSTART);
204
205 if (i == (int8_t)data.size - 1)
206 CAdapterCommunication::PushEscaped(output, MSGCODE_TRANSMIT_EOM);
207 else
208 CAdapterCommunication::PushEscaped(output, MSGCODE_TRANSMIT);
209
210 CAdapterCommunication::PushEscaped(output, data.data[i]);
211
212 output.push_back(MSGEND);
213 }
214
215 return TransmitFormatted(output, bWaitForAck);
216 }
217
218 bool CCECProcessor::SetLogicalAddress(cec_logical_address iLogicalAddress)
219 {
220 CStdString strLog;
221 strLog.Format("setting logical address to %d", iLogicalAddress);
222 m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str());
223
224 m_iLogicalAddress = iLogicalAddress;
225 return m_communication && m_communication->SetAckMask(0x1 << (uint8_t)m_iLogicalAddress);
226 }
227
228 bool CCECProcessor::TransmitFormatted(const cec_frame &data, bool bWaitForAck /* = true */)
229 {
230 CLockObject lock(&m_mutex);
231 if (!m_communication || !m_communication->Write(data))
232 return false;
233
234 if (bWaitForAck && !WaitForAck())
235 {
236 m_controller->AddLog(CEC_LOG_DEBUG, "did not receive ACK");
237 return false;
238 }
239
240 return true;
241 }
242
243 void CCECProcessor::TransmitAbort(cec_logical_address address, cec_opcode opcode, ECecAbortReason reason /* = CEC_ABORT_REASON_UNRECOGNIZED_OPCODE */)
244 {
245 m_controller->AddLog(CEC_LOG_DEBUG, "transmitting abort message");
246 cec_frame frame;
247 frame.clear();
248
249 frame.push_back(GetSourceDestination(address));
250 frame.push_back((uint8_t) CEC_OPCODE_FEATURE_ABORT);
251 frame.push_back((uint8_t) opcode);
252 frame.push_back((uint8_t) reason);
253 Transmit(frame);
254 }
255
256 void CCECProcessor::ReportCECVersion(cec_logical_address address /* = CECDEVICE_TV */)
257 {
258 cec_frame frame;
259 frame.clear();
260
261 m_controller->AddLog(CEC_LOG_NOTICE, "reporting CEC version as 1.3a");
262 frame.push_back(GetSourceDestination(address));
263 frame.push_back((uint8_t) CEC_OPCODE_CEC_VERSION);
264 frame.push_back((uint8_t) CEC_VERSION_1_3A);
265 Transmit(frame);
266 }
267
268 void CCECProcessor::ReportPowerState(cec_logical_address address /*= CECDEVICE_TV */, bool bOn /* = true */)
269 {
270 cec_frame frame;
271 frame.clear();
272
273 if (bOn)
274 m_controller->AddLog(CEC_LOG_NOTICE, "reporting \"On\" power status");
275 else
276 m_controller->AddLog(CEC_LOG_NOTICE, "reporting \"Off\" power status");
277
278 frame.push_back(GetSourceDestination(address));
279 frame.push_back((uint8_t) CEC_OPCODE_REPORT_POWER_STATUS);
280 frame.push_back(bOn ? (uint8_t) CEC_POWER_STATUS_ON : (uint8_t) CEC_POWER_STATUS_STANDBY);
281 Transmit(frame);
282 }
283
284 void CCECProcessor::ReportMenuState(cec_logical_address address /* = CECDEVICE_TV */, bool bActive /* = true */)
285 {
286 cec_frame frame;
287 frame.clear();
288
289 if (bActive)
290 m_controller->AddLog(CEC_LOG_NOTICE, "reporting menu state as active");
291 else
292 m_controller->AddLog(CEC_LOG_NOTICE, "reporting menu state as inactive");
293
294 frame.push_back(GetSourceDestination(address));
295 frame.push_back((uint8_t) CEC_OPCODE_MENU_STATUS);
296 frame.push_back(bActive ? (uint8_t) CEC_MENU_STATE_ACTIVATED : (uint8_t) CEC_MENU_STATE_DEACTIVATED);
297 Transmit(frame);
298 }
299
300 void CCECProcessor::ReportVendorID(cec_logical_address address /* = CECDEVICE_TV */)
301 {
302 m_controller->AddLog(CEC_LOG_NOTICE, "vendor ID requested, feature abort");
303 TransmitAbort(address, CEC_OPCODE_GIVE_DEVICE_VENDOR_ID);
304 }
305
306 void CCECProcessor::ReportOSDName(cec_logical_address address /* = CECDEVICE_TV */)
307 {
308 cec_frame frame;
309 frame.clear();
310
311 const char *osdname = m_strDeviceName.c_str();
312 CStdString strLog;
313 strLog.Format("reporting OSD name as %s", osdname);
314 m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str());
315 frame.push_back(GetSourceDestination(address));
316 frame.push_back((uint8_t) CEC_OPCODE_SET_OSD_NAME);
317
318 for (unsigned int i = 0; i < strlen(osdname); i++)
319 frame.push_back(osdname[i]);
320
321 Transmit(frame);
322 }
323
324 void CCECProcessor::ReportPhysicalAddress(void)
325 {
326 cec_frame frame;
327 frame.clear();
328
329 CStdString strLog;
330 strLog.Format("reporting physical address as %04x", m_physicaladdress);
331 m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str());
332 frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST));
333 frame.push_back((uint8_t) CEC_OPCODE_REPORT_PHYSICAL_ADDRESS);
334 frame.push_back((uint8_t) ((m_physicaladdress >> 8) & 0xFF));
335 frame.push_back((uint8_t) (m_physicaladdress & 0xFF));
336 frame.push_back((uint8_t) CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
337 Transmit(frame);
338 }
339
340 void CCECProcessor::BroadcastActiveSource(void)
341 {
342 cec_frame frame;
343 frame.clear();
344
345 m_controller->AddLog(CEC_LOG_NOTICE, "broadcasting active source");
346 frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST));
347 frame.push_back((uint8_t) CEC_OPCODE_ACTIVE_SOURCE);
348 frame.push_back((uint8_t) ((m_physicaladdress >> 8) & 0xFF));
349 frame.push_back((uint8_t) (m_physicaladdress & 0xFF));
350 Transmit(frame);
351 }
352
353 uint8_t CCECProcessor::GetSourceDestination(cec_logical_address destination /* = CECDEVICE_BROADCAST */) const
354 {
355 return ((uint8_t)m_iLogicalAddress << 4) + (uint8_t)destination;
356 }
357
358 bool CCECProcessor::WaitForAck(uint32_t iTimeout /* = 1000 */)
359 {
360 bool bGotAck(false);
361 bool bError(false);
362
363 int64_t iNow = GetTimeMs();
364 int64_t iTargetTime = iNow + (uint64_t) iTimeout;
365
366 while (!bGotAck && !bError && (iTimeout == 0 || iNow < iTargetTime))
367 {
368 cec_frame msg;
369 msg.clear();
370
371 while (!bGotAck && !bError && m_communication->Read(msg, iTimeout))
372 {
373 uint8_t iCode = msg.data[0] & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK);
374
375 switch (iCode)
376 {
377 case MSGCODE_COMMAND_ACCEPTED:
378 m_controller->AddLog(CEC_LOG_DEBUG, "MSGCODE_COMMAND_ACCEPTED");
379 break;
380 case MSGCODE_TRANSMIT_SUCCEEDED:
381 m_controller->AddLog(CEC_LOG_DEBUG, "MSGCODE_TRANSMIT_SUCCEEDED");
382 // TODO
383 bGotAck = true;
384 break;
385 case MSGCODE_RECEIVE_FAILED:
386 m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_RECEIVE_FAILED");
387 bError = true;
388 break;
389 case MSGCODE_COMMAND_REJECTED:
390 m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_COMMAND_REJECTED");
391 bError = true;
392 break;
393 case MSGCODE_TRANSMIT_FAILED_LINE:
394 m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_LINE");
395 bError = true;
396 break;
397 case MSGCODE_TRANSMIT_FAILED_ACK:
398 m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_ACK");
399 bError = true;
400 break;
401 case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA:
402 m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA");
403 bError = true;
404 break;
405 case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE:
406 m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE");
407 bError = true;
408 break;
409 default:
410 m_frameBuffer.Push(msg);
411 bGotAck = (msg.data[0] & MSGCODE_FRAME_ACK) != 0;
412 break;
413 }
414 iNow = GetTimeMs();
415 }
416 }
417
418 return bGotAck && !bError;
419 }
420
421 bool CCECProcessor::ParseMessage(cec_frame &msg)
422 {
423 bool bReturn(false);
424
425 if (msg.size == 0)
426 return bReturn;
427
428 CStdString logStr;
429 uint8_t iCode = msg.data[0] & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK);
430 bool bEom = (msg.data[0] & MSGCODE_FRAME_EOM) != 0;
431 bool bAck = (msg.data[0] & MSGCODE_FRAME_ACK) != 0;
432
433 switch(iCode)
434 {
435 case MSGCODE_NOTHING:
436 m_controller->AddLog(CEC_LOG_DEBUG, "MSGCODE_NOTHING");
437 break;
438 case MSGCODE_TIMEOUT_ERROR:
439 case MSGCODE_HIGH_ERROR:
440 case MSGCODE_LOW_ERROR:
441 {
442 if (iCode == MSGCODE_TIMEOUT_ERROR)
443 logStr = "MSGCODE_TIMEOUT";
444 else if (iCode == MSGCODE_HIGH_ERROR)
445 logStr = "MSGCODE_HIGH_ERROR";
446 else
447 logStr = "MSGCODE_LOW_ERROR";
448
449 int iLine = (msg.size >= 3) ? (msg.data[1] << 8) | (msg.data[2]) : 0;
450 uint32_t iTime = (msg.size >= 7) ? (msg.data[3] << 24) | (msg.data[4] << 16) | (msg.data[5] << 8) | (msg.data[6]) : 0;
451 logStr.AppendFormat(" line:%i", iLine);
452 logStr.AppendFormat(" time:%u", iTime);
453 m_controller->AddLog(CEC_LOG_WARNING, logStr.c_str());
454 }
455 break;
456 case MSGCODE_FRAME_START:
457 {
458 logStr = "MSGCODE_FRAME_START";
459 m_currentframe.clear();
460 if (msg.size >= 2)
461 {
462 int iInitiator = msg.data[1] >> 4;
463 int iDestination = msg.data[1] & 0xF;
464 logStr.AppendFormat(" initiator:%u destination:%u ack:%s %s", iInitiator, iDestination, bAck ? "high" : "low", bEom ? "eom" : "");
465
466 m_currentframe.push_back(msg.data[1]);
467 }
468 m_controller->AddLog(CEC_LOG_DEBUG, logStr.c_str());
469 }
470 break;
471 case MSGCODE_FRAME_DATA:
472 {
473 logStr = "MSGCODE_FRAME_DATA";
474 if (msg.size >= 2)
475 {
476 uint8_t iData = msg.data[1];
477 logStr.AppendFormat(" %02x", iData);
478 m_currentframe.push_back(iData);
479 }
480 m_controller->AddLog(CEC_LOG_DEBUG, logStr.c_str());
481 }
482 if (bEom)
483 bReturn = true;
484 break;
485 default:
486 break;
487 }
488
489 return bReturn;
490 }
491
492 void CCECProcessor::ParseCurrentFrame(void)
493 {
494 uint8_t initiator = m_currentframe.data[0] >> 4;
495 uint8_t destination = m_currentframe.data[0] & 0xF;
496
497 CStdString dataStr;
498 dataStr.Format("received frame: initiator: %u destination: %u", initiator, destination);
499
500 if (m_currentframe.size > 1)
501 {
502 dataStr += " data:";
503 for (unsigned int i = 1; i < m_currentframe.size; i++)
504 dataStr.AppendFormat(" %02x", m_currentframe.data[i]);
505 }
506 m_controller->AddLog(CEC_LOG_DEBUG, dataStr.c_str());
507
508 if (m_currentframe.size <= 1)
509 return;
510
511 cec_opcode opCode = (cec_opcode) m_currentframe.data[1];
512 if (destination == (uint16_t) m_iLogicalAddress)
513 {
514 switch(opCode)
515 {
516 case CEC_OPCODE_GIVE_PHYSICAL_ADDRESS:
517 ReportPhysicalAddress();
518 SetActiveView();
519 break;
520 case CEC_OPCODE_GIVE_OSD_NAME:
521 ReportOSDName((cec_logical_address)initiator);
522 break;
523 case CEC_OPCODE_GIVE_DEVICE_VENDOR_ID:
524 ReportVendorID((cec_logical_address)initiator);
525 break;
526 case CEC_OPCODE_MENU_REQUEST:
527 ReportMenuState((cec_logical_address)initiator);
528 break;
529 case CEC_OPCODE_GIVE_DEVICE_POWER_STATUS:
530 ReportPowerState((cec_logical_address)initiator);
531 break;
532 case CEC_OPCODE_GET_CEC_VERSION:
533 ReportCECVersion((cec_logical_address)initiator);
534 break;
535 case CEC_OPCODE_USER_CONTROL_PRESSED:
536 if (m_currentframe.size > 2)
537 {
538 m_controller->AddKey();
539
540 if (m_currentframe.data[2] <= CEC_USER_CONTROL_CODE_MAX)
541 m_controller->SetCurrentButton((cec_user_control_code) m_currentframe.data[2]);
542 }
543 break;
544 case CEC_OPCODE_USER_CONTROL_RELEASE:
545 m_controller->AddKey();
546 break;
547 default:
548 cec_frame params = m_currentframe;
549 params.shift(2);
550 m_controller->AddCommand((cec_logical_address) initiator, (cec_logical_address) destination, opCode, &params);
551 break;
552 }
553 }
554 else if (destination == (uint8_t) CECDEVICE_BROADCAST)
555 {
556 if (opCode == CEC_OPCODE_REQUEST_ACTIVE_SOURCE)
557 {
558 BroadcastActiveSource();
559 }
560 else if (opCode == CEC_OPCODE_SET_STREAM_PATH)
561 {
562 if (m_currentframe.size >= 4)
563 {
564 int streamaddr = ((int)m_currentframe.data[2] << 8) | ((int)m_currentframe.data[3]);
565 CStdString strLog;
566 strLog.Format("%i requests stream path from physical address %04x", initiator, streamaddr);
567 m_controller->AddLog(CEC_LOG_DEBUG, strLog.c_str());
568 if (streamaddr == m_physicaladdress)
569 BroadcastActiveSource();
570 }
571 }
572 else
573 {
574 cec_frame params = m_currentframe;
575 params.shift(2);
576 m_controller->AddCommand((cec_logical_address) initiator, (cec_logical_address) destination, opCode, &params);
577 }
578 }
579 else
580 {
581 CStdString strLog;
582 strLog.Format("ignoring frame: destination: %u != %u", destination, (uint16_t)m_iLogicalAddress);
583 m_controller->AddLog(CEC_LOG_DEBUG, strLog.c_str());
584 }
585 }