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 | } | |
825ddb96 LOK |
258 | |
259 | bool CCECParser::GetNextCommand(cec_command *command) | |
260 | { | |
261 | return m_commandBuffer.Pop(*command); | |
262 | } | |
abbca718 LOK |
263 | //@} |
264 | ||
825ddb96 | 265 | void CCECParser::TransmitAbort(cec_logical_address address, cec_opcode opcode, ECecAbortReason reason /* = CEC_ABORT_REASON_UNRECOGNIZED_OPCODE */) |
abbca718 LOK |
266 | { |
267 | AddLog(CEC_LOG_DEBUG, "transmitting abort message"); | |
268 | cec_frame frame; | |
269 | frame.push_back(GetSourceDestination(address)); | |
270 | frame.push_back(CEC_OPCODE_FEATURE_ABORT); | |
271 | frame.push_back(opcode); | |
272 | frame.push_back(reason); | |
273 | Transmit(frame); | |
274 | } | |
275 | ||
276 | void CCECParser::ReportCECVersion(cec_logical_address address /* = CECDEVICE_TV */) | |
277 | { | |
278 | cec_frame frame; | |
279 | AddLog(CEC_LOG_NOTICE, "reporting CEC version as 1.3a"); | |
280 | frame.push_back(GetSourceDestination(address)); | |
281 | frame.push_back(CEC_OPCODE_CEC_VERSION); | |
282 | frame.push_back(CEC_VERSION_1_3A); | |
283 | Transmit(frame); | |
284 | } | |
285 | ||
286 | void CCECParser::ReportPowerState(cec_logical_address address /*= CECDEVICE_TV */, bool bOn /* = true */) | |
287 | { | |
288 | cec_frame frame; | |
289 | if (bOn) | |
290 | AddLog(CEC_LOG_NOTICE, "reporting \"On\" power status"); | |
291 | else | |
292 | AddLog(CEC_LOG_NOTICE, "reporting \"Off\" power status"); | |
293 | ||
294 | frame.push_back(GetSourceDestination(address)); | |
295 | frame.push_back(CEC_OPCODE_REPORT_POWER_STATUS); | |
296 | frame.push_back(bOn ? CEC_POWER_STATUS_ON : CEC_POWER_STATUS_STANDBY); | |
297 | Transmit(frame); | |
298 | } | |
299 | ||
300 | void CCECParser::ReportMenuState(cec_logical_address address /* = CECDEVICE_TV */, bool bActive /* = true */) | |
301 | { | |
302 | cec_frame frame; | |
303 | if (bActive) | |
304 | AddLog(CEC_LOG_NOTICE, "reporting menu state as active"); | |
305 | else | |
306 | AddLog(CEC_LOG_NOTICE, "reporting menu state as inactive"); | |
307 | ||
308 | frame.push_back(GetSourceDestination(address)); | |
309 | frame.push_back(CEC_OPCODE_MENU_STATUS); | |
310 | frame.push_back(bActive ? CEC_MENU_STATE_ACTIVATED : CEC_MENU_STATE_DEACTIVATED); | |
311 | Transmit(frame); | |
312 | } | |
313 | ||
314 | void CCECParser::ReportVendorID(cec_logical_address address /* = CECDEVICE_TV */) | |
315 | { | |
316 | AddLog(CEC_LOG_NOTICE, "vendor ID requested, feature abort"); | |
317 | TransmitAbort(address, CEC_OPCODE_GIVE_DEVICE_VENDOR_ID); | |
318 | } | |
319 | ||
320 | void CCECParser::ReportOSDName(cec_logical_address address /* = CECDEVICE_TV */) | |
321 | { | |
322 | cec_frame frame; | |
323 | const char *osdname = m_strDeviceName.c_str(); | |
324 | CStdString strLog; | |
325 | strLog.Format("reporting OSD name as %s", osdname); | |
326 | AddLog(CEC_LOG_NOTICE, strLog.c_str()); | |
327 | frame.push_back(GetSourceDestination(address)); | |
328 | frame.push_back(CEC_OPCODE_SET_OSD_NAME); | |
329 | ||
330 | for (unsigned int i = 0; i < strlen(osdname); i++) | |
331 | frame.push_back(osdname[i]); | |
332 | ||
333 | Transmit(frame); | |
334 | } | |
335 | ||
336 | void CCECParser::ReportPhysicalAddress(void) | |
337 | { | |
338 | cec_frame frame; | |
339 | CStdString strLog; | |
340 | strLog.Format("reporting physical address as %04x", m_physicaladdress); | |
341 | AddLog(CEC_LOG_NOTICE, strLog.c_str()); | |
342 | frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST)); | |
343 | frame.push_back(CEC_OPCODE_REPORT_PHYSICAL_ADDRESS); | |
344 | frame.push_back((m_physicaladdress >> 8) & 0xFF); | |
345 | frame.push_back(m_physicaladdress & 0xFF); | |
346 | frame.push_back(CEC_DEVICE_TYPE_PLAYBACK_DEVICE); | |
347 | Transmit(frame); | |
348 | } | |
349 | ||
350 | void CCECParser::BroadcastActiveSource(void) | |
351 | { | |
352 | cec_frame frame; | |
353 | AddLog(CEC_LOG_NOTICE, "broadcasting active source"); | |
354 | frame.push_back(GetSourceDestination(CECDEVICE_BROADCAST)); | |
355 | frame.push_back(CEC_OPCODE_ACTIVE_SOURCE); | |
356 | frame.push_back((m_physicaladdress >> 8) & 0xFF); | |
357 | frame.push_back(m_physicaladdress & 0xFF); | |
358 | Transmit(frame); | |
359 | } | |
360 | ||
361 | bool CCECParser::TransmitFormatted(const cec_frame &data, bool bWaitForAck /* = true */, int64_t iTimeout /* = 2000 */) | |
362 | { | |
363 | CLockObject lock(&m_mutex, iTimeout); | |
364 | if (!lock.IsLocked()) | |
365 | { | |
366 | AddLog(CEC_LOG_ERROR, "could not get a write lock"); | |
367 | return false; | |
368 | } | |
369 | ||
370 | if (m_serialport->Write(data) != data.size()) | |
371 | { | |
372 | CStdString strError; | |
373 | strError.Format("error writing to serial port: %s", m_serialport->GetError().c_str()); | |
374 | AddLog(CEC_LOG_ERROR, strError); | |
375 | return false; | |
376 | } | |
377 | AddLog(CEC_LOG_DEBUG, "command sent"); | |
378 | ||
379 | CCondition::Sleep((int) data.size() * 24 /*data*/ + 5 /*start bit (4.5 ms)*/ + 50 /* to be on the safe side */); | |
380 | if (bWaitForAck && !WaitForAck()) | |
381 | { | |
382 | AddLog(CEC_LOG_DEBUG, "did not receive ACK"); | |
383 | return false; | |
384 | } | |
385 | ||
386 | return true; | |
387 | } | |
388 | ||
389 | bool CCECParser::Transmit(const cec_frame &data, bool bWaitForAck /* = true */, int64_t iTimeout /* = 5000 */) | |
390 | { | |
391 | CStdString txStr = "transmit "; | |
392 | for (unsigned int i = 0; i < data.size(); i++) | |
393 | txStr.AppendFormat(" %02x", data[i]); | |
394 | AddLog(CEC_LOG_DEBUG, txStr.c_str()); | |
395 | ||
396 | if (data.empty()) | |
397 | { | |
398 | AddLog(CEC_LOG_WARNING, "transmit buffer is empty"); | |
399 | return false; | |
400 | } | |
401 | ||
402 | cec_frame output; | |
403 | ||
404 | //set ack polarity to high when transmitting to the broadcast address | |
405 | //set ack polarity low when transmitting to any other address | |
406 | output.push_back(MSGSTART); | |
407 | PushEscaped(output, MSGCODE_TRANSMIT_ACK_POLARITY); | |
408 | ||
409 | if ((data[0] & 0xF) == 0xF) | |
410 | PushEscaped(output, CEC_TRUE); | |
411 | else | |
412 | PushEscaped(output, CEC_FALSE); | |
413 | ||
414 | output.push_back(MSGEND); | |
415 | ||
416 | for (unsigned int i = 0; i < data.size(); i++) | |
417 | { | |
418 | output.push_back(MSGSTART); | |
419 | ||
420 | if (i == data.size() - 1) | |
421 | PushEscaped(output, MSGCODE_TRANSMIT_EOM); | |
422 | else | |
423 | PushEscaped(output, MSGCODE_TRANSMIT); | |
424 | ||
425 | PushEscaped(output, data[i]); | |
426 | ||
427 | output.push_back(MSGEND); | |
428 | } | |
429 | ||
430 | return TransmitFormatted(output, bWaitForAck, iTimeout); | |
431 | } | |
432 | ||
433 | bool CCECParser::WaitForAck(int64_t iTimeout /* = 1000 */) | |
434 | { | |
435 | bool bGotAck(false); | |
436 | bool bError(false); | |
437 | ||
438 | int64_t iNow = GetTimeMs(); | |
439 | int64_t iTargetTime = iNow + iTimeout; | |
440 | ||
441 | while (!bGotAck && !bError && (iTimeout <= 0 || iNow < iTargetTime)) | |
442 | { | |
443 | if (!ReadFromDevice((int) iTimeout)) | |
444 | { | |
445 | AddLog(CEC_LOG_ERROR, "failed to read from device"); | |
446 | return false; | |
447 | } | |
448 | ||
449 | cec_frame msg; | |
450 | while (!bGotAck && !bError && GetMessage(msg, false)) | |
451 | { | |
452 | uint8_t iCode = msg[0] & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK); | |
453 | ||
454 | switch (iCode) | |
455 | { | |
456 | case MSGCODE_COMMAND_ACCEPTED: | |
457 | AddLog(CEC_LOG_DEBUG, "MSGCODE_COMMAND_ACCEPTED"); | |
458 | break; | |
459 | case MSGCODE_TRANSMIT_SUCCEEDED: | |
460 | AddLog(CEC_LOG_DEBUG, "MSGCODE_TRANSMIT_SUCCEEDED"); | |
461 | // TODO | |
462 | bGotAck = true; | |
463 | break; | |
464 | case MSGCODE_RECEIVE_FAILED: | |
465 | AddLog(CEC_LOG_WARNING, "MSGCODE_RECEIVE_FAILED"); | |
466 | bError = true; | |
467 | break; | |
468 | case MSGCODE_COMMAND_REJECTED: | |
469 | AddLog(CEC_LOG_WARNING, "MSGCODE_COMMAND_REJECTED"); | |
470 | bError = true; | |
471 | break; | |
472 | case MSGCODE_TRANSMIT_FAILED_LINE: | |
473 | AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_LINE"); | |
474 | bError = true; | |
475 | break; | |
476 | case MSGCODE_TRANSMIT_FAILED_ACK: | |
477 | AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_ACK"); | |
478 | bError = true; | |
479 | break; | |
480 | case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA: | |
481 | AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA"); | |
482 | bError = true; | |
483 | break; | |
484 | case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE: | |
485 | AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE"); | |
486 | bError = true; | |
487 | break; | |
488 | default: | |
489 | m_frameBuffer.Push(msg); | |
490 | bGotAck = (msg[0] & MSGCODE_FRAME_ACK) != 0; | |
491 | break; | |
492 | } | |
493 | iNow = GetTimeMs(); | |
494 | } | |
495 | } | |
496 | ||
497 | return bGotAck && !bError; | |
498 | } | |
499 | ||
500 | bool CCECParser::ReadFromDevice(int iTimeout) | |
501 | { | |
502 | uint8_t buff[1024]; | |
503 | int iBytesRead = m_serialport->Read(buff, sizeof(buff), iTimeout); | |
504 | if (iBytesRead < 0) | |
505 | { | |
506 | CStdString strError; | |
507 | strError.Format("error reading from serial port: %s", m_serialport->GetError().c_str()); | |
508 | AddLog(CEC_LOG_ERROR, strError); | |
509 | return false; | |
510 | } | |
511 | else if (iBytesRead > 0) | |
512 | AddData(buff, iBytesRead); | |
513 | ||
514 | return true; | |
515 | } | |
516 | ||
517 | void CCECParser::ProcessMessages(void) | |
518 | { | |
519 | cec_frame msg; | |
520 | while (GetMessage(msg)) | |
521 | ParseMessage(msg); | |
522 | } | |
523 | ||
524 | bool CCECParser::GetMessage(cec_frame &msg, bool bFromBuffer /* = true */) | |
525 | { | |
526 | if (bFromBuffer && m_frameBuffer.Pop(msg)) | |
527 | return true; | |
528 | ||
529 | if (m_iInbufUsed < 1) | |
530 | return false; | |
531 | ||
532 | //search for first start of message | |
533 | int startpos = -1; | |
534 | for (int i = 0; i < m_iInbufUsed; i++) | |
535 | { | |
536 | if (m_inbuf[i] == MSGSTART) | |
537 | { | |
538 | startpos = i; | |
539 | break; | |
540 | } | |
541 | } | |
542 | ||
543 | if (startpos == -1) | |
544 | return false; | |
545 | ||
546 | //move anything from the first start of message to the beginning of the buffer | |
547 | if (startpos > 0) | |
548 | { | |
549 | memmove(m_inbuf, m_inbuf + startpos, m_iInbufUsed - startpos); | |
550 | m_iInbufUsed -= startpos; | |
551 | } | |
552 | ||
553 | if (m_iInbufUsed < 2) | |
554 | return false; | |
555 | ||
556 | //look for end of message | |
557 | startpos = -1; | |
558 | int endpos = -1; | |
559 | for (int i = 1; i < m_iInbufUsed; i++) | |
560 | { | |
561 | if (m_inbuf[i] == MSGEND) | |
562 | { | |
563 | endpos = i; | |
564 | break; | |
565 | } | |
566 | else if (m_inbuf[i] == MSGSTART) | |
567 | { | |
568 | startpos = i; | |
569 | break; | |
570 | } | |
571 | } | |
572 | ||
573 | if (startpos > 0) //we found a msgstart before msgend, this is not right, remove | |
574 | { | |
575 | AddLog(CEC_LOG_ERROR, "received MSGSTART before MSGEND"); | |
576 | memmove(m_inbuf, m_inbuf + startpos, m_iInbufUsed - startpos); | |
577 | m_iInbufUsed -= startpos; | |
578 | return false; | |
579 | } | |
580 | ||
581 | if (endpos > 0) //found a MSGEND | |
582 | { | |
583 | msg.clear(); | |
584 | bool isesc = false; | |
585 | for (int i = 1; i < endpos; i++) | |
586 | { | |
587 | if (isesc) | |
588 | { | |
589 | msg.push_back(m_inbuf[i] + (uint8_t)ESCOFFSET); | |
590 | isesc = false; | |
591 | } | |
592 | else if (m_inbuf[i] == MSGESC) | |
593 | { | |
594 | isesc = true; | |
595 | } | |
596 | else | |
597 | { | |
598 | msg.push_back(m_inbuf[i]); | |
599 | } | |
600 | } | |
601 | ||
602 | if (endpos + 1 < m_iInbufUsed) | |
603 | memmove(m_inbuf, m_inbuf + endpos + 1, m_iInbufUsed - endpos - 1); | |
604 | ||
605 | m_iInbufUsed -= endpos + 1; | |
606 | ||
607 | return true; | |
608 | } | |
609 | ||
610 | return false; | |
611 | } | |
612 | ||
613 | void CCECParser::ParseMessage(cec_frame &msg) | |
614 | { | |
615 | if (msg.empty()) | |
616 | return; | |
617 | ||
618 | CStdString logStr; | |
619 | uint8_t iCode = msg[0] & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK); | |
620 | bool bEom = (msg[0] & MSGCODE_FRAME_EOM) != 0; | |
621 | bool bAck = (msg[0] & MSGCODE_FRAME_ACK) != 0; | |
622 | ||
623 | switch(iCode) | |
624 | { | |
625 | case MSGCODE_NOTHING: | |
626 | AddLog(CEC_LOG_DEBUG, "MSGCODE_NOTHING"); | |
627 | break; | |
628 | case MSGCODE_TIMEOUT_ERROR: | |
629 | case MSGCODE_HIGH_ERROR: | |
630 | case MSGCODE_LOW_ERROR: | |
631 | { | |
632 | if (iCode == MSGCODE_TIMEOUT_ERROR) | |
633 | logStr = "MSGCODE_TIMEOUT"; | |
634 | else if (iCode == MSGCODE_HIGH_ERROR) | |
635 | logStr = "MSGCODE_HIGH_ERROR"; | |
636 | else | |
637 | logStr = "MSGCODE_LOW_ERROR"; | |
638 | ||
639 | int iLine = (msg.size() >= 3) ? (msg[1] << 8) | (msg[2]) : 0; | |
640 | uint32_t iTime = (msg.size() >= 7) ? (msg[3] << 24) | (msg[4] << 16) | (msg[5] << 8) | (msg[6]) : 0; | |
641 | logStr.AppendFormat(" line:%i", iLine); | |
642 | logStr.AppendFormat(" time:%u", iTime); | |
643 | AddLog(CEC_LOG_WARNING, logStr.c_str()); | |
644 | } | |
645 | break; | |
646 | case MSGCODE_FRAME_START: | |
647 | { | |
648 | logStr = "MSGCODE_FRAME_START"; | |
649 | m_currentframe.clear(); | |
650 | if (msg.size() >= 2) | |
651 | { | |
652 | int iInitiator = msg[1] >> 4; | |
653 | int iDestination = msg[1] & 0xF; | |
654 | logStr.AppendFormat(" initiator:%u destination:%u ack:%s %s", iInitiator, iDestination, bAck ? "high" : "low", bEom ? "eom" : ""); | |
655 | ||
656 | m_currentframe.push_back(msg[1]); | |
657 | } | |
658 | AddLog(CEC_LOG_DEBUG, logStr.c_str()); | |
659 | } | |
660 | break; | |
661 | case MSGCODE_FRAME_DATA: | |
662 | { | |
663 | logStr = "MSGCODE_FRAME_DATA"; | |
664 | if (msg.size() >= 2) | |
665 | { | |
666 | uint8_t iData = msg[1]; | |
667 | logStr.AppendFormat(" %02x", iData); | |
668 | m_currentframe.push_back(iData); | |
669 | } | |
670 | AddLog(CEC_LOG_DEBUG, logStr.c_str()); | |
671 | } | |
672 | if (bEom) | |
673 | ParseCurrentFrame(); | |
674 | break; | |
675 | default: | |
676 | break; | |
677 | } | |
678 | } | |
679 | ||
680 | void CCECParser::ParseCurrentFrame(void) | |
681 | { | |
682 | uint8_t initiator = m_currentframe[0] >> 4; | |
683 | uint8_t destination = m_currentframe[0] & 0xF; | |
684 | ||
685 | CStdString dataStr; | |
686 | dataStr.Format("received frame: initiator: %u destination: %u", initiator, destination); | |
687 | ||
688 | if (m_currentframe.size() > 1) | |
689 | { | |
690 | dataStr += " data:"; | |
691 | for (unsigned int i = 1; i < m_currentframe.size(); i++) | |
692 | dataStr.AppendFormat(" %02x", m_currentframe[i]); | |
693 | } | |
694 | AddLog(CEC_LOG_DEBUG, dataStr.c_str()); | |
695 | ||
696 | if (m_currentframe.size() <= 1) | |
697 | return; | |
698 | ||
699 | vector<uint8_t> tx; | |
825ddb96 | 700 | cec_opcode opCode = (cec_opcode) m_currentframe[1]; |
abbca718 LOK |
701 | if (destination == (uint16_t) m_iLogicalAddress) |
702 | { | |
703 | switch(opCode) | |
704 | { | |
705 | case CEC_OPCODE_GIVE_PHYSICAL_ADDRESS: | |
706 | ReportPhysicalAddress(); | |
707 | SetActiveView(); | |
708 | break; | |
709 | case CEC_OPCODE_GIVE_OSD_NAME: | |
710 | ReportOSDName((cec_logical_address)initiator); | |
711 | break; | |
712 | case CEC_OPCODE_GIVE_DEVICE_VENDOR_ID: | |
713 | ReportVendorID((cec_logical_address)initiator); | |
714 | break; | |
715 | case CEC_OPCODE_MENU_REQUEST: | |
716 | ReportMenuState((cec_logical_address)initiator); | |
717 | break; | |
718 | case CEC_OPCODE_GIVE_DEVICE_POWER_STATUS: | |
719 | ReportPowerState((cec_logical_address)initiator); | |
720 | break; | |
721 | case CEC_OPCODE_GET_CEC_VERSION: | |
722 | ReportCECVersion((cec_logical_address)initiator); | |
723 | break; | |
724 | case CEC_OPCODE_USER_CONTROL_PRESSED: | |
725 | if (m_currentframe.size() > 2) | |
726 | { | |
727 | AddKey(); | |
728 | ||
729 | if (m_currentframe[2] <= CEC_USER_CONTROL_CODE_MAX) | |
730 | { | |
731 | m_iCurrentButton = (cec_user_control_code) m_currentframe[2]; | |
732 | m_buttontime = GetTimeMs(); | |
733 | } | |
734 | } | |
735 | break; | |
736 | case CEC_OPCODE_USER_CONTROL_RELEASE: | |
737 | AddKey(); | |
738 | break; | |
739 | default: | |
825ddb96 LOK |
740 | cec_frame params = m_currentframe; |
741 | params.erase(params.begin(), params.begin() + 2); | |
742 | AddCommand((cec_logical_address) initiator, (cec_logical_address) destination, opCode, ¶ms); | |
abbca718 LOK |
743 | break; |
744 | } | |
745 | } | |
746 | else if (destination == (uint8_t) CECDEVICE_BROADCAST) | |
747 | { | |
748 | if (opCode == CEC_OPCODE_REQUEST_ACTIVE_SOURCE) | |
749 | { | |
750 | BroadcastActiveSource(); | |
751 | } | |
752 | else if (opCode == CEC_OPCODE_SET_STREAM_PATH) | |
753 | { | |
754 | if (m_currentframe.size() >= 4) | |
755 | { | |
756 | int streamaddr = ((int)m_currentframe[2] << 8) | ((int)m_currentframe[3]); | |
757 | CStdString strLog; | |
758 | strLog.Format("%i requests stream path from physical address %04x", initiator, streamaddr); | |
759 | AddLog(CEC_LOG_DEBUG, strLog.c_str()); | |
760 | if (streamaddr == m_physicaladdress) | |
761 | BroadcastActiveSource(); | |
762 | } | |
763 | } | |
764 | } | |
765 | else | |
766 | { | |
767 | CStdString strLog; | |
768 | strLog.Format("ignoring frame: destination: %u != %u", destination, (uint16_t)m_iLogicalAddress); | |
769 | AddLog(CEC_LOG_DEBUG, strLog.c_str()); | |
770 | } | |
771 | } | |
772 | ||
773 | void CCECParser::AddData(uint8_t *data, int iLen) | |
774 | { | |
775 | if (iLen + m_iInbufUsed > m_iInbufSize) | |
776 | { | |
777 | m_iInbufSize = iLen + m_iInbufUsed; | |
778 | m_inbuf = (uint8_t*)realloc(m_inbuf, m_iInbufSize); | |
779 | } | |
780 | ||
781 | memcpy(m_inbuf + m_iInbufUsed, data, iLen); | |
782 | m_iInbufUsed += iLen; | |
783 | } | |
784 | ||
785 | void CCECParser::PushEscaped(cec_frame &vec, uint8_t byte) | |
786 | { | |
787 | if (byte >= MSGESC && byte != MSGSTART) | |
788 | { | |
789 | vec.push_back(MSGESC); | |
790 | vec.push_back(byte - ESCOFFSET); | |
791 | } | |
792 | else | |
793 | { | |
794 | vec.push_back(byte); | |
795 | } | |
796 | } | |
797 | ||
798 | void CCECParser::CheckKeypressTimeout(int64_t now) | |
799 | { | |
800 | if (m_iCurrentButton != CEC_USER_CONTROL_CODE_UNKNOWN && now - m_buttontime > 500) | |
801 | { | |
802 | AddKey(); | |
803 | m_iCurrentButton = CEC_USER_CONTROL_CODE_UNKNOWN; | |
804 | } | |
805 | } | |
806 | ||
6dfe9213 | 807 | bool CCECParser::SetLogicalAddress(cec_logical_address iLogicalAddress) |
abbca718 LOK |
808 | { |
809 | CStdString strLog; | |
6dfe9213 | 810 | strLog.Format("setting logical address to %d", iLogicalAddress); |
abbca718 LOK |
811 | AddLog(CEC_LOG_NOTICE, strLog.c_str()); |
812 | ||
6dfe9213 LOK |
813 | m_iLogicalAddress = iLogicalAddress; |
814 | return SetAckMask(0x1 << (uint8_t)m_iLogicalAddress); | |
815 | } | |
816 | ||
817 | bool CCECParser::SetAckMask(uint16_t iMask) | |
818 | { | |
819 | CStdString strLog; | |
820 | strLog.Format("setting ackmask to %2x", iMask); | |
821 | AddLog(CEC_LOG_DEBUG, strLog.c_str()); | |
822 | ||
abbca718 | 823 | cec_frame output; |
abbca718 | 824 | |
6dfe9213 | 825 | output.push_back(MSGSTART); |
abbca718 | 826 | PushEscaped(output, MSGCODE_SET_ACK_MASK); |
6dfe9213 | 827 | PushEscaped(output, iMask >> 8); |
bcd03b37 | 828 | PushEscaped(output, (uint8_t)iMask); |
abbca718 LOK |
829 | output.push_back(MSGEND); |
830 | ||
831 | if (m_serialport->Write(output) == -1) | |
832 | { | |
6dfe9213 LOK |
833 | strLog.Format("error writing to serial port: %s", m_serialport->GetError().c_str()); |
834 | AddLog(CEC_LOG_ERROR, strLog); | |
abbca718 LOK |
835 | return false; |
836 | } | |
837 | ||
838 | return true; | |
839 | } | |
840 | ||
841 | void CCECParser::AddLog(cec_log_level level, const string &strMessage) | |
842 | { | |
843 | cec_log_message message; | |
844 | message.level = level; | |
845 | message.message.assign(strMessage.c_str()); | |
846 | m_logBuffer.Push(message); | |
847 | } | |
848 | ||
849 | void CCECParser::AddKey(void) | |
850 | { | |
851 | if (m_iCurrentButton != CEC_USER_CONTROL_CODE_UNKNOWN) | |
852 | { | |
853 | cec_keypress key; | |
854 | key.duration = (unsigned int) (GetTimeMs() - m_buttontime); | |
855 | key.keycode = m_iCurrentButton; | |
856 | m_keyBuffer.Push(key); | |
857 | m_iCurrentButton = CEC_USER_CONTROL_CODE_UNKNOWN; | |
858 | m_buttontime = 0; | |
859 | } | |
860 | } | |
861 | ||
825ddb96 LOK |
862 | void CCECParser::AddCommand(cec_logical_address source, cec_logical_address destination, cec_opcode opcode, cec_frame *parameters) |
863 | { | |
864 | cec_command command; | |
865 | command.source = source; | |
866 | command.destination = destination; | |
867 | command.opcode = opcode; | |
868 | if (parameters) | |
869 | command.parameters = *parameters; | |
870 | m_commandBuffer.Push(command); | |
871 | } | |
872 | ||
abbca718 LOK |
873 | int CCECParser::GetMinVersion(void) |
874 | { | |
875 | return CEC_MIN_VERSION; | |
876 | } | |
877 | ||
878 | int CCECParser::GetLibVersion(void) | |
879 | { | |
880 | return CEC_LIB_VERSION; | |
881 | } | |
882 | ||
883 | int CCECParser::FindDevices(std::vector<cec_device> &deviceList, const char *strDevicePath /* = NULL */) | |
884 | { | |
885 | CStdString strDebug; | |
886 | if (strDevicePath) | |
887 | strDebug.Format("trying to autodetect the com port for device path '%s'", strDevicePath); | |
888 | else | |
889 | strDebug.Format("trying to autodetect all CEC adapters"); | |
890 | AddLog(CEC_LOG_DEBUG, strDebug); | |
891 | ||
892 | return CCECDetect::FindDevices(deviceList, strDevicePath); | |
893 | } | |
894 | ||
bcd03b37 | 895 | DECLSPEC void * CECCreate(const char *strDeviceName, CEC::cec_logical_address iLogicalAddress /*= CEC::CECDEVICE_PLAYBACKDEVICE1 */, int iPhysicalAddress /* = CEC_DEFAULT_PHYSICAL_ADDRESS */) |
abbca718 | 896 | { |
df7339c6 | 897 | return static_cast< void* > (new CCECParser(strDeviceName, iLogicalAddress, iPhysicalAddress)); |
abbca718 | 898 | } |