Commit | Line | Data |
---|---|---|
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 | ||
46 | using namespace CEC; | |
47 | using namespace std; | |
48 | ||
49 | #define CEC_MAX_RETRY 5 | |
50 | ||
51 | /*! | |
52 | * ICECDevice implementation | |
53 | */ | |
54 | //@{ | |
df7339c6 | 55 | CCECParser::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 | ||
68 | CCECParser::~CCECParser(void) | |
69 | { | |
70 | m_bRunning = false; | |
71 | pthread_join(m_thread, NULL); | |
72 | m_serialport->Close(); | |
73 | delete m_serialport; | |
74 | } | |
75 | ||
76 | bool 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 | ||
115 | void *CCECParser::ThreadHandler(CCECParser *parser) | |
116 | { | |
117 | if (parser) | |
118 | parser->Process(); | |
119 | return 0; | |
120 | } | |
121 | ||
122 | bool 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 | ||
151 | bool 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 | ||
171 | bool 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 | ||
189 | uint8_t CCECParser::GetSourceDestination(cec_logical_address destination /* = CECDEVICE_BROADCAST */) | |
190 | { | |
191 | return ((uint8_t)m_iLogicalAddress << 4) + (uint8_t)destination; | |
192 | } | |
193 | ||
194 | bool 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 | ||
205 | bool 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 | ||
216 | bool 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 | ||
227 | bool 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 | ||
238 | bool 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 | ||
249 | bool CCECParser::GetNextLogMessage(cec_log_message *message) | |
250 | { | |
251 | return m_logBuffer.Pop(*message); | |
252 | } | |
253 | ||
254 | bool CCECParser::GetNextKeypress(cec_keypress *key) | |
255 | { | |
256 | return m_keyBuffer.Pop(*key); | |
257 | } | |
258 | //@} | |
259 | ||
260 | void 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 | ||
271 | void 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 | ||
281 | void 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 | ||
295 | void 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 | ||
309 | void 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 | ||
315 | void 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 | ||
331 | void 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 | ||
345 | void 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 | ||
356 | bool 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 | ||
384 | bool 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 | ||
428 | bool 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 | ||
495 | bool 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 | ||
512 | void CCECParser::ProcessMessages(void) | |
513 | { | |
514 | cec_frame msg; | |
515 | while (GetMessage(msg)) | |
516 | ParseMessage(msg); | |
517 | } | |
518 | ||
519 | bool 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 | ||
608 | void 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 | ||
675 | void 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 | ||
765 | void 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 | ||
777 | void 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 | ||
790 | void 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 | 799 | bool 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 | ||
809 | bool 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 LOK |
819 | PushEscaped(output, iMask >> 8); |
820 | PushEscaped(output, 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 | ||
833 | void 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 | ||
841 | void 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 | ||
854 | int CCECParser::GetMinVersion(void) | |
855 | { | |
856 | return CEC_MIN_VERSION; | |
857 | } | |
858 | ||
859 | int CCECParser::GetLibVersion(void) | |
860 | { | |
861 | return CEC_LIB_VERSION; | |
862 | } | |
863 | ||
864 | int 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 | ||
df7339c6 | 876 | DECLSPEC 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 | } |