Commit | Line | Data |
---|---|---|
cbbe90dd JB |
1 | /* |
2 | * This file is part of the libCEC(R) library. | |
3 | * | |
4 | * libCEC(R) is Copyright (C) 2011-2013 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 "env.h" | |
34 | #include "CECProcessor.h" | |
35 | ||
36 | #include "adapter/AdapterFactory.h" | |
37 | #include "devices/CECBusDevice.h" | |
38 | #include "devices/CECAudioSystem.h" | |
39 | #include "devices/CECPlaybackDevice.h" | |
40 | #include "devices/CECRecordingDevice.h" | |
41 | #include "devices/CECTuner.h" | |
42 | #include "devices/CECTV.h" | |
43 | #include "implementations/CECCommandHandler.h" | |
44 | #include "LibCEC.h" | |
45 | #include "CECClient.h" | |
46 | #include "CECTypeUtils.h" | |
47 | #include "platform/util/timeutils.h" | |
48 | #include "platform/util/util.h" | |
49 | ||
50 | using namespace CEC; | |
51 | using namespace std; | |
52 | using namespace PLATFORM; | |
53 | ||
54 | #define CEC_PROCESSOR_SIGNAL_WAIT_TIME 1000 | |
55 | #define ACTIVE_SOURCE_CHECK_INTERVAL 500 | |
56 | #define TV_PRESENT_CHECK_INTERVAL 30000 | |
57 | ||
58 | #define ToString(x) CCECTypeUtils::ToString(x) | |
59 | ||
60 | CCECStandbyProtection::CCECStandbyProtection(CCECProcessor* processor) : | |
61 | m_processor(processor) {} | |
62 | CCECStandbyProtection::~CCECStandbyProtection(void) {} | |
63 | ||
64 | void* CCECStandbyProtection::Process(void) | |
65 | { | |
66 | int64_t last = GetTimeMs(); | |
67 | int64_t next; | |
68 | while (!IsStopped()) | |
69 | { | |
70 | PLATFORM::CEvent::Sleep(1000); | |
71 | ||
72 | next = GetTimeMs(); | |
73 | ||
74 | // reset the connection if the clock changed | |
75 | if (next < last || next - last > 10000) | |
76 | { | |
77 | libcec_parameter param; | |
78 | param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN; | |
79 | m_processor->GetLib()->Alert(CEC_ALERT_CONNECTION_LOST, param); | |
80 | break; | |
81 | } | |
82 | ||
83 | last = next; | |
84 | } | |
85 | return NULL; | |
86 | } | |
87 | ||
88 | CCECProcessor::CCECProcessor(CLibCEC *libcec) : | |
89 | m_bInitialised(false), | |
90 | m_communication(NULL), | |
91 | m_libcec(libcec), | |
92 | m_iStandardLineTimeout(3), | |
93 | m_iRetryLineTimeout(3), | |
94 | m_iLastTransmission(0), | |
95 | m_bMonitor(true), | |
96 | m_addrAllocator(NULL), | |
97 | m_bStallCommunication(false), | |
98 | m_connCheck(NULL) | |
99 | { | |
100 | m_busDevices = new CCECDeviceMap(this); | |
101 | } | |
102 | ||
103 | CCECProcessor::~CCECProcessor(void) | |
104 | { | |
105 | m_bStallCommunication = false; | |
106 | DELETE_AND_NULL(m_addrAllocator); | |
107 | Close(); | |
108 | DELETE_AND_NULL(m_busDevices); | |
109 | } | |
110 | ||
111 | bool CCECProcessor::Start(const char *strPort, uint16_t iBaudRate /* = CEC_SERIAL_DEFAULT_BAUDRATE */, uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */) | |
112 | { | |
113 | CLockObject lock(m_mutex); | |
114 | // open a connection | |
115 | if (!OpenConnection(strPort, iBaudRate, iTimeoutMs)) | |
116 | return false; | |
117 | ||
118 | // create the processor thread | |
119 | if (!IsRunning()) | |
120 | { | |
121 | if (!CreateThread()) | |
122 | { | |
123 | m_libcec->AddLog(CEC_LOG_ERROR, "could not create a processor thread"); | |
124 | return false; | |
125 | } | |
126 | } | |
127 | ||
128 | return true; | |
129 | } | |
130 | ||
131 | void CCECProcessor::Close(void) | |
132 | { | |
133 | // mark as uninitialised | |
134 | SetCECInitialised(false); | |
135 | ||
136 | // stop the processor | |
137 | DELETE_AND_NULL(m_connCheck); | |
138 | StopThread(-1); | |
139 | m_inBuffer.Broadcast(); | |
140 | StopThread(); | |
141 | ||
142 | // close the connection | |
143 | CLockObject lock(m_mutex); | |
144 | DELETE_AND_NULL(m_communication); | |
145 | } | |
146 | ||
147 | void CCECProcessor::ResetMembers(void) | |
148 | { | |
149 | // close the connection | |
150 | DELETE_AND_NULL(m_communication); | |
151 | ||
152 | // reset the other members to the initial state | |
153 | m_iStandardLineTimeout = 3; | |
154 | m_iRetryLineTimeout = 3; | |
155 | m_iLastTransmission = 0; | |
156 | m_busDevices->ResetDeviceStatus(); | |
157 | } | |
158 | ||
159 | bool CCECProcessor::OpenConnection(const char *strPort, uint16_t iBaudRate, uint32_t iTimeoutMs, bool bStartListening /* = true */) | |
160 | { | |
161 | bool bReturn(false); | |
162 | CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT); | |
163 | ||
164 | // ensure that a previous connection is closed | |
165 | Close(); | |
166 | ||
167 | // reset all member to the initial state | |
168 | ResetMembers(); | |
169 | ||
170 | // check whether the Close() method deleted any previous connection | |
171 | if (m_communication) | |
172 | { | |
173 | m_libcec->AddLog(CEC_LOG_ERROR, "previous connection could not be closed"); | |
174 | return bReturn; | |
175 | } | |
176 | ||
177 | // create a new connection | |
178 | m_communication = CAdapterFactory(this->m_libcec).GetInstance(strPort, iBaudRate); | |
179 | ||
180 | // open a new connection | |
181 | unsigned iConnectTry(0); | |
182 | while (timeout.TimeLeft() > 0 && (bReturn = m_communication->Open((timeout.TimeLeft() / CEC_CONNECT_TRIES), false, bStartListening)) == false) | |
183 | { | |
184 | m_libcec->AddLog(CEC_LOG_ERROR, "could not open a connection (try %d)", ++iConnectTry); | |
185 | m_communication->Close(); | |
186 | CEvent::Sleep(CEC_DEFAULT_CONNECT_RETRY_WAIT); | |
187 | } | |
188 | ||
189 | m_libcec->AddLog(CEC_LOG_NOTICE, "connection opened"); | |
190 | ||
191 | // mark as initialised | |
192 | SetCECInitialised(true); | |
193 | ||
194 | return bReturn; | |
195 | } | |
196 | ||
197 | bool CCECProcessor::CECInitialised(void) | |
198 | { | |
199 | CLockObject lock(m_threadMutex); | |
200 | return m_bInitialised; | |
201 | } | |
202 | ||
203 | void CCECProcessor::SetCECInitialised(bool bSetTo /* = true */) | |
204 | { | |
205 | { | |
206 | CLockObject lock(m_mutex); | |
207 | m_bInitialised = bSetTo; | |
208 | } | |
209 | if (!bSetTo) | |
210 | UnregisterClients(); | |
211 | } | |
212 | ||
213 | bool CCECProcessor::TryLogicalAddress(cec_logical_address address, cec_version libCECSpecVersion /* = CEC_VERSION_1_4 */) | |
214 | { | |
215 | // find the device | |
216 | CCECBusDevice *device = m_busDevices->At(address); | |
217 | if (device) | |
218 | { | |
219 | // check if it's already marked as present or used | |
220 | if (device->IsPresent() || device->IsHandledByLibCEC()) | |
221 | return false; | |
222 | ||
223 | // poll the LA if not | |
224 | return device->TryLogicalAddress(libCECSpecVersion); | |
225 | } | |
226 | ||
227 | return false; | |
228 | } | |
229 | ||
230 | void CCECProcessor::ReplaceHandlers(void) | |
231 | { | |
232 | if (!CECInitialised()) | |
233 | return; | |
234 | ||
235 | // check each device | |
236 | for (CECDEVICEMAP::iterator it = m_busDevices->Begin(); it != m_busDevices->End(); it++) | |
237 | it->second->ReplaceHandler(true); | |
238 | } | |
239 | ||
240 | bool CCECProcessor::OnCommandReceived(const cec_command &command) | |
241 | { | |
242 | return m_inBuffer.Push(command); | |
243 | } | |
244 | ||
245 | void *CCECProcessor::Process(void) | |
246 | { | |
247 | m_libcec->AddLog(CEC_LOG_DEBUG, "processor thread started"); | |
248 | ||
249 | if (!m_connCheck) | |
250 | m_connCheck = new CCECStandbyProtection(this); | |
251 | m_connCheck->CreateThread(); | |
252 | ||
253 | cec_command command; command.Clear(); | |
254 | CTimeout activeSourceCheck(ACTIVE_SOURCE_CHECK_INTERVAL); | |
255 | CTimeout tvPresentCheck(TV_PRESENT_CHECK_INTERVAL); | |
256 | ||
257 | // as long as we're not being stopped and the connection is open | |
258 | while (!IsStopped() && m_communication->IsOpen()) | |
259 | { | |
260 | // wait for a new incoming command, and process it | |
261 | if (m_inBuffer.Pop(command, CEC_PROCESSOR_SIGNAL_WAIT_TIME)) | |
262 | ProcessCommand(command); | |
263 | ||
264 | if (CECInitialised() && !IsStopped()) | |
265 | { | |
266 | // check clients for keypress timeouts | |
267 | m_libcec->CheckKeypressTimeout(); | |
268 | ||
269 | // check if we need to replace handlers | |
270 | ReplaceHandlers(); | |
271 | ||
272 | // check whether we need to activate a source, if it failed before | |
273 | if (activeSourceCheck.TimeLeft() == 0) | |
274 | { | |
275 | if (CECInitialised()) | |
276 | TransmitPendingActiveSourceCommands(); | |
277 | activeSourceCheck.Init(ACTIVE_SOURCE_CHECK_INTERVAL); | |
278 | } | |
279 | ||
280 | // check whether the TV is present and responding | |
281 | if (tvPresentCheck.TimeLeft() == 0) | |
282 | { | |
283 | CCECClient *primary = GetPrimaryClient(); | |
284 | // only check whether the tv responds to polls when a client is connected and not in monitoring mode | |
285 | if (primary && primary->GetConfiguration()->bMonitorOnly != 1) | |
286 | { | |
287 | if (!m_busDevices->At(CECDEVICE_TV)->IsPresent()) | |
288 | { | |
289 | libcec_parameter param; | |
290 | param.paramType = CEC_PARAMETER_TYPE_STRING; | |
291 | param.paramData = (void*)"TV does not respond to CEC polls"; | |
292 | primary->Alert(CEC_ALERT_TV_POLL_FAILED, param); | |
293 | } | |
294 | } | |
295 | tvPresentCheck.Init(TV_PRESENT_CHECK_INTERVAL); | |
296 | } | |
297 | } | |
298 | } | |
299 | ||
300 | return NULL; | |
301 | } | |
302 | ||
303 | bool CCECProcessor::ActivateSource(uint16_t iStreamPath) | |
304 | { | |
305 | bool bReturn(false); | |
306 | ||
307 | // find the device with the given PA | |
308 | CCECBusDevice *device = GetDeviceByPhysicalAddress(iStreamPath); | |
309 | // and make it the active source when found | |
310 | if (device) | |
311 | bReturn = device->ActivateSource(); | |
312 | else | |
313 | m_libcec->AddLog(CEC_LOG_DEBUG, "device with PA '%04x' not found", iStreamPath); | |
314 | ||
315 | return bReturn; | |
316 | } | |
317 | ||
318 | void CCECProcessor::SetActiveSource(bool bSetTo, bool bClientUnregistered) | |
319 | { | |
320 | if (m_communication) | |
321 | m_communication->SetActiveSource(bSetTo, bClientUnregistered); | |
322 | } | |
323 | ||
324 | void CCECProcessor::SetStandardLineTimeout(uint8_t iTimeout) | |
325 | { | |
326 | CLockObject lock(m_mutex); | |
327 | m_iStandardLineTimeout = iTimeout; | |
328 | } | |
329 | ||
330 | uint8_t CCECProcessor::GetStandardLineTimeout(void) | |
331 | { | |
332 | CLockObject lock(m_mutex); | |
333 | return m_iStandardLineTimeout; | |
334 | } | |
335 | ||
336 | void CCECProcessor::SetRetryLineTimeout(uint8_t iTimeout) | |
337 | { | |
338 | CLockObject lock(m_mutex); | |
339 | m_iRetryLineTimeout = iTimeout; | |
340 | } | |
341 | ||
342 | uint8_t CCECProcessor::GetRetryLineTimeout(void) | |
343 | { | |
344 | CLockObject lock(m_mutex); | |
345 | return m_iRetryLineTimeout; | |
346 | } | |
347 | ||
348 | bool CCECProcessor::PhysicalAddressInUse(uint16_t iPhysicalAddress) | |
349 | { | |
350 | CCECBusDevice *device = GetDeviceByPhysicalAddress(iPhysicalAddress); | |
351 | return device != NULL; | |
352 | } | |
353 | ||
354 | void CCECProcessor::LogOutput(const cec_command &data) | |
355 | { | |
356 | CStdString strTx; | |
357 | ||
358 | // initiator and destination | |
359 | strTx.Format("<< %02x", ((uint8_t)data.initiator << 4) + (uint8_t)data.destination); | |
360 | ||
361 | // append the opcode | |
362 | if (data.opcode_set) | |
363 | strTx.AppendFormat(":%02x", (uint8_t)data.opcode); | |
364 | ||
365 | // append the parameters | |
366 | for (uint8_t iPtr = 0; iPtr < data.parameters.size; iPtr++) | |
367 | strTx.AppendFormat(":%02x", data.parameters[iPtr]); | |
368 | ||
369 | // and log it | |
370 | m_libcec->AddLog(CEC_LOG_TRAFFIC, strTx.c_str()); | |
371 | } | |
372 | ||
373 | bool CCECProcessor::PollDevice(cec_logical_address iAddress) | |
374 | { | |
375 | // try to find the primary device | |
376 | CCECBusDevice *primary = GetPrimaryDevice(); | |
377 | // poll the destination, with the primary as source | |
378 | if (primary) | |
379 | return primary->TransmitPoll(iAddress, true); | |
380 | ||
381 | CCECBusDevice *device = m_busDevices->At(CECDEVICE_UNREGISTERED); | |
382 | if (device) | |
383 | return device->TransmitPoll(iAddress, true); | |
384 | ||
385 | return false; | |
386 | } | |
387 | ||
388 | CCECBusDevice *CCECProcessor::GetDeviceByPhysicalAddress(uint16_t iPhysicalAddress, bool bSuppressUpdate /* = true */) | |
389 | { | |
390 | return m_busDevices ? | |
391 | m_busDevices->GetDeviceByPhysicalAddress(iPhysicalAddress, bSuppressUpdate) : | |
392 | NULL; | |
393 | } | |
394 | ||
395 | CCECBusDevice *CCECProcessor::GetDevice(cec_logical_address address) const | |
396 | { | |
397 | return m_busDevices ? | |
398 | m_busDevices->At(address) : | |
399 | NULL; | |
400 | } | |
401 | ||
402 | cec_logical_address CCECProcessor::GetActiveSource(bool bRequestActiveSource /* = true */) | |
403 | { | |
404 | // get the device that is marked as active source from the device map | |
405 | CCECBusDevice *activeSource = m_busDevices->GetActiveSource(); | |
406 | if (activeSource) | |
407 | return activeSource->GetLogicalAddress(); | |
408 | ||
409 | if (bRequestActiveSource) | |
410 | { | |
411 | // request the active source from the bus | |
412 | CCECBusDevice *primary = GetPrimaryDevice(); | |
413 | if (primary) | |
414 | { | |
415 | primary->RequestActiveSource(); | |
416 | return GetActiveSource(false); | |
417 | } | |
418 | } | |
419 | ||
420 | // unknown or none | |
421 | return CECDEVICE_UNKNOWN; | |
422 | } | |
423 | ||
424 | bool CCECProcessor::IsActiveSource(cec_logical_address iAddress) | |
425 | { | |
426 | CCECBusDevice *device = m_busDevices->At(iAddress); | |
427 | return device && device->IsActiveSource(); | |
428 | } | |
429 | ||
430 | bool CCECProcessor::Transmit(const cec_command &data, bool bIsReply) | |
431 | { | |
432 | cec_command transmitData(data); | |
433 | uint8_t iMaxTries(0); | |
434 | bool bRetry(true); | |
435 | uint8_t iTries(0); | |
436 | ||
437 | // get the current timeout setting | |
438 | uint8_t iLineTimeout(GetStandardLineTimeout()); | |
439 | ||
440 | // reset the state of this message to 'unknown' | |
441 | cec_adapter_message_state adapterState = ADAPTER_MESSAGE_STATE_UNKNOWN; | |
442 | ||
443 | CLockObject lock(m_mutex); | |
444 | if (!m_communication) | |
445 | return false; | |
446 | ||
447 | if (!m_communication->SupportsSourceLogicalAddress(transmitData.initiator)) | |
448 | { | |
449 | if (transmitData.initiator == CECDEVICE_UNREGISTERED && m_communication->SupportsSourceLogicalAddress(CECDEVICE_FREEUSE)) | |
450 | { | |
451 | m_libcec->AddLog(CEC_LOG_DEBUG, "initiator '%s' is not supported by the CEC adapter. using '%s' instead", ToString(transmitData.initiator), ToString(CECDEVICE_FREEUSE)); | |
452 | transmitData.initiator = CECDEVICE_FREEUSE; | |
453 | } | |
454 | else | |
455 | { | |
456 | m_libcec->AddLog(CEC_LOG_DEBUG, "initiator '%s' is not supported by the CEC adapter", ToString(transmitData.initiator)); | |
457 | return false; | |
458 | } | |
459 | } | |
460 | ||
461 | LogOutput(transmitData); | |
462 | ||
463 | // find the initiator device | |
464 | CCECBusDevice *initiator = m_busDevices->At(transmitData.initiator); | |
465 | if (!initiator) | |
466 | { | |
467 | m_libcec->AddLog(CEC_LOG_WARNING, "invalid initiator"); | |
468 | return false; | |
469 | } | |
470 | ||
471 | // find the destination device, if it's not the broadcast address | |
472 | if (transmitData.destination != CECDEVICE_BROADCAST) | |
473 | { | |
474 | // check if the device is marked as handled by libCEC | |
475 | CCECBusDevice *destination = m_busDevices->At(transmitData.destination); | |
476 | if (destination && destination->IsHandledByLibCEC()) | |
477 | { | |
478 | // and reject the command if it's trying to send data to a device that is handled by libCEC | |
479 | m_libcec->AddLog(CEC_LOG_WARNING, "not sending data to myself!"); | |
480 | return false; | |
481 | } | |
482 | } | |
483 | ||
484 | // wait until we finished allocating a new LA if it got lost | |
485 | lock.Unlock(); | |
486 | while (m_bStallCommunication) Sleep(5); | |
487 | lock.Lock(); | |
488 | ||
489 | m_iLastTransmission = GetTimeMs(); | |
490 | // set the number of tries | |
491 | iMaxTries = initiator->GetHandler()->GetTransmitRetries() + 1; | |
492 | initiator->MarkHandlerReady(); | |
493 | ||
494 | // and try to send the command | |
495 | while (bRetry && ++iTries < iMaxTries) | |
496 | { | |
497 | if (initiator->IsUnsupportedFeature(transmitData.opcode)) | |
498 | return false; | |
499 | ||
500 | adapterState = !IsStopped() && m_communication && m_communication->IsOpen() ? | |
501 | m_communication->Write(transmitData, bRetry, iLineTimeout, bIsReply) : | |
502 | ADAPTER_MESSAGE_STATE_ERROR; | |
503 | iLineTimeout = m_iRetryLineTimeout; | |
504 | } | |
505 | ||
506 | return bIsReply ? | |
507 | adapterState == ADAPTER_MESSAGE_STATE_SENT_ACKED || adapterState == ADAPTER_MESSAGE_STATE_SENT || adapterState == ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT : | |
508 | adapterState == ADAPTER_MESSAGE_STATE_SENT_ACKED; | |
509 | } | |
510 | ||
511 | void CCECProcessor::TransmitAbort(cec_logical_address source, cec_logical_address destination, cec_opcode opcode, cec_abort_reason reason /* = CEC_ABORT_REASON_UNRECOGNIZED_OPCODE */) | |
512 | { | |
513 | m_libcec->AddLog(CEC_LOG_DEBUG, "<< transmitting abort message"); | |
514 | ||
515 | cec_command command; | |
516 | cec_command::Format(command, source, destination, CEC_OPCODE_FEATURE_ABORT); | |
517 | command.parameters.PushBack((uint8_t)opcode); | |
518 | command.parameters.PushBack((uint8_t)reason); | |
519 | ||
520 | Transmit(command, true); | |
521 | } | |
522 | ||
523 | void CCECProcessor::ProcessCommand(const cec_command &command) | |
524 | { | |
525 | // log the command | |
526 | m_libcec->AddLog(CEC_LOG_TRAFFIC, ToString(command).c_str()); | |
527 | ||
528 | // find the initiator | |
529 | CCECBusDevice *device = m_busDevices->At(command.initiator); | |
530 | ||
531 | if (device) | |
532 | device->HandleCommand(command); | |
533 | } | |
534 | ||
535 | bool CCECProcessor::IsPresentDevice(cec_logical_address address) | |
536 | { | |
537 | CCECBusDevice *device = m_busDevices->At(address); | |
538 | return device && device->GetStatus() == CEC_DEVICE_STATUS_PRESENT; | |
539 | } | |
540 | ||
541 | bool CCECProcessor::IsPresentDeviceType(cec_device_type type) | |
542 | { | |
543 | CECDEVICEVEC devices; | |
544 | m_busDevices->GetByType(type, devices); | |
545 | CCECDeviceMap::FilterActive(devices); | |
546 | return !devices.empty(); | |
547 | } | |
548 | ||
549 | uint16_t CCECProcessor::GetDetectedPhysicalAddress(void) const | |
550 | { | |
551 | return m_communication ? m_communication->GetPhysicalAddress() : CEC_INVALID_PHYSICAL_ADDRESS; | |
552 | } | |
553 | ||
554 | bool CCECProcessor::ClearLogicalAddresses(void) | |
555 | { | |
556 | cec_logical_addresses addresses; addresses.Clear(); | |
557 | return SetLogicalAddresses(addresses); | |
558 | } | |
559 | ||
560 | bool CCECProcessor::SetLogicalAddresses(const cec_logical_addresses &addresses) | |
561 | { | |
562 | return m_communication ? m_communication->SetLogicalAddresses(addresses) : false; | |
563 | } | |
564 | ||
565 | bool CCECProcessor::StandbyDevices(const cec_logical_address initiator, const CECDEVICEVEC &devices) | |
566 | { | |
567 | bool bReturn(true); | |
568 | for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) | |
569 | bReturn &= (*it)->Standby(initiator); | |
570 | return bReturn; | |
571 | } | |
572 | ||
573 | bool CCECProcessor::StandbyDevice(const cec_logical_address initiator, cec_logical_address address) | |
574 | { | |
575 | CCECBusDevice *device = m_busDevices->At(address); | |
576 | return device ? device->Standby(initiator) : false; | |
577 | } | |
578 | ||
579 | bool CCECProcessor::PowerOnDevices(const cec_logical_address initiator, const CECDEVICEVEC &devices) | |
580 | { | |
581 | bool bReturn(true); | |
582 | for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) | |
583 | bReturn &= (*it)->PowerOn(initiator); | |
584 | return bReturn; | |
585 | } | |
586 | ||
587 | bool CCECProcessor::PowerOnDevice(const cec_logical_address initiator, cec_logical_address address) | |
588 | { | |
589 | CCECBusDevice *device = m_busDevices->At(address); | |
590 | return device ? device->PowerOn(initiator) : false; | |
591 | } | |
592 | ||
593 | bool CCECProcessor::StartBootloader(const char *strPort /* = NULL */) | |
594 | { | |
595 | bool bReturn(false); | |
596 | // open a connection if no connection has been opened | |
597 | if (!m_communication && strPort) | |
598 | { | |
599 | CAdapterFactory factory(this->m_libcec); | |
600 | IAdapterCommunication *comm = factory.GetInstance(strPort); | |
601 | CTimeout timeout(CEC_DEFAULT_CONNECT_TIMEOUT); | |
602 | int iConnectTry(0); | |
603 | while (timeout.TimeLeft() > 0 && (bReturn = comm->Open(timeout.TimeLeft() / CEC_CONNECT_TRIES, true)) == false) | |
604 | { | |
605 | m_libcec->AddLog(CEC_LOG_ERROR, "could not open a connection (try %d)", ++iConnectTry); | |
606 | comm->Close(); | |
607 | Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT); | |
608 | } | |
609 | if (comm->IsOpen()) | |
610 | { | |
611 | bReturn = comm->StartBootloader(); | |
612 | DELETE_AND_NULL(comm); | |
613 | } | |
614 | return bReturn; | |
615 | } | |
616 | else | |
617 | { | |
618 | m_communication->StartBootloader(); | |
619 | Close(); | |
620 | bReturn = true; | |
621 | } | |
622 | ||
623 | return bReturn; | |
624 | } | |
625 | ||
626 | bool CCECProcessor::PingAdapter(void) | |
627 | { | |
628 | return m_communication->PingAdapter(); | |
629 | } | |
630 | ||
631 | void CCECProcessor::HandlePoll(cec_logical_address initiator, cec_logical_address destination) | |
632 | { | |
633 | CCECBusDevice *device = m_busDevices->At(destination); | |
634 | if (device) | |
635 | device->HandlePollFrom(initiator); | |
636 | } | |
637 | ||
638 | bool CCECProcessor::HandleReceiveFailed(cec_logical_address initiator) | |
639 | { | |
640 | CCECBusDevice *device = m_busDevices->At(initiator); | |
641 | return !device || !device->HandleReceiveFailed(); | |
642 | } | |
643 | ||
644 | bool CCECProcessor::CanPersistConfiguration(void) | |
645 | { | |
646 | return m_communication ? m_communication->GetFirmwareVersion() >= 2 : false; | |
647 | } | |
648 | ||
649 | bool CCECProcessor::PersistConfiguration(const libcec_configuration &configuration) | |
650 | { | |
651 | libcec_configuration persistConfiguration = configuration; | |
652 | if (!CLibCEC::IsValidPhysicalAddress(configuration.iPhysicalAddress)) | |
653 | { | |
654 | CCECBusDevice *device = GetPrimaryDevice(); | |
655 | if (device) | |
656 | persistConfiguration.iPhysicalAddress = device->GetCurrentPhysicalAddress(); | |
657 | } | |
658 | ||
659 | return m_communication ? m_communication->PersistConfiguration(persistConfiguration) : false; | |
660 | } | |
661 | ||
662 | void CCECProcessor::RescanActiveDevices(void) | |
663 | { | |
664 | for (CECDEVICEMAP::iterator it = m_busDevices->Begin(); it != m_busDevices->End(); it++) | |
665 | it->second->GetStatus(true); | |
666 | } | |
667 | ||
668 | bool CCECProcessor::GetDeviceInformation(const char *strPort, libcec_configuration *config, uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */) | |
669 | { | |
670 | if (!OpenConnection(strPort, CEC_SERIAL_DEFAULT_BAUDRATE, iTimeoutMs, false)) | |
671 | return false; | |
672 | ||
673 | config->iFirmwareVersion = m_communication->GetFirmwareVersion(); | |
674 | config->iPhysicalAddress = m_communication->GetPhysicalAddress(); | |
675 | config->iFirmwareBuildDate = m_communication->GetFirmwareBuildDate(); | |
676 | config->adapterType = m_communication->GetAdapterType(); | |
677 | ||
678 | Close(); | |
679 | ||
680 | return true; | |
681 | } | |
682 | ||
683 | bool CCECProcessor::TransmitPendingActiveSourceCommands(void) | |
684 | { | |
685 | bool bReturn(true); | |
686 | for (CECDEVICEMAP::iterator it = m_busDevices->Begin(); it != m_busDevices->End(); it++) | |
687 | bReturn &= it->second->TransmitPendingActiveSourceCommands(); | |
688 | return bReturn; | |
689 | } | |
690 | ||
691 | CCECTV *CCECProcessor::GetTV(void) const | |
692 | { | |
693 | return CCECBusDevice::AsTV(m_busDevices->At(CECDEVICE_TV)); | |
694 | } | |
695 | ||
696 | CCECAudioSystem *CCECProcessor::GetAudioSystem(void) const | |
697 | { | |
698 | return CCECBusDevice::AsAudioSystem(m_busDevices->At(CECDEVICE_AUDIOSYSTEM)); | |
699 | } | |
700 | ||
701 | CCECPlaybackDevice *CCECProcessor::GetPlaybackDevice(cec_logical_address address) const | |
702 | { | |
703 | return CCECBusDevice::AsPlaybackDevice(m_busDevices->At(address)); | |
704 | } | |
705 | ||
706 | CCECRecordingDevice *CCECProcessor::GetRecordingDevice(cec_logical_address address) const | |
707 | { | |
708 | return CCECBusDevice::AsRecordingDevice(m_busDevices->At(address)); | |
709 | } | |
710 | ||
711 | CCECTuner *CCECProcessor::GetTuner(cec_logical_address address) const | |
712 | { | |
713 | return CCECBusDevice::AsTuner(m_busDevices->At(address)); | |
714 | } | |
715 | ||
716 | bool CCECProcessor::AllocateLogicalAddresses(CCECClient* client) | |
717 | { | |
718 | libcec_configuration &configuration = *client->GetConfiguration(); | |
719 | ||
720 | // mark as unregistered | |
721 | client->SetRegistered(false); | |
722 | ||
723 | // unregister this client from the old addresses | |
724 | CECDEVICEVEC devices; | |
725 | m_busDevices->GetByLogicalAddresses(devices, configuration.logicalAddresses); | |
726 | for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) | |
727 | { | |
728 | // remove client entry | |
729 | CLockObject lock(m_mutex); | |
730 | m_clients.erase((*it)->GetLogicalAddress()); | |
731 | } | |
732 | ||
733 | // find logical addresses for this client | |
734 | if (!client->AllocateLogicalAddresses()) | |
735 | { | |
736 | m_libcec->AddLog(CEC_LOG_ERROR, "failed to find a free logical address for the client"); | |
737 | return false; | |
738 | } | |
739 | ||
740 | // register this client on the new addresses | |
741 | devices.clear(); | |
742 | m_busDevices->GetByLogicalAddresses(devices, configuration.logicalAddresses); | |
743 | for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) | |
744 | { | |
745 | // set the physical address of the device at this LA | |
746 | if (CLibCEC::IsValidPhysicalAddress(configuration.iPhysicalAddress)) | |
747 | (*it)->SetPhysicalAddress(configuration.iPhysicalAddress); | |
748 | ||
749 | // replace a previous client | |
750 | CLockObject lock(m_mutex); | |
751 | m_clients.erase((*it)->GetLogicalAddress()); | |
752 | m_clients.insert(make_pair((*it)->GetLogicalAddress(), client)); | |
753 | } | |
754 | ||
755 | // set the new ackmask | |
756 | SetLogicalAddresses(GetLogicalAddresses()); | |
757 | ||
758 | // resume outgoing communication | |
759 | m_bStallCommunication = false; | |
760 | ||
761 | return true; | |
762 | } | |
763 | ||
764 | uint16_t CCECProcessor::GetPhysicalAddressFromEeprom(void) | |
765 | { | |
766 | libcec_configuration config; config.Clear(); | |
767 | if (m_communication) | |
768 | m_communication->GetConfiguration(config); | |
769 | return config.iPhysicalAddress; | |
770 | } | |
771 | ||
772 | bool CCECProcessor::RegisterClient(CCECClient *client) | |
773 | { | |
774 | if (!client) | |
775 | return false; | |
776 | ||
777 | libcec_configuration &configuration = *client->GetConfiguration(); | |
778 | ||
779 | if (configuration.clientVersion < CEC_CLIENT_VERSION_2_0_0) | |
780 | { | |
781 | m_libcec->AddLog(CEC_LOG_ERROR, "failed to register a new CEC client: client version %s is no longer supported", ToString((cec_client_version)configuration.clientVersion)); | |
782 | return false; | |
783 | } | |
784 | ||
785 | if (configuration.bMonitorOnly == 1) | |
786 | return true; | |
787 | ||
788 | if (!CECInitialised()) | |
789 | { | |
790 | m_libcec->AddLog(CEC_LOG_ERROR, "failed to register a new CEC client: CEC processor is not initialised"); | |
791 | return false; | |
792 | } | |
793 | ||
794 | // unregister the client first if it's already been marked as registered | |
795 | if (client->IsRegistered()) | |
796 | UnregisterClient(client); | |
797 | ||
798 | // ensure that controlled mode is enabled | |
799 | m_communication->SetControlledMode(true); | |
800 | m_bMonitor = false; | |
801 | ||
802 | // source logical address for requests | |
803 | cec_logical_address sourceAddress(CECDEVICE_UNREGISTERED); | |
804 | if (!m_communication->SupportsSourceLogicalAddress(CECDEVICE_UNREGISTERED)) | |
805 | { | |
806 | if (m_communication->SupportsSourceLogicalAddress(CECDEVICE_FREEUSE)) | |
807 | sourceAddress = CECDEVICE_FREEUSE; | |
808 | else | |
809 | { | |
810 | m_libcec->AddLog(CEC_LOG_ERROR, "failed to register a new CEC client: both unregistered and free use are not supported by the device"); | |
811 | return false; | |
812 | } | |
813 | } | |
814 | ||
815 | // ensure that we know the vendor id of the TV | |
816 | CCECBusDevice *tv = GetTV(); | |
817 | cec_vendor_id tvVendor(tv->GetVendorId(sourceAddress)); | |
818 | ||
819 | // wait until the handler is replaced, to avoid double registrations | |
820 | if (tvVendor != CEC_VENDOR_UNKNOWN && | |
821 | CCECCommandHandler::HasSpecificHandler(tvVendor)) | |
822 | { | |
823 | while (!tv->ReplaceHandler(false)) | |
824 | CEvent::Sleep(5); | |
825 | } | |
826 | ||
827 | // get the configuration from the client | |
828 | m_libcec->AddLog(CEC_LOG_NOTICE, "registering new CEC client - v%s", ToString((cec_client_version)configuration.clientVersion)); | |
829 | ||
830 | // get the current ackmask, so we can restore it if polling fails | |
831 | cec_logical_addresses previousMask = GetLogicalAddresses(); | |
832 | ||
833 | // mark as uninitialised | |
834 | client->SetInitialised(false); | |
835 | ||
836 | // find logical addresses for this client | |
837 | if (!AllocateLogicalAddresses(client)) | |
838 | { | |
839 | m_libcec->AddLog(CEC_LOG_ERROR, "failed to register the new CEC client - cannot allocate the requested device types"); | |
840 | SetLogicalAddresses(previousMask); | |
841 | return false; | |
842 | } | |
843 | ||
844 | // get the settings from the rom | |
845 | if (configuration.bGetSettingsFromROM == 1) | |
846 | { | |
847 | libcec_configuration config; config.Clear(); | |
848 | m_communication->GetConfiguration(config); | |
849 | ||
850 | CLockObject lock(m_mutex); | |
851 | if (!config.deviceTypes.IsEmpty()) | |
852 | configuration.deviceTypes = config.deviceTypes; | |
853 | if (CLibCEC::IsValidPhysicalAddress(config.iPhysicalAddress)) | |
854 | configuration.iPhysicalAddress = config.iPhysicalAddress; | |
855 | snprintf(configuration.strDeviceName, 13, "%s", config.strDeviceName); | |
856 | } | |
857 | ||
858 | // set the firmware version and build date | |
859 | configuration.serverVersion = LIBCEC_VERSION_CURRENT; | |
860 | configuration.iFirmwareVersion = m_communication->GetFirmwareVersion(); | |
861 | configuration.iFirmwareBuildDate = m_communication->GetFirmwareBuildDate(); | |
862 | configuration.adapterType = m_communication->GetAdapterType(); | |
863 | ||
864 | // mark the client as registered | |
865 | client->SetRegistered(true); | |
866 | ||
867 | sourceAddress = client->GetPrimaryLogicalAdddress(); | |
868 | ||
869 | // initialise the client | |
870 | bool bReturn = client->OnRegister(); | |
871 | ||
872 | // log the new registration | |
873 | CStdString strLog; | |
874 | strLog.Format("%s: %s", bReturn ? "CEC client registered" : "failed to register the CEC client", client->GetConnectionInfo().c_str()); | |
875 | m_libcec->AddLog(bReturn ? CEC_LOG_NOTICE : CEC_LOG_ERROR, strLog); | |
876 | ||
877 | // display a warning if the firmware can be upgraded | |
878 | if (bReturn && !IsRunningLatestFirmware()) | |
879 | { | |
880 | const char *strUpgradeMessage = "The firmware of this adapter can be upgraded. Please visit http://blog.pulse-eight.com/ for more information."; | |
881 | m_libcec->AddLog(CEC_LOG_WARNING, strUpgradeMessage); | |
882 | libcec_parameter param; | |
883 | param.paramData = (void*)strUpgradeMessage; param.paramType = CEC_PARAMETER_TYPE_STRING; | |
884 | client->Alert(CEC_ALERT_SERVICE_DEVICE, param); | |
885 | } | |
886 | ||
887 | // ensure that the command handler for the TV is initialised | |
888 | if (bReturn) | |
889 | { | |
890 | CCECCommandHandler *handler = GetTV()->GetHandler(); | |
891 | if (handler) | |
892 | handler->InitHandler(); | |
893 | GetTV()->MarkHandlerReady(); | |
894 | } | |
895 | ||
896 | // report our OSD name to the TV, since some TVs don't request it | |
897 | client->GetPrimaryDevice()->TransmitOSDName(CECDEVICE_TV, false); | |
898 | ||
899 | // request the power status of the TV | |
900 | tv->RequestPowerStatus(sourceAddress, true, true); | |
901 | ||
902 | return bReturn; | |
903 | } | |
904 | ||
905 | bool CCECProcessor::UnregisterClient(CCECClient *client) | |
906 | { | |
907 | if (!client) | |
908 | return false; | |
909 | ||
910 | if (client->IsRegistered()) | |
911 | m_libcec->AddLog(CEC_LOG_NOTICE, "unregistering client: %s", client->GetConnectionInfo().c_str()); | |
912 | ||
913 | // notify the client that it will be unregistered | |
914 | client->OnUnregister(); | |
915 | ||
916 | { | |
917 | CLockObject lock(m_mutex); | |
918 | // find all devices that match the LA's of this client | |
919 | CECDEVICEVEC devices; | |
920 | m_busDevices->GetByLogicalAddresses(devices, client->GetConfiguration()->logicalAddresses); | |
921 | for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) | |
922 | { | |
923 | // find the client | |
924 | map<cec_logical_address, CCECClient *>::iterator entry = m_clients.find((*it)->GetLogicalAddress()); | |
925 | // unregister the client | |
926 | if (entry != m_clients.end()) | |
927 | m_clients.erase(entry); | |
928 | ||
929 | // reset the device status | |
930 | (*it)->ResetDeviceStatus(true); | |
931 | } | |
932 | } | |
933 | ||
934 | // set the new ackmask | |
935 | cec_logical_addresses addresses = GetLogicalAddresses(); | |
936 | if (SetLogicalAddresses(addresses)) | |
937 | { | |
938 | // no more clients left, disable controlled mode | |
939 | if (addresses.IsEmpty() && !m_bMonitor) | |
940 | m_communication->SetControlledMode(false); | |
941 | ||
942 | return true; | |
943 | } | |
944 | ||
945 | return false; | |
946 | } | |
947 | ||
948 | void CCECProcessor::UnregisterClients(void) | |
949 | { | |
950 | m_libcec->AddLog(CEC_LOG_DEBUG, "unregistering all CEC clients"); | |
951 | ||
952 | vector<CCECClient *> clients = m_libcec->GetClients(); | |
953 | for (vector<CCECClient *>::iterator client = clients.begin(); client != clients.end(); client++) | |
954 | UnregisterClient(*client); | |
955 | ||
956 | CLockObject lock(m_mutex); | |
957 | m_clients.clear(); | |
958 | } | |
959 | ||
960 | CCECClient *CCECProcessor::GetClient(const cec_logical_address address) | |
961 | { | |
962 | CLockObject lock(m_mutex); | |
963 | map<cec_logical_address, CCECClient *>::const_iterator client = m_clients.find(address); | |
964 | if (client != m_clients.end()) | |
965 | return client->second; | |
966 | return NULL; | |
967 | } | |
968 | ||
969 | CCECClient *CCECProcessor::GetPrimaryClient(void) | |
970 | { | |
971 | CLockObject lock(m_mutex); | |
972 | map<cec_logical_address, CCECClient *>::const_iterator client = m_clients.begin(); | |
973 | if (client != m_clients.end()) | |
974 | return client->second; | |
975 | return NULL; | |
976 | } | |
977 | ||
978 | CCECBusDevice *CCECProcessor::GetPrimaryDevice(void) | |
979 | { | |
980 | return m_busDevices->At(GetLogicalAddress()); | |
981 | } | |
982 | ||
983 | cec_logical_address CCECProcessor::GetLogicalAddress(void) | |
984 | { | |
985 | cec_logical_addresses addresses = GetLogicalAddresses(); | |
986 | return addresses.primary; | |
987 | } | |
988 | ||
989 | cec_logical_addresses CCECProcessor::GetLogicalAddresses(void) | |
990 | { | |
991 | CLockObject lock(m_mutex); | |
992 | cec_logical_addresses addresses; | |
993 | addresses.Clear(); | |
994 | for (map<cec_logical_address, CCECClient *>::const_iterator client = m_clients.begin(); client != m_clients.end(); client++) | |
995 | addresses.Set(client->first); | |
996 | ||
997 | return addresses; | |
998 | } | |
999 | ||
1000 | bool CCECProcessor::IsHandledByLibCEC(const cec_logical_address address) const | |
1001 | { | |
1002 | CCECBusDevice *device = GetDevice(address); | |
1003 | return device && device->IsHandledByLibCEC(); | |
1004 | } | |
1005 | ||
1006 | bool CCECProcessor::IsRunningLatestFirmware(void) | |
1007 | { | |
1008 | return m_communication && m_communication->IsOpen() ? | |
1009 | m_communication->IsRunningLatestFirmware() : | |
1010 | true; | |
1011 | } | |
1012 | ||
1013 | void CCECProcessor::SwitchMonitoring(bool bSwitchTo) | |
1014 | { | |
1015 | { | |
1016 | CLockObject lock(m_mutex); | |
1017 | m_bMonitor = bSwitchTo; | |
1018 | } | |
1019 | if (bSwitchTo) | |
1020 | UnregisterClients(); | |
1021 | } | |
1022 | ||
1023 | void CCECProcessor::HandleLogicalAddressLost(cec_logical_address oldAddress) | |
1024 | { | |
1025 | // stall outgoing messages until we know our new LA | |
1026 | m_bStallCommunication = true; | |
1027 | ||
1028 | m_libcec->AddLog(CEC_LOG_NOTICE, "logical address %x was taken by another device, allocating a new address", oldAddress); | |
1029 | CCECClient* client = GetClient(oldAddress); | |
1030 | if (!client) | |
1031 | client = GetPrimaryClient(); | |
1032 | if (client) | |
1033 | { | |
1034 | if (m_addrAllocator) | |
1035 | while (m_addrAllocator->IsRunning()) Sleep(5); | |
1036 | delete m_addrAllocator; | |
1037 | ||
1038 | m_addrAllocator = new CCECAllocateLogicalAddress(this, client); | |
1039 | m_addrAllocator->CreateThread(); | |
1040 | } | |
1041 | } | |
1042 | ||
1043 | void CCECProcessor::HandlePhysicalAddressChanged(uint16_t iNewAddress) | |
1044 | { | |
1045 | m_libcec->AddLog(CEC_LOG_NOTICE, "physical address changed to %04x", iNewAddress); | |
1046 | CCECClient* client = GetPrimaryClient(); | |
1047 | if (client) | |
1048 | client->SetPhysicalAddress(iNewAddress); | |
1049 | } | |
1050 | ||
1051 | uint16_t CCECProcessor::GetAdapterVendorId(void) const | |
1052 | { | |
1053 | return m_communication ? m_communication->GetAdapterVendorId() : 0; | |
1054 | } | |
1055 | ||
1056 | uint16_t CCECProcessor::GetAdapterProductId(void) const | |
1057 | { | |
1058 | return m_communication ? m_communication->GetAdapterProductId() : 0; | |
1059 | } | |
1060 | ||
1061 | CCECAllocateLogicalAddress::CCECAllocateLogicalAddress(CCECProcessor* processor, CCECClient* client) : | |
1062 | m_processor(processor), | |
1063 | m_client(client) { } | |
1064 | ||
1065 | void* CCECAllocateLogicalAddress::Process(void) | |
1066 | { | |
1067 | m_processor->AllocateLogicalAddresses(m_client); | |
1068 | return NULL; | |
1069 | } |