Imported Upstream version 2.2.0
[deb_libcec.git] / src / lib / implementations / SLCommandHandler.cpp
CommitLineData
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 "SLCommandHandler.h"
35
36#include "lib/platform/util/timeutils.h"
37#include "lib/devices/CECBusDevice.h"
38#include "lib/devices/CECPlaybackDevice.h"
39#include "lib/CECProcessor.h"
40#include "lib/LibCEC.h"
41
42using namespace CEC;
43using namespace PLATFORM;
44
45#define SL_COMMAND_TYPE_HDDRECORDER_DISC 0x01
46#define SL_COMMAND_TYPE_VCR 0x02
47#define SL_COMMAND_TYPE_DVDPLAYER 0x03
48#define SL_COMMAND_TYPE_HDDRECORDER_DISC2 0x04
49#define SL_COMMAND_TYPE_HDDRECORDER 0x05
50
51#define SL_COMMAND_INIT 0x01
52#define SL_COMMAND_ACK_INIT 0x02
53#define SL_COMMAND_POWER_ON 0x03
54#define SL_COMMAND_CONNECT_REQUEST 0x04
55#define SL_COMMAND_SET_DEVICE_MODE 0x05
56#define SL_COMMAND_REQUEST_POWER_STATUS 0xa0
57
58#define LIB_CEC m_busDevice->GetProcessor()->GetLib()
59#define ToString(p) LIB_CEC->ToString(p)
60
61CSLCommandHandler::CSLCommandHandler(CCECBusDevice *busDevice,
62 int32_t iTransmitTimeout /* = CEC_DEFAULT_TRANSMIT_TIMEOUT */,
63 int32_t iTransmitWait /* = CEC_DEFAULT_TRANSMIT_WAIT */,
64 int8_t iTransmitRetries /* = CEC_DEFAULT_TRANSMIT_RETRIES */,
65 int64_t iActiveSourcePending /* = 0 */) :
66 CCECCommandHandler(busDevice, iTransmitTimeout, iTransmitWait, iTransmitRetries, iActiveSourcePending),
67 m_bSLEnabled(false)
68{
69 m_vendorId = CEC_VENDOR_LG;
70
71 /* LG devices don't always reply to CEC version requests, so just set it to 1.3a */
72 m_busDevice->SetCecVersion(CEC_VERSION_1_3A);
73
74 /* LG devices always return "korean" as language */
75 cec_menu_language lang;
76 lang.device = m_busDevice->GetLogicalAddress();
77 snprintf(lang.language, 4, "eng");
78 m_busDevice->SetMenuLanguage(lang);
79}
80
81bool CSLCommandHandler::InitHandler(void)
82{
83 if (m_bHandlerInited)
84 return true;
85 m_bHandlerInited = true;
86
87 if (m_busDevice->GetLogicalAddress() != CECDEVICE_TV)
88 return true;
89
90 CCECBusDevice *primary = m_processor->GetPrimaryDevice();
91 if (primary && primary->GetLogicalAddress() != CECDEVICE_UNREGISTERED)
92 {
93 /* imitate LG devices */
94 if (m_busDevice->GetLogicalAddress() != primary->GetLogicalAddress())
95 {
96 primary->SetVendorId(CEC_VENDOR_LG);
97 primary->ReplaceHandler(false);
98 }
99 }
100
101 return true;
102}
103
104int CSLCommandHandler::HandleVendorCommand(const cec_command &command)
105{
106 if (!m_processor->IsHandledByLibCEC(command.destination))
107 return true;
108
109 if (command.parameters.size == 1 &&
110 command.parameters[0] == SL_COMMAND_INIT)
111 {
112 HandleVendorCommandSLInit(command);
113 return COMMAND_HANDLED;
114 }
115 else if (command.parameters.size == 2 &&
116 command.parameters[0] == SL_COMMAND_POWER_ON)
117 {
118 HandleVendorCommandPowerOn(command);
119 return COMMAND_HANDLED;
120 }
121 else if (command.parameters.size == 2 &&
122 command.parameters[0] == SL_COMMAND_CONNECT_REQUEST)
123 {
124 HandleVendorCommandSLConnect(command);
125 return COMMAND_HANDLED;
126 }
127 else if (command.parameters.size == 1 &&
128 command.parameters[0] == SL_COMMAND_REQUEST_POWER_STATUS)
129 {
130 HandleVendorCommandPowerOnStatus(command);
131 return COMMAND_HANDLED;
132 }
133
134 return CCECCommandHandler::HandleVendorCommand(command);
135}
136
137void CSLCommandHandler::HandleVendorCommandSLInit(const cec_command &command)
138{
139 CCECBusDevice* dev = m_processor->GetDevice(command.destination);
140 if (dev && dev->IsHandledByLibCEC())
141 {
142 if (!dev->IsActiveSource())
143 {
144 dev->SetPowerStatus(CEC_POWER_STATUS_STANDBY);
145 dev->TransmitPowerState(command.initiator, true);
146 }
147
148 TransmitVendorCommandSLAckInit(command.destination, command.initiator);
149 }
150}
151
152void CSLCommandHandler::TransmitVendorCommandSLAckInit(const cec_logical_address iSource, const cec_logical_address iDestination)
153{
154 cec_command response;
155 cec_command::Format(response, iSource, iDestination, CEC_OPCODE_VENDOR_COMMAND);
156 response.PushBack(SL_COMMAND_ACK_INIT);
157 response.PushBack(SL_COMMAND_TYPE_HDDRECORDER);
158
159 Transmit(response, false, true);
160 SetSLInitialised();
161}
162
163void CSLCommandHandler::HandleVendorCommandPowerOn(const cec_command &command)
164{
165 if (command.initiator != CECDEVICE_TV)
166 return;
167
168 CCECBusDevice *device = m_processor->GetPrimaryDevice();
169 if (device)
170 {
171 SetSLInitialised();
172 device->MarkAsActiveSource();
173 device->SetPowerStatus(CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON);
174 device->TransmitPowerState(command.initiator, true);
175
176 CEvent::Sleep(2000);
177 device->SetPowerStatus(CEC_POWER_STATUS_ON);
178 device->TransmitPowerState(command.initiator, false);
179 device->TransmitPhysicalAddress(false);
180
181 if (device->IsActiveSource())
182 ActivateSource();
183 }
184}
185void CSLCommandHandler::HandleVendorCommandPowerOnStatus(const cec_command &command)
186{
187 if (command.destination != CECDEVICE_BROADCAST)
188 {
189 CCECBusDevice *device = m_processor->GetPrimaryDevice();
190 if (device)
191 {
192 device->SetPowerStatus(CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON);
193 device->TransmitPowerState(command.initiator, true);
194 device->SetPowerStatus(CEC_POWER_STATUS_ON);
195 }
196 }
197}
198
199void CSLCommandHandler::HandleVendorCommandSLConnect(const cec_command &command)
200{
201 SetSLInitialised();
202 TransmitVendorCommandSetDeviceMode(command.destination, command.initiator, CEC_DEVICE_TYPE_RECORDING_DEVICE);
203
204
205 if (m_processor->IsActiveSource(command.destination) && m_processor->IsHandledByLibCEC(command.destination))
206 {
207 CCECBusDevice* dev = m_processor->GetDevice(command.destination);
208 CCECPlaybackDevice* pb = dev->AsPlaybackDevice();
209 if (pb)
210 pb->TransmitDeckStatus(command.initiator, true);
211 dev->TransmitPowerState(command.initiator, true);
212 }
213}
214
215void CSLCommandHandler::TransmitVendorCommandSetDeviceMode(const cec_logical_address iSource, const cec_logical_address iDestination, const cec_device_type type)
216{
217 cec_command response;
218 cec_command::Format(response, iSource, iDestination, CEC_OPCODE_VENDOR_COMMAND);
219 response.PushBack(SL_COMMAND_SET_DEVICE_MODE);
220 response.PushBack((uint8_t)type);
221 Transmit(response, false, true);
222}
223
224int CSLCommandHandler::HandleGiveDeckStatus(const cec_command &command)
225{
226 if (!m_processor->CECInitialised() ||
227 !m_processor->IsHandledByLibCEC(command.destination))
228 return CEC_ABORT_REASON_NOT_IN_CORRECT_MODE_TO_RESPOND;
229
230 CCECPlaybackDevice *device = CCECBusDevice::AsPlaybackDevice(GetDevice(command.destination));
231 if (!device || command.parameters.size == 0)
232 return CEC_ABORT_REASON_INVALID_OPERAND;
233
234 device->SetDeckStatus(CEC_DECK_INFO_OTHER_STATUS_LG);
235 if (command.parameters[0] == CEC_STATUS_REQUEST_ON)
236 {
237 device->TransmitDeckStatus(command.initiator, true);
238 ActivateSource();
239 return COMMAND_HANDLED;
240 }
241 else if (command.parameters[0] == CEC_STATUS_REQUEST_ONCE)
242 {
243 device->TransmitDeckStatus(command.initiator, true);
244 return COMMAND_HANDLED;
245 }
246
247 return CCECCommandHandler::HandleGiveDeckStatus(command);
248}
249
250int CSLCommandHandler::HandleGiveDevicePowerStatus(const cec_command &command)
251{
252 if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination) && command.initiator == CECDEVICE_TV)
253 {
254 CCECBusDevice *device = GetDevice(command.destination);
255 if (device && device->GetCurrentPowerStatus() != CEC_POWER_STATUS_ON)
256 {
257 device->TransmitPowerState(command.initiator, true);
258 device->SetPowerStatus(CEC_POWER_STATUS_ON);
259 }
260 else
261 {
262 if (m_resetPowerState.IsSet() && m_resetPowerState.TimeLeft() > 0)
263 {
264 /* TODO assume that we've bugged out. the return button no longer works after this */
265 LIB_CEC->AddLog(CEC_LOG_WARNING, "FIXME: LG seems to have bugged out. resetting to 'in transition standby to on'. the return button will not work");
266 device->SetPowerStatus(CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON);
267 device->TransmitPowerState(command.initiator, true);
268 device->SetPowerStatus(CEC_POWER_STATUS_ON);
269 m_resetPowerState.Init(5000);
270 }
271 else
272 {
273 device->TransmitPowerState(command.initiator, true);
274 m_resetPowerState.Init(5000);
275 }
276 }
277
278 return COMMAND_HANDLED;
279 }
280
281 return CEC_ABORT_REASON_NOT_IN_CORRECT_MODE_TO_RESPOND;
282}
283
284int CSLCommandHandler::HandleRequestActiveSource(const cec_command &command)
285{
286 if (m_processor->CECInitialised())
287 {
288 if (!SLInitialised())
289 TransmitVendorCommandSLAckInit(m_processor->GetPrimaryDevice()->GetLogicalAddress(), command.initiator);
290 CCECCommandHandler::HandleRequestActiveSource(command);
291 }
292 return CEC_ABORT_REASON_NOT_IN_CORRECT_MODE_TO_RESPOND;
293}
294
295int CSLCommandHandler::HandleFeatureAbort(const cec_command &command)
296{
297 CCECBusDevice* primary = m_processor->GetPrimaryDevice();
298 if (command.parameters.size == 0 && primary->GetLogicalAddress() != CECDEVICE_UNKNOWN &&
299 primary->GetCurrentPowerStatus() == CEC_POWER_STATUS_ON && !SLInitialised() &&
300 command.initiator == CECDEVICE_TV)
301 {
302 if (!SLInitialised() && m_processor->IsActiveSource(command.destination))
303 {
304 TransmitVendorCommandSLAckInit(command.destination, command.initiator);
305 return COMMAND_HANDLED;
306 }
307 }
308
309 return CCECCommandHandler::HandleFeatureAbort(command);
310}
311
312int CSLCommandHandler::HandleStandby(const cec_command &command)
313{
314 ResetSLState();
315
316 return CCECCommandHandler::HandleStandby(command);
317}
318
319void CSLCommandHandler::ResetSLState(void)
320{
321 LIB_CEC->AddLog(CEC_LOG_NOTICE, "resetting SL initialised state");
322 CLockObject lock(m_SLMutex);
323 m_bSLEnabled = false;
324 m_processor->GetPrimaryDevice()->SetPowerStatus(CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON);
325}
326
327void CSLCommandHandler::SetSLInitialised(void)
328{
329 LIB_CEC->AddLog(CEC_LOG_NOTICE, "SL initialised");
330 CLockObject lock(m_SLMutex);
331 m_bSLEnabled = true;
332}
333
334bool CSLCommandHandler::SLInitialised(void)
335{
336 CLockObject lock(m_SLMutex);
337 return m_bSLEnabled;
338}
339
340bool CSLCommandHandler::PowerOn(const cec_logical_address iInitiator, const cec_logical_address iDestination)
341{
342 if (iDestination != CECDEVICE_TV)
343 {
344 /* LG devices only allow themselves to be woken up by the TV with a vendor command */
345 cec_command command;
346
347 if (!m_bSLEnabled)
348 TransmitVendorID(CECDEVICE_TV, iDestination, CEC_VENDOR_LG, false);
349
350 cec_command::Format(command, CECDEVICE_TV, iDestination, CEC_OPCODE_VENDOR_COMMAND);
351 command.PushBack(SL_COMMAND_POWER_ON);
352 command.PushBack(0);
353 return Transmit(command, false, false);
354 }
355
356 return CCECCommandHandler::PowerOn(iInitiator, iDestination);
357}
358
359bool CSLCommandHandler::ActivateSource(bool bTransmitDelayedCommandsOnly /* = false */)
360{
361 if (m_busDevice->IsActiveSource() &&
362 m_busDevice->IsHandledByLibCEC())
363 {
364 {
365 CLockObject lock(m_mutex);
366 // check if we need to send a delayed source switch
367 if (bTransmitDelayedCommandsOnly)
368 {
369 if (m_iActiveSourcePending == 0 || GetTimeMs() < m_iActiveSourcePending)
370 return false;
371
372#ifdef CEC_DEBUGGING
373 LIB_CEC->AddLog(CEC_LOG_DEBUG, "transmitting delayed activate source command");
374#endif
375 }
376 }
377
378 CCECPlaybackDevice *device = m_busDevice->AsPlaybackDevice();
379 if (device)
380 device->SetDeckStatus(!device->IsActiveSource() ? CEC_DECK_INFO_OTHER_STATUS : CEC_DECK_INFO_OTHER_STATUS_LG);
381
382 // power on the TV
383 CCECBusDevice* tv = m_processor->GetDevice(CECDEVICE_TV);
384 bool bTvPresent = (tv && tv->GetStatus() == CEC_DEVICE_STATUS_PRESENT);
385 bool bActiveSourceFailed(false);
386 if (bTvPresent)
387 {
388 bActiveSourceFailed = !device->TransmitImageViewOn();
389 }
390 else
391 {
392 LIB_CEC->AddLog(CEC_LOG_DEBUG, "TV not present, not sending 'image view on'");
393 }
394
395 // check if we're allowed to switch sources
396 bool bSourceSwitchAllowed = SourceSwitchAllowed();
397 if (!bSourceSwitchAllowed)
398 LIB_CEC->AddLog(CEC_LOG_DEBUG, "source switch is currently not allowed by command handler");
399
400 // switch sources (if allowed)
401 if (!bActiveSourceFailed && bSourceSwitchAllowed)
402 {
403 bActiveSourceFailed = !m_busDevice->TransmitActiveSource(false);
404 }
405
406 // retry later
407 if (bActiveSourceFailed || !bSourceSwitchAllowed)
408 {
409 LIB_CEC->AddLog(CEC_LOG_DEBUG, "failed to make '%s' the active source. will retry later", m_busDevice->GetLogicalAddressName());
410 int64_t now(GetTimeMs());
411 CLockObject lock(m_mutex);
412 if (m_iActiveSourcePending == 0 || m_iActiveSourcePending < now)
413 m_iActiveSourcePending = now + (int64_t)CEC_ACTIVE_SOURCE_SWITCH_RETRY_TIME_MS;
414 return false;
415 }
416 else
417 {
418 CLockObject lock(m_mutex);
419 // clear previous pending active source command
420 m_iActiveSourcePending = 0;
421 }
422
423 // mark the handler as initialised
424 CLockObject lock(m_mutex);
425 m_bHandlerInited = true;
426 }
427 return true;
428}