X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Flib%2FCECProcessor.cpp;h=63e481551457682c0e58545b37272028a56979f0;hb=f7539eaf1ed0a488c0a93998c9b178d435014c51;hp=0add41b67ca42cb3763ee54fffff890a151b528c;hpb=99aeafb929fa132a096c236c4ae1eb78c2a595ec;p=deb_libcec.git diff --git a/src/lib/CECProcessor.cpp b/src/lib/CECProcessor.cpp index 0add41b..63e4815 100644 --- a/src/lib/CECProcessor.cpp +++ b/src/lib/CECProcessor.cpp @@ -1,7 +1,7 @@ /* * This file is part of the libCEC(R) library. * - * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited. All rights reserved. + * libCEC(R) is Copyright (C) 2011-2013 Pulse-Eight Limited. All rights reserved. * libCEC(R) is an original work, containing original code. * * libCEC(R) is a trademark of Pulse-Eight Limited. @@ -30,9 +30,10 @@ * http://www.pulse-eight.net/ */ +#include "env.h" #include "CECProcessor.h" -#include "adapter/USBCECAdapterCommunication.h" +#include "adapter/AdapterFactory.h" #include "devices/CECBusDevice.h" #include "devices/CECAudioSystem.h" #include "devices/CECPlaybackDevice.h" @@ -42,71 +43,112 @@ #include "implementations/CECCommandHandler.h" #include "LibCEC.h" #include "CECClient.h" +#include "CECTypeUtils.h" #include "platform/util/timeutils.h" +#include "platform/util/util.h" using namespace CEC; using namespace std; using namespace PLATFORM; #define CEC_PROCESSOR_SIGNAL_WAIT_TIME 1000 +#define ACTIVE_SOURCE_CHECK_INTERVAL 500 +#define TV_PRESENT_CHECK_INTERVAL 30000 -#define ToString(x) m_libcec->ToString(x) +#define ToString(x) CCECTypeUtils::ToString(x) + +CCECStandbyProtection::CCECStandbyProtection(CCECProcessor* processor) : + m_processor(processor) {} +CCECStandbyProtection::~CCECStandbyProtection(void) {} + +void* CCECStandbyProtection::Process(void) +{ + int64_t last = GetTimeMs(); + int64_t next; + while (!IsStopped()) + { + PLATFORM::CEvent::Sleep(1000); + + next = GetTimeMs(); + + // reset the connection if the clock changed + if (next < last || next - last > 10000) + { + libcec_parameter param; + param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN; + m_processor->GetLib()->Alert(CEC_ALERT_CONNECTION_LOST, param); + break; + } + + last = next; + } + return NULL; +} CCECProcessor::CCECProcessor(CLibCEC *libcec) : m_bInitialised(false), m_communication(NULL), m_libcec(libcec), - m_bMonitor(false), - m_iPreviousAckMask(0), m_iStandardLineTimeout(3), m_iRetryLineTimeout(3), - m_iLastTransmission(0) + m_iLastTransmission(0), + m_bMonitor(true), + m_addrAllocator(NULL), + m_bStallCommunication(false), + m_connCheck(NULL) { m_busDevices = new CCECDeviceMap(this); } CCECProcessor::~CCECProcessor(void) { + m_bStallCommunication = false; + DELETE_AND_NULL(m_addrAllocator); Close(); - delete m_busDevices; + DELETE_AND_NULL(m_busDevices); } bool CCECProcessor::Start(const char *strPort, uint16_t iBaudRate /* = CEC_SERIAL_DEFAULT_BAUDRATE */, uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */) { CLockObject lock(m_mutex); + // open a connection if (!OpenConnection(strPort, iBaudRate, iTimeoutMs)) return false; - /* create the processor thread */ + // create the processor thread if (!IsRunning()) { - if (CreateThread()) - m_libcec->AddLog(CEC_LOG_DEBUG, "processor thread started"); - else + if (!CreateThread()) { m_libcec->AddLog(CEC_LOG_ERROR, "could not create a processor thread"); return false; } } - SetCECInitialised(true); - return true; } void CCECProcessor::Close(void) { + // mark as uninitialised SetCECInitialised(false); + + // stop the processor + DELETE_AND_NULL(m_connCheck); + StopThread(-1); + m_inBuffer.Broadcast(); StopThread(); - if (m_communication) - { - delete m_communication; - m_communication = NULL; - } + // close the connection + DELETE_AND_NULL(m_communication); +} - m_bMonitor = false; - m_iPreviousAckMask = 0; +void CCECProcessor::ResetMembers(void) +{ + // close the connection + DELETE_AND_NULL(m_communication); + + // reset the other members to the initial state m_iStandardLineTimeout = 3; m_iRetryLineTimeout = 3; m_iLastTransmission = 0; @@ -116,22 +158,25 @@ void CCECProcessor::Close(void) bool CCECProcessor::OpenConnection(const char *strPort, uint16_t iBaudRate, uint32_t iTimeoutMs, bool bStartListening /* = true */) { bool bReturn(false); + CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT); + + // ensure that a previous connection is closed Close(); + // reset all member to the initial state + ResetMembers(); + + // check whether the Close() method deleted any previous connection + if (m_communication) { - CLockObject lock(m_mutex); - if (m_communication && m_communication->IsOpen()) - { - m_libcec->AddLog(CEC_LOG_ERROR, "connection already opened"); - return true; - } - else if (!m_communication) - m_communication = new CUSBCECAdapterCommunication(this, strPort, iBaudRate); + m_libcec->AddLog(CEC_LOG_ERROR, "previous connection could not be closed"); + return bReturn; } - CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT); + // create a new connection + m_communication = CAdapterFactory(this->m_libcec).GetInstance(strPort, iBaudRate); - /* open a new connection */ + // open a new connection unsigned iConnectTry(0); while (timeout.TimeLeft() > 0 && (bReturn = m_communication->Open((timeout.TimeLeft() / CEC_CONNECT_TRIES), false, bStartListening)) == false) { @@ -142,6 +187,9 @@ bool CCECProcessor::OpenConnection(const char *strPort, uint16_t iBaudRate, uint m_libcec->AddLog(CEC_LOG_NOTICE, "connection opened"); + // mark as initialised + SetCECInitialised(true); + return bReturn; } @@ -153,22 +201,26 @@ bool CCECProcessor::CECInitialised(void) void CCECProcessor::SetCECInitialised(bool bSetTo /* = true */) { - CLockObject lock(m_mutex); - m_bInitialised = bSetTo; + { + CLockObject lock(m_mutex); + m_bInitialised = bSetTo; + } if (!bSetTo) UnregisterClients(); } -bool CCECProcessor::TryLogicalAddress(cec_logical_address address) +bool CCECProcessor::TryLogicalAddress(cec_logical_address address, cec_version libCECSpecVersion /* = CEC_VERSION_1_4 */) { + // find the device CCECBusDevice *device = m_busDevices->At(address); if (device) { + // check if it's already marked as present or used if (device->IsPresent() || device->IsHandledByLibCEC()) return false; - SetAckMask(0); - return device->TryLogicalAddress(); + // poll the LA if not + return device->TryLogicalAddress(libCECSpecVersion); } return false; @@ -179,6 +231,7 @@ void CCECProcessor::ReplaceHandlers(void) if (!CECInitialised()) return; + // check each device for (CECDEVICEMAP::iterator it = m_busDevices->Begin(); it != m_busDevices->End(); it++) it->second->ReplaceHandler(true); } @@ -192,30 +245,67 @@ void *CCECProcessor::Process(void) { m_libcec->AddLog(CEC_LOG_DEBUG, "processor thread started"); - cec_command command; - command.Clear(); + if (!m_connCheck) + m_connCheck = new CCECStandbyProtection(this); + m_connCheck->CreateThread(); + + cec_command command; command.Clear(); + CTimeout activeSourceCheck(ACTIVE_SOURCE_CHECK_INTERVAL); + CTimeout tvPresentCheck(TV_PRESENT_CHECK_INTERVAL); + // as long as we're not being stopped and the connection is open while (!IsStopped() && m_communication->IsOpen()) { + // wait for a new incoming command, and process it if (m_inBuffer.Pop(command, CEC_PROCESSOR_SIGNAL_WAIT_TIME)) - ParseCommand(command); + ProcessCommand(command); - if (CECInitialised()) + if (CECInitialised() && !IsStopped()) { + // check clients for keypress timeouts + m_libcec->CheckKeypressTimeout(); + + // check if we need to replace handlers ReplaceHandlers(); - m_libcec->CheckKeypressTimeout(); + // check whether we need to activate a source, if it failed before + if (activeSourceCheck.TimeLeft() == 0) + { + if (CECInitialised()) + TransmitPendingActiveSourceCommands(); + activeSourceCheck.Init(ACTIVE_SOURCE_CHECK_INTERVAL); + } + + // check whether the TV is present and responding + if (tvPresentCheck.TimeLeft() == 0) + { + CCECClient *primary = GetPrimaryClient(); + // only check whether the tv responds to polls when a client is connected and not in monitoring mode + if (primary && primary->GetConfiguration()->bMonitorOnly != 1) + { + if (!m_busDevices->At(CECDEVICE_TV)->IsPresent()) + { + libcec_parameter param; + param.paramType = CEC_PARAMETER_TYPE_STRING; + param.paramData = (void*)"TV does not respond to CEC polls"; + primary->Alert(CEC_ALERT_TV_POLL_FAILED, param); + } + } + tvPresentCheck.Init(TV_PRESENT_CHECK_INTERVAL); + } } } return NULL; } -bool CCECProcessor::SetActiveSource(uint16_t iStreamPath) +bool CCECProcessor::ActivateSource(uint16_t iStreamPath) { bool bReturn(false); + // find the device with the given PA CCECBusDevice *device = GetDeviceByPhysicalAddress(iStreamPath); + // and make it the active source when found if (device) bReturn = device->ActivateSource(); else @@ -224,18 +314,36 @@ bool CCECProcessor::SetActiveSource(uint16_t iStreamPath) return bReturn; } +void CCECProcessor::SetActiveSource(bool bSetTo, bool bClientUnregistered) +{ + if (m_communication) + m_communication->SetActiveSource(bSetTo, bClientUnregistered); +} + void CCECProcessor::SetStandardLineTimeout(uint8_t iTimeout) { CLockObject lock(m_mutex); m_iStandardLineTimeout = iTimeout; } +uint8_t CCECProcessor::GetStandardLineTimeout(void) +{ + CLockObject lock(m_mutex); + return m_iStandardLineTimeout; +} + void CCECProcessor::SetRetryLineTimeout(uint8_t iTimeout) { CLockObject lock(m_mutex); m_iRetryLineTimeout = iTimeout; } +uint8_t CCECProcessor::GetRetryLineTimeout(void) +{ + CLockObject lock(m_mutex); + return m_iRetryLineTimeout; +} + bool CCECProcessor::PhysicalAddressInUse(uint16_t iPhysicalAddress) { CCECBusDevice *device = GetDeviceByPhysicalAddress(iPhysicalAddress); @@ -245,41 +353,34 @@ bool CCECProcessor::PhysicalAddressInUse(uint16_t iPhysicalAddress) void CCECProcessor::LogOutput(const cec_command &data) { CStdString strTx; + + // initiator and destination strTx.Format("<< %02x", ((uint8_t)data.initiator << 4) + (uint8_t)data.destination); + + // append the opcode if (data.opcode_set) strTx.AppendFormat(":%02x", (uint8_t)data.opcode); + // append the parameters for (uint8_t iPtr = 0; iPtr < data.parameters.size; iPtr++) strTx.AppendFormat(":%02x", data.parameters[iPtr]); - m_libcec->AddLog(CEC_LOG_TRAFFIC, strTx.c_str()); -} - -bool CCECProcessor::SwitchMonitoring(bool bEnable) -{ - m_libcec->AddLog(CEC_LOG_NOTICE, "== %s monitoring mode ==", bEnable ? "enabling" : "disabling"); - { - CLockObject lock(m_mutex); - m_bMonitor = bEnable; - m_iPreviousAckMask = m_communication->GetAckMask(); - } - - if (bEnable) - return SetAckMask(0); - else - return SetAckMask(m_iPreviousAckMask); + // and log it + m_libcec->AddLog(CEC_LOG_TRAFFIC, strTx.c_str()); } bool CCECProcessor::PollDevice(cec_logical_address iAddress) { - CCECBusDevice *device = m_busDevices->At(iAddress); + // try to find the primary device CCECBusDevice *primary = GetPrimaryDevice(); + // poll the destination, with the primary as source + if (primary) + return primary->TransmitPoll(iAddress, true); + + CCECBusDevice *device = m_busDevices->At(CECDEVICE_UNREGISTERED); if (device) - { - return primary ? - primary->TransmitPoll(iAddress) : - device->TransmitPoll(iAddress); - } + return device->TransmitPoll(iAddress, true); + return false; } @@ -325,55 +426,82 @@ bool CCECProcessor::IsActiveSource(cec_logical_address iAddress) return device && device->IsActiveSource(); } -bool CCECProcessor::Transmit(const cec_command &data) +bool CCECProcessor::Transmit(const cec_command &data, bool bIsReply) { - CCECBusDevice *initiator = m_busDevices->At(data.initiator); + cec_command transmitData(data); + uint8_t iMaxTries(0); + bool bRetry(true); + uint8_t iTries(0); + + // get the current timeout setting + uint8_t iLineTimeout(GetStandardLineTimeout()); + + // reset the state of this message to 'unknown' + cec_adapter_message_state adapterState = ADAPTER_MESSAGE_STATE_UNKNOWN; + + if (!m_communication->SupportsSourceLogicalAddress(transmitData.initiator)) + { + if (transmitData.initiator == CECDEVICE_UNREGISTERED && m_communication->SupportsSourceLogicalAddress(CECDEVICE_FREEUSE)) + { + m_libcec->AddLog(CEC_LOG_DEBUG, "initiator '%s' is not supported by the CEC adapter. using '%s' instead", ToString(transmitData.initiator), ToString(CECDEVICE_FREEUSE)); + transmitData.initiator = CECDEVICE_FREEUSE; + } + else + { + m_libcec->AddLog(CEC_LOG_DEBUG, "initiator '%s' is not supported by the CEC adapter", ToString(transmitData.initiator)); + return false; + } + } + + LogOutput(transmitData); + + // find the initiator device + CCECBusDevice *initiator = m_busDevices->At(transmitData.initiator); if (!initiator) { m_libcec->AddLog(CEC_LOG_WARNING, "invalid initiator"); return false; } - if (data.destination != CECDEVICE_BROADCAST) + // find the destination device, if it's not the broadcast address + if (transmitData.destination != CECDEVICE_BROADCAST) { - CCECBusDevice *destination = m_busDevices->At(data.destination); + // check if the device is marked as handled by libCEC + CCECBusDevice *destination = m_busDevices->At(transmitData.destination); if (destination && destination->IsHandledByLibCEC()) { + // and reject the command if it's trying to send data to a device that is handled by libCEC m_libcec->AddLog(CEC_LOG_WARNING, "not sending data to myself!"); return false; } } - uint8_t iMaxTries(0); + // wait until we finished allocating a new LA if it got lost + while (m_bStallCommunication) Sleep(5); + { CLockObject lock(m_mutex); - if (IsStopped()) - return false; - LogOutput(data); m_iLastTransmission = GetTimeMs(); - if (!m_communication || !m_communication->IsOpen()) - { - m_libcec->AddLog(CEC_LOG_ERROR, "cannot transmit command: connection closed"); - return false; - } + // set the number of tries iMaxTries = initiator->GetHandler()->GetTransmitRetries() + 1; + initiator->MarkHandlerReady(); } - bool bRetry(true); - uint8_t iTries(0); - uint8_t iLineTimeout = m_iStandardLineTimeout; - cec_adapter_message_state adapterState = ADAPTER_MESSAGE_STATE_UNKNOWN; - + // and try to send the command while (bRetry && ++iTries < iMaxTries) { - if (initiator->IsUnsupportedFeature(data.opcode)) + if (initiator->IsUnsupportedFeature(transmitData.opcode)) return false; - adapterState = m_communication->Write(data, bRetry, iLineTimeout); + adapterState = !IsStopped() && m_communication && m_communication->IsOpen() ? + m_communication->Write(transmitData, bRetry, iLineTimeout, bIsReply) : + ADAPTER_MESSAGE_STATE_ERROR; iLineTimeout = m_iRetryLineTimeout; } - return adapterState == ADAPTER_MESSAGE_STATE_SENT_ACKED; + return bIsReply ? + adapterState == ADAPTER_MESSAGE_STATE_SENT_ACKED || adapterState == ADAPTER_MESSAGE_STATE_SENT || adapterState == ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT : + adapterState == ADAPTER_MESSAGE_STATE_SENT_ACKED; } void CCECProcessor::TransmitAbort(cec_logical_address source, cec_logical_address destination, cec_opcode opcode, cec_abort_reason reason /* = CEC_ABORT_REASON_UNRECOGNIZED_OPCODE */) @@ -385,25 +513,19 @@ void CCECProcessor::TransmitAbort(cec_logical_address source, cec_logical_addres command.parameters.PushBack((uint8_t)opcode); command.parameters.PushBack((uint8_t)reason); - Transmit(command); + Transmit(command, true); } -void CCECProcessor::ParseCommand(const cec_command &command) +void CCECProcessor::ProcessCommand(const cec_command &command) { - CStdString dataStr; - dataStr.Format(">> %1x%1x", command.initiator, command.destination); - if (command.opcode_set == 1) - dataStr.AppendFormat(":%02x", command.opcode); - for (uint8_t iPtr = 0; iPtr < command.parameters.size; iPtr++) - dataStr.AppendFormat(":%02x", (unsigned int)command.parameters[iPtr]); - m_libcec->AddLog(CEC_LOG_TRAFFIC, dataStr.c_str()); + // log the command + m_libcec->AddLog(CEC_LOG_TRAFFIC, ToString(command).c_str()); - if (!m_bMonitor) - { - CCECBusDevice *device = m_busDevices->At(command.initiator); - if (device) - device->HandleCommand(command); - } + // find the initiator + CCECBusDevice *device = m_busDevices->At(command.initiator); + + if (device) + device->HandleCommand(command); } bool CCECProcessor::IsPresentDevice(cec_logical_address address) @@ -425,9 +547,15 @@ uint16_t CCECProcessor::GetDetectedPhysicalAddress(void) const return m_communication ? m_communication->GetPhysicalAddress() : CEC_INVALID_PHYSICAL_ADDRESS; } -bool CCECProcessor::SetAckMask(uint16_t iMask) +bool CCECProcessor::ClearLogicalAddresses(void) { - return m_communication ? m_communication->SetAckMask(iMask) : false; + cec_logical_addresses addresses; addresses.Clear(); + return SetLogicalAddresses(addresses); +} + +bool CCECProcessor::SetLogicalAddresses(const cec_logical_addresses &addresses) +{ + return m_communication ? m_communication->SetLogicalAddresses(addresses) : false; } bool CCECProcessor::StandbyDevices(const cec_logical_address initiator, const CECDEVICEVEC &devices) @@ -461,9 +589,11 @@ bool CCECProcessor::PowerOnDevice(const cec_logical_address initiator, cec_logic bool CCECProcessor::StartBootloader(const char *strPort /* = NULL */) { bool bReturn(false); + // open a connection if no connection has been opened if (!m_communication && strPort) { - IAdapterCommunication *comm = new CUSBCECAdapterCommunication(this, strPort); + CAdapterFactory factory(this->m_libcec); + IAdapterCommunication *comm = factory.GetInstance(strPort); CTimeout timeout(CEC_DEFAULT_CONNECT_TIMEOUT); int iConnectTry(0); while (timeout.TimeLeft() > 0 && (bReturn = comm->Open(timeout.TimeLeft() / CEC_CONNECT_TRIES, true)) == false) @@ -475,7 +605,7 @@ bool CCECProcessor::StartBootloader(const char *strPort /* = NULL */) if (comm->IsOpen()) { bReturn = comm->StartBootloader(); - delete comm; + DELETE_AND_NULL(comm); } return bReturn; } @@ -507,20 +637,22 @@ bool CCECProcessor::HandleReceiveFailed(cec_logical_address initiator) return !device || !device->HandleReceiveFailed(); } -bool CCECProcessor::SetStreamPath(uint16_t iPhysicalAddress) -{ - // stream path changes are sent by the TV - return GetTV()->GetHandler()->TransmitSetStreamPath(iPhysicalAddress); -} - bool CCECProcessor::CanPersistConfiguration(void) { return m_communication ? m_communication->GetFirmwareVersion() >= 2 : false; } -bool CCECProcessor::PersistConfiguration(libcec_configuration *configuration) +bool CCECProcessor::PersistConfiguration(const libcec_configuration &configuration) { - return m_communication ? m_communication->PersistConfiguration(configuration) : false; + libcec_configuration persistConfiguration = configuration; + if (!CLibCEC::IsValidPhysicalAddress(configuration.iPhysicalAddress)) + { + CCECBusDevice *device = GetPrimaryDevice(); + if (device) + persistConfiguration.iPhysicalAddress = device->GetCurrentPhysicalAddress(); + } + + return m_communication ? m_communication->PersistConfiguration(persistConfiguration) : false; } void CCECProcessor::RescanActiveDevices(void) @@ -537,6 +669,9 @@ bool CCECProcessor::GetDeviceInformation(const char *strPort, libcec_configurati config->iFirmwareVersion = m_communication->GetFirmwareVersion(); config->iPhysicalAddress = m_communication->GetPhysicalAddress(); config->iFirmwareBuildDate = m_communication->GetFirmwareBuildDate(); + config->adapterType = m_communication->GetAdapterType(); + + Close(); return true; } @@ -574,42 +709,139 @@ CCECTuner *CCECProcessor::GetTuner(cec_logical_address address) const return CCECBusDevice::AsTuner(m_busDevices->At(address)); } -bool CCECProcessor::RegisterClient(CCECClient *client) +bool CCECProcessor::AllocateLogicalAddresses(CCECClient* client) { - if (!client) - return false; - libcec_configuration &configuration = *client->GetConfiguration(); - m_libcec->AddLog(CEC_LOG_NOTICE, "registering new CEC client - v%s", ToString((cec_client_version)configuration.clientVersion)); + // mark as unregistered client->SetRegistered(false); - client->SetInitialised(false); - uint16_t iPreviousMask(m_communication->GetAckMask()); + // unregister this client from the old addresses + CECDEVICEVEC devices; + m_busDevices->GetByLogicalAddresses(devices, configuration.logicalAddresses); + for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) + { + // remove client entry + CLockObject lock(m_mutex); + m_clients.erase((*it)->GetLogicalAddress()); + } // find logical addresses for this client - if (!client->FindLogicalAddresses()) + if (!client->AllocateLogicalAddresses()) { - SetAckMask(iPreviousMask); + m_libcec->AddLog(CEC_LOG_ERROR, "failed to find a free logical address for the client"); return false; } // register this client on the new addresses - CECDEVICEVEC devices; + devices.clear(); m_busDevices->GetByLogicalAddresses(devices, configuration.logicalAddresses); for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) { + // set the physical address of the device at this LA + if (CLibCEC::IsValidPhysicalAddress(configuration.iPhysicalAddress)) + (*it)->SetPhysicalAddress(configuration.iPhysicalAddress); + + // replace a previous client CLockObject lock(m_mutex); m_clients.erase((*it)->GetLogicalAddress()); - m_clients.insert(make_pair((*it)->GetLogicalAddress(), client)); - client->SetRegistered(true); + m_clients.insert(make_pair((*it)->GetLogicalAddress(), client)); + } + + // set the new ackmask + SetLogicalAddresses(GetLogicalAddresses()); + + // resume outgoing communication + m_bStallCommunication = false; + + return true; +} + +uint16_t CCECProcessor::GetPhysicalAddressFromEeprom(void) +{ + libcec_configuration config; config.Clear(); + if (m_communication) + m_communication->GetConfiguration(config); + return config.iPhysicalAddress; +} + +bool CCECProcessor::RegisterClient(CCECClient *client) +{ + if (!client) + return false; + + libcec_configuration &configuration = *client->GetConfiguration(); + + if (configuration.clientVersion < CEC_CLIENT_VERSION_2_0_0) + { + 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)); + return false; + } + + if (configuration.bMonitorOnly == 1) + return true; + + if (!CECInitialised()) + { + m_libcec->AddLog(CEC_LOG_ERROR, "failed to register a new CEC client: CEC processor is not initialised"); + return false; + } + + // unregister the client first if it's already been marked as registered + if (client->IsRegistered()) + UnregisterClient(client); + + // ensure that controlled mode is enabled + m_communication->SetControlledMode(true); + m_bMonitor = false; + + // source logical address for requests + cec_logical_address sourceAddress(CECDEVICE_UNREGISTERED); + if (!m_communication->SupportsSourceLogicalAddress(CECDEVICE_UNREGISTERED)) + { + if (m_communication->SupportsSourceLogicalAddress(CECDEVICE_FREEUSE)) + sourceAddress = CECDEVICE_FREEUSE; + else + { + m_libcec->AddLog(CEC_LOG_ERROR, "failed to register a new CEC client: both unregistered and free use are not supported by the device"); + return false; + } + } + + // ensure that we know the vendor id of the TV + CCECBusDevice *tv = GetTV(); + cec_vendor_id tvVendor(tv->GetVendorId(sourceAddress)); + + // wait until the handler is replaced, to avoid double registrations + if (tvVendor != CEC_VENDOR_UNKNOWN && + CCECCommandHandler::HasSpecificHandler(tvVendor)) + { + while (!tv->ReplaceHandler(false)) + CEvent::Sleep(5); + } + + // get the configuration from the client + m_libcec->AddLog(CEC_LOG_NOTICE, "registering new CEC client - v%s", ToString((cec_client_version)configuration.clientVersion)); + + // get the current ackmask, so we can restore it if polling fails + cec_logical_addresses previousMask = GetLogicalAddresses(); + + // mark as uninitialised + client->SetInitialised(false); + + // find logical addresses for this client + if (!AllocateLogicalAddresses(client)) + { + m_libcec->AddLog(CEC_LOG_ERROR, "failed to register the new CEC client - cannot allocate the requested device types"); + SetLogicalAddresses(previousMask); + return false; } // get the settings from the rom if (configuration.bGetSettingsFromROM == 1) { - libcec_configuration config; - m_communication->GetConfiguration(&config); + libcec_configuration config; config.Clear(); + m_communication->GetConfiguration(config); CLockObject lock(m_mutex); if (!config.deviceTypes.IsEmpty()) @@ -619,80 +851,105 @@ bool CCECProcessor::RegisterClient(CCECClient *client) snprintf(configuration.strDeviceName, 13, "%s", config.strDeviceName); } - // set the new ack mask - bool bReturn = SetAckMask(GetLogicalAddresses().AckMask()) && - client->Initialise(); - // set the firmware version and build date configuration.serverVersion = LIBCEC_VERSION_CURRENT; configuration.iFirmwareVersion = m_communication->GetFirmwareVersion(); configuration.iFirmwareBuildDate = m_communication->GetFirmwareBuildDate(); + configuration.adapterType = m_communication->GetAdapterType(); - CStdString strLog; - if (bReturn) - strLog = "CEC client registered."; - else - strLog = "failed to register the CEC client."; - strLog.AppendFormat(" libCEC version = %s, client version = %s, firmware version = %d", ToString((cec_server_version)configuration.serverVersion), ToString((cec_client_version)configuration.clientVersion), configuration.iFirmwareVersion); - if (configuration.iFirmwareBuildDate != CEC_FW_BUILD_UNKNOWN) - { - time_t buildTime = (time_t)configuration.iFirmwareBuildDate; - strLog.AppendFormat(", firmware build date: %s", asctime(gmtime(&buildTime))); - strLog = strLog.Left((int)strLog.length() - 1); // strip \n added by asctime - strLog.append(" +0000"); - } + // mark the client as registered + client->SetRegistered(true); - m_libcec->AddLog(bReturn ? CEC_LOG_NOTICE : CEC_LOG_ERROR, strLog); + sourceAddress = client->GetPrimaryLogicalAdddress(); - if (bReturn) - { - strLog = "Using logical address(es): "; - CECDEVICEVEC devices; - m_busDevices->GetByLogicalAddresses(devices, configuration.logicalAddresses); - for (CECDEVICEVEC::iterator it = devices.begin(); it != devices.end(); it++) - strLog.AppendFormat("%s (%X) ", (*it)->GetLogicalAddressName(), (*it)->GetLogicalAddress()); - m_libcec->AddLog(CEC_LOG_NOTICE, strLog); - } + // initialise the client + bool bReturn = client->OnRegister(); + + // log the new registration + CStdString strLog; + strLog.Format("%s: %s", bReturn ? "CEC client registered" : "failed to register the CEC client", client->GetConnectionInfo().c_str()); + m_libcec->AddLog(bReturn ? CEC_LOG_NOTICE : CEC_LOG_ERROR, strLog); // display a warning if the firmware can be upgraded - if (!m_communication->IsRunningLatestFirmware()) + if (bReturn && !IsRunningLatestFirmware()) { const char *strUpgradeMessage = "The firmware of this adapter can be upgraded. Please visit http://blog.pulse-eight.com/ for more information."; m_libcec->AddLog(CEC_LOG_WARNING, strUpgradeMessage); - client->Alert(CEC_ALERT_SERVICE_DEVICE, libcec_parameter(strUpgradeMessage)); - } - else - { - m_libcec->AddLog(CEC_LOG_DEBUG, "the adapter is using the latest (known) firmware version"); + libcec_parameter param; + param.paramData = (void*)strUpgradeMessage; param.paramType = CEC_PARAMETER_TYPE_STRING; + client->Alert(CEC_ALERT_SERVICE_DEVICE, param); } + // ensure that the command handler for the TV is initialised if (bReturn) { - /* get the vendor id from the TV, so we are using the correct handler */ - GetTV()->GetVendorId(configuration.logicalAddresses.primary); + CCECCommandHandler *handler = GetTV()->GetHandler(); + if (handler) + handler->InitHandler(); + GetTV()->MarkHandlerReady(); } + // report our OSD name to the TV, since some TVs don't request it + client->GetPrimaryDevice()->TransmitOSDName(CECDEVICE_TV, false); + + // request the power status of the TV + tv->RequestPowerStatus(sourceAddress, true, true); + return bReturn; } -void CCECProcessor::UnregisterClient(CCECClient *client) +bool CCECProcessor::UnregisterClient(CCECClient *client) { - CLockObject lock(m_mutex); - CECDEVICEVEC devices; - m_busDevices->GetByLogicalAddresses(devices, client->GetConfiguration()->logicalAddresses); - for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) + if (!client) + return false; + + if (client->IsRegistered()) + m_libcec->AddLog(CEC_LOG_NOTICE, "unregistering client: %s", client->GetConnectionInfo().c_str()); + + // notify the client that it will be unregistered + client->OnUnregister(); + + { + CLockObject lock(m_mutex); + // find all devices that match the LA's of this client + CECDEVICEVEC devices; + m_busDevices->GetByLogicalAddresses(devices, client->GetConfiguration()->logicalAddresses); + for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) + { + // find the client + map::iterator entry = m_clients.find((*it)->GetLogicalAddress()); + // unregister the client + if (entry != m_clients.end()) + m_clients.erase(entry); + + // reset the device status + (*it)->ResetDeviceStatus(true); + } + } + + // set the new ackmask + cec_logical_addresses addresses = GetLogicalAddresses(); + if (SetLogicalAddresses(addresses)) { - map::iterator entry = m_clients.find((*it)->GetLogicalAddress()); - if (entry != m_clients.end()) - m_clients.erase(entry); + // no more clients left, disable controlled mode + if (addresses.IsEmpty() && !m_bMonitor) + m_communication->SetControlledMode(false); + + return true; } + + return false; } void CCECProcessor::UnregisterClients(void) { + m_libcec->AddLog(CEC_LOG_DEBUG, "unregistering all CEC clients"); + + vector clients = m_libcec->GetClients(); + for (vector::iterator client = clients.begin(); client != clients.end(); client++) + UnregisterClient(*client); + CLockObject lock(m_mutex); - for (map::iterator client = m_clients.begin(); client != m_clients.end(); client++) - client->second->OnUnregister(); m_clients.clear(); } @@ -714,20 +971,22 @@ CCECClient *CCECProcessor::GetPrimaryClient(void) return NULL; } -CCECBusDevice *CCECProcessor::GetPrimaryDevice(void) const +CCECBusDevice *CCECProcessor::GetPrimaryDevice(void) { return m_busDevices->At(GetLogicalAddress()); } -cec_logical_address CCECProcessor::GetLogicalAddress(void) const +cec_logical_address CCECProcessor::GetLogicalAddress(void) { cec_logical_addresses addresses = GetLogicalAddresses(); return addresses.primary; } -cec_logical_addresses CCECProcessor::GetLogicalAddresses(void) const +cec_logical_addresses CCECProcessor::GetLogicalAddresses(void) { + CLockObject lock(m_mutex); cec_logical_addresses addresses; + addresses.Clear(); for (map::const_iterator client = m_clients.begin(); client != m_clients.end(); client++) addresses.Set(client->first); @@ -739,3 +998,68 @@ bool CCECProcessor::IsHandledByLibCEC(const cec_logical_address address) const CCECBusDevice *device = GetDevice(address); return device && device->IsHandledByLibCEC(); } + +bool CCECProcessor::IsRunningLatestFirmware(void) +{ + return m_communication && m_communication->IsOpen() ? + m_communication->IsRunningLatestFirmware() : + true; +} + +void CCECProcessor::SwitchMonitoring(bool bSwitchTo) +{ + { + CLockObject lock(m_mutex); + m_bMonitor = bSwitchTo; + } + if (bSwitchTo) + UnregisterClients(); +} + +void CCECProcessor::HandleLogicalAddressLost(cec_logical_address oldAddress) +{ + // stall outgoing messages until we know our new LA + m_bStallCommunication = true; + + m_libcec->AddLog(CEC_LOG_NOTICE, "logical address %x was taken by another device, allocating a new address", oldAddress); + CCECClient* client = GetClient(oldAddress); + if (!client) + client = GetPrimaryClient(); + if (client) + { + if (m_addrAllocator) + while (m_addrAllocator->IsRunning()) Sleep(5); + delete m_addrAllocator; + + m_addrAllocator = new CCECAllocateLogicalAddress(this, client); + m_addrAllocator->CreateThread(); + } +} + +void CCECProcessor::HandlePhysicalAddressChanged(uint16_t iNewAddress) +{ + m_libcec->AddLog(CEC_LOG_NOTICE, "physical address changed to %04x", iNewAddress); + CCECClient* client = GetPrimaryClient(); + if (client) + client->SetPhysicalAddress(iNewAddress); +} + +uint16_t CCECProcessor::GetAdapterVendorId(void) const +{ + return m_communication ? m_communication->GetAdapterVendorId() : 0; +} + +uint16_t CCECProcessor::GetAdapterProductId(void) const +{ + return m_communication ? m_communication->GetAdapterProductId() : 0; +} + +CCECAllocateLogicalAddress::CCECAllocateLogicalAddress(CCECProcessor* processor, CCECClient* client) : + m_processor(processor), + m_client(client) { } + +void* CCECAllocateLogicalAddress::Process(void) +{ + m_processor->AllocateLogicalAddresses(m_client); + return NULL; +}