cec: fix compilation on windows after interface changes
[deb_libcec.git] / src / lib / CECParser.cpp
CommitLineData
abbca718
LOK
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 "CECParser.h"
34
35#include <algorithm>
36#include <cstdio>
37#include <cstdlib>
38#include <cstring>
39#include <sys/stat.h>
40#include "util/StdString.h"
41#include "libPlatform/serialport.h"
42#include "util/threads.h"
43#include "util/timeutils.h"
44#include "CECDetect.h"
45
46using namespace CEC;
47using namespace std;
48
49#define CEC_MAX_RETRY 5
50
51/*!
52 * ICECDevice implementation
53 */
54//@{
df7339c6 55CCECParser::CCECParser(const char *strDeviceName, cec_logical_address iLogicalAddress /* = CECDEVICE_PLAYBACKDEVICE1 */, int iPhysicalAddress /* = CEC_DEFAULT_PHYSICAL_ADDRESS*/) :
abbca718
LOK
56 m_inbuf(NULL),
57 m_iInbufSize(0),
58 m_iInbufUsed(0),
59 m_iCurrentButton(CEC_USER_CONTROL_CODE_UNKNOWN),
df7339c6
LOK
60 m_physicaladdress(iPhysicalAddress),
61 m_iLogicalAddress(iLogicalAddress),
abbca718
LOK
62 m_strDeviceName(strDeviceName),
63 m_bRunning(false)
64{
65 m_serialport = new CSerialPort;
66}
67
68CCECParser::~CCECParser(void)
69{
70 m_bRunning = false;
71 pthread_join(m_thread, NULL);
72 m_serialport->Close();
73 delete m_serialport;
74}
75
76bool CCECParser::Open(const char *strPort, int iTimeoutMs /* = 10000 */)
77{
78 bool bReturn(false);
79
80 if (!(bReturn = m_serialport->Open(strPort, 38400)))
81 {
82 CStdString strError;
83 strError.Format("error opening serial port '%s': %s", strPort, m_serialport->GetError().c_str());
84 AddLog(CEC_LOG_ERROR, strError);
85 return bReturn;
86 }
87
88 //clear any input bytes
89 uint8_t buff[1024];
90 m_serialport->Read(buff, sizeof(buff), CEC_SETTLE_DOWN_TIME);
91
92 if (bReturn)
6dfe9213 93 bReturn = SetLogicalAddress(m_iLogicalAddress);
abbca718
LOK
94
95 if (!bReturn)
96 {
97 CStdString strError;
98 strError.Format("error opening serial port '%s': %s", strPort, m_serialport->GetError().c_str());
99 AddLog(CEC_LOG_ERROR, strError);
100 return bReturn;
101 }
102
103 if (bReturn)
104 {
105 m_bRunning = true;
106 if (pthread_create(&m_thread, NULL, (void *(*) (void *))&CCECParser::ThreadHandler, (void *)this) == 0)
107 pthread_detach(m_thread);
108 else
109 m_bRunning = false;
110 }
111
112 return bReturn;
113}
114
115void *CCECParser::ThreadHandler(CCECParser *parser)
116{
117 if (parser)
118 parser->Process();
119 return 0;
120}
121
122bool CCECParser::Process(void)
123{
124 int64_t now = GetTimeMs();
125 while (m_bRunning)
126 {
127 {
128 CLockObject lock(&m_mutex, 1000);
129 if (lock.IsLocked())
130 {
131 if (!ReadFromDevice(100))
132 {
133 m_bRunning = false;
134 return false;
135 }
136 }
137 }
138
139 //AddLog(CEC_LOG_DEBUG, "processing messages");
140 ProcessMessages();
141 now = GetTimeMs();
142 CheckKeypressTimeout(now);
143 CCondition::Sleep(50);
144 }
145
146 AddLog(CEC_LOG_DEBUG, "reader thread terminated");
147 m_bRunning = false;
148 return true;
149}
150
151bool CCECParser::Ping(void)
152{
153 AddLog(CEC_LOG_DEBUG, "sending ping");
154 cec_frame output;
155 output.push_back(MSGSTART);
156 PushEscaped(output, MSGCODE_PING);
157 output.push_back(MSGEND);
158
159 if (!TransmitFormatted(output, false, (int64_t) 5000))
160 {
161 AddLog(CEC_LOG_ERROR, "could not send ping command");
162 return false;
163 }
164
165 AddLog(CEC_LOG_DEBUG, "ping tranmitted");
166
167 // TODO check for pong
168 return true;
169}
170
171bool CCECParser::StartBootloader(void)
172{
173 AddLog(CEC_LOG_DEBUG, "starting the bootloader");
174 cec_frame output;
175 output.push_back(MSGSTART);
176 PushEscaped(output, MSGCODE_START_BOOTLOADER);
177 output.push_back(MSGEND);
178
179 if (!TransmitFormatted(output, false, (int64_t) 5000))
180 {
181 AddLog(CEC_LOG_ERROR, "could not start the bootloader");
182 return false;
183 }
184
185 AddLog(CEC_LOG_DEBUG, "bootloader start command transmitted");
186 return true;
187}
188
189uint8_t CCECParser::GetSourceDestination(cec_logical_address destination /* = CECDEVICE_BROADCAST */)
190{
191 return ((uint8_t)m_iLogicalAddress << 4) + (uint8_t)destination;
192}
193
194bool CCECParser::PowerOffDevices(cec_logical_address address /* = CECDEVICE_BROADCAST */)
195{
196 CStdString strLog;
197 strLog.Format("powering off devices with logical address %d", (int8_t)address);
198 AddLog(CEC_LOG_DEBUG, strLog.c_str());
199 cec_frame frame;
200 frame.push_back(GetSourceDestination(address));
201 frame.push_back(CEC_OPCODE_STANDBY);
202 return Transmit(frame);
203}
204
205bool CCECParser::PowerOnDevices(cec_logical_address address /* = CECDEVICE_BROADCAST */)
206{
207 CStdString strLog;
208 strLog.Format("powering on devices with logical address %d", (int8_t)address);
209 AddLog(CEC_LOG_DEBUG, strLog.c_str());
210 cec_frame frame;
211 frame.push_back(GetSourceDestination(address));
212 frame.push_back(CEC_OPCODE_TEXT_VIEW_ON);
213 return Transmit(frame);
214}
215
216bool CCECParser::StandbyDevices(cec_logical_address address /* = CECDEVICE_BROADCAST */)
217{
218 CStdString strLog;
219 strLog.Format("putting all devices with logical address %d in standby mode", (int8_t)address);
220 AddLog(CEC_LOG_DEBUG, strLog.c_str());
221 cec_frame frame;
222 frame.push_back(GetSourceDestination(address));
223 frame.push_back(CEC_OPCODE_STANDBY);
224 return Transmit(frame);
225}
226
227bool CCECParser::SetActiveView(void)
228{
229 AddLog(CEC_LOG_DEBUG, "setting active view");
230 cec_frame frame;
231 frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST));
232 frame.push_back(CEC_OPCODE_ACTIVE_SOURCE);
233 frame.push_back((m_physicaladdress >> 8) & 0xFF);
234 frame.push_back(m_physicaladdress & 0xFF);
235 return Transmit(frame);
236}
237
238bool CCECParser::SetInactiveView(void)
239{
240 AddLog(CEC_LOG_DEBUG, "setting inactive view");
241 cec_frame frame;
242 frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST));
243 frame.push_back(CEC_OPCODE_INACTIVE_SOURCE);
244 frame.push_back((m_physicaladdress >> 8) & 0xFF);
245 frame.push_back(m_physicaladdress & 0xFF);
246 return Transmit(frame);
247}
248
249bool CCECParser::GetNextLogMessage(cec_log_message *message)
250{
251 return m_logBuffer.Pop(*message);
252}
253
254bool CCECParser::GetNextKeypress(cec_keypress *key)
255{
256 return m_keyBuffer.Pop(*key);
257}
258//@}
259
260void CCECParser::TransmitAbort(cec_logical_address address, ECecOpcode opcode, ECecAbortReason reason /* = CEC_ABORT_REASON_UNRECOGNIZED_OPCODE */)
261{
262 AddLog(CEC_LOG_DEBUG, "transmitting abort message");
263 cec_frame frame;
264 frame.push_back(GetSourceDestination(address));
265 frame.push_back(CEC_OPCODE_FEATURE_ABORT);
266 frame.push_back(opcode);
267 frame.push_back(reason);
268 Transmit(frame);
269}
270
271void CCECParser::ReportCECVersion(cec_logical_address address /* = CECDEVICE_TV */)
272{
273 cec_frame frame;
274 AddLog(CEC_LOG_NOTICE, "reporting CEC version as 1.3a");
275 frame.push_back(GetSourceDestination(address));
276 frame.push_back(CEC_OPCODE_CEC_VERSION);
277 frame.push_back(CEC_VERSION_1_3A);
278 Transmit(frame);
279}
280
281void CCECParser::ReportPowerState(cec_logical_address address /*= CECDEVICE_TV */, bool bOn /* = true */)
282{
283 cec_frame frame;
284 if (bOn)
285 AddLog(CEC_LOG_NOTICE, "reporting \"On\" power status");
286 else
287 AddLog(CEC_LOG_NOTICE, "reporting \"Off\" power status");
288
289 frame.push_back(GetSourceDestination(address));
290 frame.push_back(CEC_OPCODE_REPORT_POWER_STATUS);
291 frame.push_back(bOn ? CEC_POWER_STATUS_ON : CEC_POWER_STATUS_STANDBY);
292 Transmit(frame);
293}
294
295void CCECParser::ReportMenuState(cec_logical_address address /* = CECDEVICE_TV */, bool bActive /* = true */)
296{
297 cec_frame frame;
298 if (bActive)
299 AddLog(CEC_LOG_NOTICE, "reporting menu state as active");
300 else
301 AddLog(CEC_LOG_NOTICE, "reporting menu state as inactive");
302
303 frame.push_back(GetSourceDestination(address));
304 frame.push_back(CEC_OPCODE_MENU_STATUS);
305 frame.push_back(bActive ? CEC_MENU_STATE_ACTIVATED : CEC_MENU_STATE_DEACTIVATED);
306 Transmit(frame);
307}
308
309void CCECParser::ReportVendorID(cec_logical_address address /* = CECDEVICE_TV */)
310{
311 AddLog(CEC_LOG_NOTICE, "vendor ID requested, feature abort");
312 TransmitAbort(address, CEC_OPCODE_GIVE_DEVICE_VENDOR_ID);
313}
314
315void CCECParser::ReportOSDName(cec_logical_address address /* = CECDEVICE_TV */)
316{
317 cec_frame frame;
318 const char *osdname = m_strDeviceName.c_str();
319 CStdString strLog;
320 strLog.Format("reporting OSD name as %s", osdname);
321 AddLog(CEC_LOG_NOTICE, strLog.c_str());
322 frame.push_back(GetSourceDestination(address));
323 frame.push_back(CEC_OPCODE_SET_OSD_NAME);
324
325 for (unsigned int i = 0; i < strlen(osdname); i++)
326 frame.push_back(osdname[i]);
327
328 Transmit(frame);
329}
330
331void CCECParser::ReportPhysicalAddress(void)
332{
333 cec_frame frame;
334 CStdString strLog;
335 strLog.Format("reporting physical address as %04x", m_physicaladdress);
336 AddLog(CEC_LOG_NOTICE, strLog.c_str());
337 frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST));
338 frame.push_back(CEC_OPCODE_REPORT_PHYSICAL_ADDRESS);
339 frame.push_back((m_physicaladdress >> 8) & 0xFF);
340 frame.push_back(m_physicaladdress & 0xFF);
341 frame.push_back(CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
342 Transmit(frame);
343}
344
345void CCECParser::BroadcastActiveSource(void)
346{
347 cec_frame frame;
348 AddLog(CEC_LOG_NOTICE, "broadcasting active source");
349 frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST));
350 frame.push_back(CEC_OPCODE_ACTIVE_SOURCE);
351 frame.push_back((m_physicaladdress >> 8) & 0xFF);
352 frame.push_back(m_physicaladdress & 0xFF);
353 Transmit(frame);
354}
355
356bool CCECParser::TransmitFormatted(const cec_frame &data, bool bWaitForAck /* = true */, int64_t iTimeout /* = 2000 */)
357{
358 CLockObject lock(&m_mutex, iTimeout);
359 if (!lock.IsLocked())
360 {
361 AddLog(CEC_LOG_ERROR, "could not get a write lock");
362 return false;
363 }
364
365 if (m_serialport->Write(data) != data.size())
366 {
367 CStdString strError;
368 strError.Format("error writing to serial port: %s", m_serialport->GetError().c_str());
369 AddLog(CEC_LOG_ERROR, strError);
370 return false;
371 }
372 AddLog(CEC_LOG_DEBUG, "command sent");
373
374 CCondition::Sleep((int) data.size() * 24 /*data*/ + 5 /*start bit (4.5 ms)*/ + 50 /* to be on the safe side */);
375 if (bWaitForAck && !WaitForAck())
376 {
377 AddLog(CEC_LOG_DEBUG, "did not receive ACK");
378 return false;
379 }
380
381 return true;
382}
383
384bool CCECParser::Transmit(const cec_frame &data, bool bWaitForAck /* = true */, int64_t iTimeout /* = 5000 */)
385{
386 CStdString txStr = "transmit ";
387 for (unsigned int i = 0; i < data.size(); i++)
388 txStr.AppendFormat(" %02x", data[i]);
389 AddLog(CEC_LOG_DEBUG, txStr.c_str());
390
391 if (data.empty())
392 {
393 AddLog(CEC_LOG_WARNING, "transmit buffer is empty");
394 return false;
395 }
396
397 cec_frame output;
398
399 //set ack polarity to high when transmitting to the broadcast address
400 //set ack polarity low when transmitting to any other address
401 output.push_back(MSGSTART);
402 PushEscaped(output, MSGCODE_TRANSMIT_ACK_POLARITY);
403
404 if ((data[0] & 0xF) == 0xF)
405 PushEscaped(output, CEC_TRUE);
406 else
407 PushEscaped(output, CEC_FALSE);
408
409 output.push_back(MSGEND);
410
411 for (unsigned int i = 0; i < data.size(); i++)
412 {
413 output.push_back(MSGSTART);
414
415 if (i == data.size() - 1)
416 PushEscaped(output, MSGCODE_TRANSMIT_EOM);
417 else
418 PushEscaped(output, MSGCODE_TRANSMIT);
419
420 PushEscaped(output, data[i]);
421
422 output.push_back(MSGEND);
423 }
424
425 return TransmitFormatted(output, bWaitForAck, iTimeout);
426}
427
428bool CCECParser::WaitForAck(int64_t iTimeout /* = 1000 */)
429{
430 bool bGotAck(false);
431 bool bError(false);
432
433 int64_t iNow = GetTimeMs();
434 int64_t iTargetTime = iNow + iTimeout;
435
436 while (!bGotAck && !bError && (iTimeout <= 0 || iNow < iTargetTime))
437 {
438 if (!ReadFromDevice((int) iTimeout))
439 {
440 AddLog(CEC_LOG_ERROR, "failed to read from device");
441 return false;
442 }
443
444 cec_frame msg;
445 while (!bGotAck && !bError && GetMessage(msg, false))
446 {
447 uint8_t iCode = msg[0] & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK);
448
449 switch (iCode)
450 {
451 case MSGCODE_COMMAND_ACCEPTED:
452 AddLog(CEC_LOG_DEBUG, "MSGCODE_COMMAND_ACCEPTED");
453 break;
454 case MSGCODE_TRANSMIT_SUCCEEDED:
455 AddLog(CEC_LOG_DEBUG, "MSGCODE_TRANSMIT_SUCCEEDED");
456 // TODO
457 bGotAck = true;
458 break;
459 case MSGCODE_RECEIVE_FAILED:
460 AddLog(CEC_LOG_WARNING, "MSGCODE_RECEIVE_FAILED");
461 bError = true;
462 break;
463 case MSGCODE_COMMAND_REJECTED:
464 AddLog(CEC_LOG_WARNING, "MSGCODE_COMMAND_REJECTED");
465 bError = true;
466 break;
467 case MSGCODE_TRANSMIT_FAILED_LINE:
468 AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_LINE");
469 bError = true;
470 break;
471 case MSGCODE_TRANSMIT_FAILED_ACK:
472 AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_ACK");
473 bError = true;
474 break;
475 case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA:
476 AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA");
477 bError = true;
478 break;
479 case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE:
480 AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE");
481 bError = true;
482 break;
483 default:
484 m_frameBuffer.Push(msg);
485 bGotAck = (msg[0] & MSGCODE_FRAME_ACK) != 0;
486 break;
487 }
488 iNow = GetTimeMs();
489 }
490 }
491
492 return bGotAck && !bError;
493}
494
495bool CCECParser::ReadFromDevice(int iTimeout)
496{
497 uint8_t buff[1024];
498 int iBytesRead = m_serialport->Read(buff, sizeof(buff), iTimeout);
499 if (iBytesRead < 0)
500 {
501 CStdString strError;
502 strError.Format("error reading from serial port: %s", m_serialport->GetError().c_str());
503 AddLog(CEC_LOG_ERROR, strError);
504 return false;
505 }
506 else if (iBytesRead > 0)
507 AddData(buff, iBytesRead);
508
509 return true;
510}
511
512void CCECParser::ProcessMessages(void)
513{
514 cec_frame msg;
515 while (GetMessage(msg))
516 ParseMessage(msg);
517}
518
519bool CCECParser::GetMessage(cec_frame &msg, bool bFromBuffer /* = true */)
520{
521 if (bFromBuffer && m_frameBuffer.Pop(msg))
522 return true;
523
524 if (m_iInbufUsed < 1)
525 return false;
526
527 //search for first start of message
528 int startpos = -1;
529 for (int i = 0; i < m_iInbufUsed; i++)
530 {
531 if (m_inbuf[i] == MSGSTART)
532 {
533 startpos = i;
534 break;
535 }
536 }
537
538 if (startpos == -1)
539 return false;
540
541 //move anything from the first start of message to the beginning of the buffer
542 if (startpos > 0)
543 {
544 memmove(m_inbuf, m_inbuf + startpos, m_iInbufUsed - startpos);
545 m_iInbufUsed -= startpos;
546 }
547
548 if (m_iInbufUsed < 2)
549 return false;
550
551 //look for end of message
552 startpos = -1;
553 int endpos = -1;
554 for (int i = 1; i < m_iInbufUsed; i++)
555 {
556 if (m_inbuf[i] == MSGEND)
557 {
558 endpos = i;
559 break;
560 }
561 else if (m_inbuf[i] == MSGSTART)
562 {
563 startpos = i;
564 break;
565 }
566 }
567
568 if (startpos > 0) //we found a msgstart before msgend, this is not right, remove
569 {
570 AddLog(CEC_LOG_ERROR, "received MSGSTART before MSGEND");
571 memmove(m_inbuf, m_inbuf + startpos, m_iInbufUsed - startpos);
572 m_iInbufUsed -= startpos;
573 return false;
574 }
575
576 if (endpos > 0) //found a MSGEND
577 {
578 msg.clear();
579 bool isesc = false;
580 for (int i = 1; i < endpos; i++)
581 {
582 if (isesc)
583 {
584 msg.push_back(m_inbuf[i] + (uint8_t)ESCOFFSET);
585 isesc = false;
586 }
587 else if (m_inbuf[i] == MSGESC)
588 {
589 isesc = true;
590 }
591 else
592 {
593 msg.push_back(m_inbuf[i]);
594 }
595 }
596
597 if (endpos + 1 < m_iInbufUsed)
598 memmove(m_inbuf, m_inbuf + endpos + 1, m_iInbufUsed - endpos - 1);
599
600 m_iInbufUsed -= endpos + 1;
601
602 return true;
603 }
604
605 return false;
606}
607
608void CCECParser::ParseMessage(cec_frame &msg)
609{
610 if (msg.empty())
611 return;
612
613 CStdString logStr;
614 uint8_t iCode = msg[0] & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK);
615 bool bEom = (msg[0] & MSGCODE_FRAME_EOM) != 0;
616 bool bAck = (msg[0] & MSGCODE_FRAME_ACK) != 0;
617
618 switch(iCode)
619 {
620 case MSGCODE_NOTHING:
621 AddLog(CEC_LOG_DEBUG, "MSGCODE_NOTHING");
622 break;
623 case MSGCODE_TIMEOUT_ERROR:
624 case MSGCODE_HIGH_ERROR:
625 case MSGCODE_LOW_ERROR:
626 {
627 if (iCode == MSGCODE_TIMEOUT_ERROR)
628 logStr = "MSGCODE_TIMEOUT";
629 else if (iCode == MSGCODE_HIGH_ERROR)
630 logStr = "MSGCODE_HIGH_ERROR";
631 else
632 logStr = "MSGCODE_LOW_ERROR";
633
634 int iLine = (msg.size() >= 3) ? (msg[1] << 8) | (msg[2]) : 0;
635 uint32_t iTime = (msg.size() >= 7) ? (msg[3] << 24) | (msg[4] << 16) | (msg[5] << 8) | (msg[6]) : 0;
636 logStr.AppendFormat(" line:%i", iLine);
637 logStr.AppendFormat(" time:%u", iTime);
638 AddLog(CEC_LOG_WARNING, logStr.c_str());
639 }
640 break;
641 case MSGCODE_FRAME_START:
642 {
643 logStr = "MSGCODE_FRAME_START";
644 m_currentframe.clear();
645 if (msg.size() >= 2)
646 {
647 int iInitiator = msg[1] >> 4;
648 int iDestination = msg[1] & 0xF;
649 logStr.AppendFormat(" initiator:%u destination:%u ack:%s %s", iInitiator, iDestination, bAck ? "high" : "low", bEom ? "eom" : "");
650
651 m_currentframe.push_back(msg[1]);
652 }
653 AddLog(CEC_LOG_DEBUG, logStr.c_str());
654 }
655 break;
656 case MSGCODE_FRAME_DATA:
657 {
658 logStr = "MSGCODE_FRAME_DATA";
659 if (msg.size() >= 2)
660 {
661 uint8_t iData = msg[1];
662 logStr.AppendFormat(" %02x", iData);
663 m_currentframe.push_back(iData);
664 }
665 AddLog(CEC_LOG_DEBUG, logStr.c_str());
666 }
667 if (bEom)
668 ParseCurrentFrame();
669 break;
670 default:
671 break;
672 }
673}
674
675void CCECParser::ParseCurrentFrame(void)
676{
677 uint8_t initiator = m_currentframe[0] >> 4;
678 uint8_t destination = m_currentframe[0] & 0xF;
679
680 CStdString dataStr;
681 dataStr.Format("received frame: initiator: %u destination: %u", initiator, destination);
682
683 if (m_currentframe.size() > 1)
684 {
685 dataStr += " data:";
686 for (unsigned int i = 1; i < m_currentframe.size(); i++)
687 dataStr.AppendFormat(" %02x", m_currentframe[i]);
688 }
689 AddLog(CEC_LOG_DEBUG, dataStr.c_str());
690
691 if (m_currentframe.size() <= 1)
692 return;
693
694 vector<uint8_t> tx;
695 ECecOpcode opCode = (ECecOpcode) m_currentframe[1];
696 if (destination == (uint16_t) m_iLogicalAddress)
697 {
698 switch(opCode)
699 {
700 case CEC_OPCODE_GIVE_PHYSICAL_ADDRESS:
701 ReportPhysicalAddress();
702 SetActiveView();
703 break;
704 case CEC_OPCODE_GIVE_OSD_NAME:
705 ReportOSDName((cec_logical_address)initiator);
706 break;
707 case CEC_OPCODE_GIVE_DEVICE_VENDOR_ID:
708 ReportVendorID((cec_logical_address)initiator);
709 break;
710 case CEC_OPCODE_MENU_REQUEST:
711 ReportMenuState((cec_logical_address)initiator);
712 break;
713 case CEC_OPCODE_GIVE_DEVICE_POWER_STATUS:
714 ReportPowerState((cec_logical_address)initiator);
715 break;
716 case CEC_OPCODE_GET_CEC_VERSION:
717 ReportCECVersion((cec_logical_address)initiator);
718 break;
719 case CEC_OPCODE_USER_CONTROL_PRESSED:
720 if (m_currentframe.size() > 2)
721 {
722 AddKey();
723
724 if (m_currentframe[2] <= CEC_USER_CONTROL_CODE_MAX)
725 {
726 m_iCurrentButton = (cec_user_control_code) m_currentframe[2];
727 m_buttontime = GetTimeMs();
728 }
729 }
730 break;
731 case CEC_OPCODE_USER_CONTROL_RELEASE:
732 AddKey();
733 break;
734 default:
735 break;
736 }
737 }
738 else if (destination == (uint8_t) CECDEVICE_BROADCAST)
739 {
740 if (opCode == CEC_OPCODE_REQUEST_ACTIVE_SOURCE)
741 {
742 BroadcastActiveSource();
743 }
744 else if (opCode == CEC_OPCODE_SET_STREAM_PATH)
745 {
746 if (m_currentframe.size() >= 4)
747 {
748 int streamaddr = ((int)m_currentframe[2] << 8) | ((int)m_currentframe[3]);
749 CStdString strLog;
750 strLog.Format("%i requests stream path from physical address %04x", initiator, streamaddr);
751 AddLog(CEC_LOG_DEBUG, strLog.c_str());
752 if (streamaddr == m_physicaladdress)
753 BroadcastActiveSource();
754 }
755 }
756 }
757 else
758 {
759 CStdString strLog;
760 strLog.Format("ignoring frame: destination: %u != %u", destination, (uint16_t)m_iLogicalAddress);
761 AddLog(CEC_LOG_DEBUG, strLog.c_str());
762 }
763}
764
765void CCECParser::AddData(uint8_t *data, int iLen)
766{
767 if (iLen + m_iInbufUsed > m_iInbufSize)
768 {
769 m_iInbufSize = iLen + m_iInbufUsed;
770 m_inbuf = (uint8_t*)realloc(m_inbuf, m_iInbufSize);
771 }
772
773 memcpy(m_inbuf + m_iInbufUsed, data, iLen);
774 m_iInbufUsed += iLen;
775}
776
777void CCECParser::PushEscaped(cec_frame &vec, uint8_t byte)
778{
779 if (byte >= MSGESC && byte != MSGSTART)
780 {
781 vec.push_back(MSGESC);
782 vec.push_back(byte - ESCOFFSET);
783 }
784 else
785 {
786 vec.push_back(byte);
787 }
788}
789
790void CCECParser::CheckKeypressTimeout(int64_t now)
791{
792 if (m_iCurrentButton != CEC_USER_CONTROL_CODE_UNKNOWN && now - m_buttontime > 500)
793 {
794 AddKey();
795 m_iCurrentButton = CEC_USER_CONTROL_CODE_UNKNOWN;
796 }
797}
798
6dfe9213 799bool CCECParser::SetLogicalAddress(cec_logical_address iLogicalAddress)
abbca718
LOK
800{
801 CStdString strLog;
6dfe9213 802 strLog.Format("setting logical address to %d", iLogicalAddress);
abbca718
LOK
803 AddLog(CEC_LOG_NOTICE, strLog.c_str());
804
6dfe9213
LOK
805 m_iLogicalAddress = iLogicalAddress;
806 return SetAckMask(0x1 << (uint8_t)m_iLogicalAddress);
807}
808
809bool CCECParser::SetAckMask(uint16_t iMask)
810{
811 CStdString strLog;
812 strLog.Format("setting ackmask to %2x", iMask);
813 AddLog(CEC_LOG_DEBUG, strLog.c_str());
814
abbca718 815 cec_frame output;
abbca718 816
6dfe9213 817 output.push_back(MSGSTART);
abbca718 818 PushEscaped(output, MSGCODE_SET_ACK_MASK);
6dfe9213 819 PushEscaped(output, iMask >> 8);
bcd03b37 820 PushEscaped(output, (uint8_t)iMask);
abbca718
LOK
821 output.push_back(MSGEND);
822
823 if (m_serialport->Write(output) == -1)
824 {
6dfe9213
LOK
825 strLog.Format("error writing to serial port: %s", m_serialport->GetError().c_str());
826 AddLog(CEC_LOG_ERROR, strLog);
abbca718
LOK
827 return false;
828 }
829
830 return true;
831}
832
833void CCECParser::AddLog(cec_log_level level, const string &strMessage)
834{
835 cec_log_message message;
836 message.level = level;
837 message.message.assign(strMessage.c_str());
838 m_logBuffer.Push(message);
839}
840
841void CCECParser::AddKey(void)
842{
843 if (m_iCurrentButton != CEC_USER_CONTROL_CODE_UNKNOWN)
844 {
845 cec_keypress key;
846 key.duration = (unsigned int) (GetTimeMs() - m_buttontime);
847 key.keycode = m_iCurrentButton;
848 m_keyBuffer.Push(key);
849 m_iCurrentButton = CEC_USER_CONTROL_CODE_UNKNOWN;
850 m_buttontime = 0;
851 }
852}
853
854int CCECParser::GetMinVersion(void)
855{
856 return CEC_MIN_VERSION;
857}
858
859int CCECParser::GetLibVersion(void)
860{
861 return CEC_LIB_VERSION;
862}
863
864int CCECParser::FindDevices(std::vector<cec_device> &deviceList, const char *strDevicePath /* = NULL */)
865{
866 CStdString strDebug;
867 if (strDevicePath)
868 strDebug.Format("trying to autodetect the com port for device path '%s'", strDevicePath);
869 else
870 strDebug.Format("trying to autodetect all CEC adapters");
871 AddLog(CEC_LOG_DEBUG, strDebug);
872
873 return CCECDetect::FindDevices(deviceList, strDevicePath);
874}
875
bcd03b37 876DECLSPEC void * CECCreate(const char *strDeviceName, CEC::cec_logical_address iLogicalAddress /*= CEC::CECDEVICE_PLAYBACKDEVICE1 */, int iPhysicalAddress /* = CEC_DEFAULT_PHYSICAL_ADDRESS */)
abbca718 877{
df7339c6 878 return static_cast< void* > (new CCECParser(strDeviceName, iLogicalAddress, iPhysicalAddress));
abbca718 879}