X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Flib%2FCECProcessor.cpp;h=a768cf4ead4acf3337f28588f4988f6b3b56c3a8;hb=c0152c0940ee81c79150dbafafd6621f576c3ccb;hp=e9ae43e1e1a665460cc8cdd0435ebfe15158a429;hpb=eafa9d4684e6298481b82923799a750b26852da1;p=deb_libcec.git diff --git a/src/lib/CECProcessor.cpp b/src/lib/CECProcessor.cpp index e9ae43e..0714d2a 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 Pulse-Eight Limited. All rights reserved. + * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited. All rights reserved. * libCEC(R) is an original work, containing original code. * * libCEC(R) is a trademark of Pulse-Eight Limited. @@ -32,541 +32,794 @@ #include "CECProcessor.h" -#include "AdapterCommunication.h" +#include "adapter/USBCECAdapterCommunication.h" #include "devices/CECBusDevice.h" +#include "devices/CECAudioSystem.h" +#include "devices/CECPlaybackDevice.h" +#include "devices/CECRecordingDevice.h" +#include "devices/CECTuner.h" +#include "devices/CECTV.h" +#include "implementations/CECCommandHandler.h" #include "LibCEC.h" -#include "util/StdString.h" -#include "platform/timeutils.h" +#include "CECClient.h" +#include "platform/util/timeutils.h" using namespace CEC; using namespace std; +using namespace PLATFORM; -CCECProcessor::CCECProcessor(CLibCEC *controller, CAdapterCommunication *serComm, const char *strDeviceName, cec_logical_address iLogicalAddress /* = CECDEVICE_PLAYBACKDEVICE1 */, uint16_t iPhysicalAddress /* = CEC_DEFAULT_PHYSICAL_ADDRESS*/) : - m_iPhysicalAddress(iPhysicalAddress), - m_iLogicalAddress(iLogicalAddress), - m_strDeviceName(strDeviceName), - m_communication(serComm), - m_controller(controller), - m_bMonitor(false) +#define CEC_PROCESSOR_SIGNAL_WAIT_TIME 1000 + +#define ToString(x) m_libcec->ToString(x) + +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) { - for (unsigned int iPtr = 0; iPtr < 16; iPtr++) - m_busDevices[iPtr] = new CCECBusDevice(this, (cec_logical_address) iPtr, 0); + m_busDevices = new CCECDeviceMap(this); } CCECProcessor::~CCECProcessor(void) { - StopThread(); - m_communication = NULL; - m_controller = NULL; - for (unsigned int iPtr = 0; iPtr < 16; iPtr++) - delete m_busDevices[iPtr]; + Close(); + delete m_busDevices; } -bool CCECProcessor::Start(void) +bool CCECProcessor::Start(const char *strPort, uint16_t iBaudRate /* = CEC_SERIAL_DEFAULT_BAUDRATE */, uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */) { - if (!m_communication || !m_communication->IsOpen()) - { - m_controller->AddLog(CEC_LOG_ERROR, "connection is closed"); + CLockObject lock(m_mutex); + // open a connection + if (!OpenConnection(strPort, iBaudRate, iTimeoutMs)) return false; - } - if (!SetLogicalAddress(m_iLogicalAddress)) + // create the processor thread + if (!IsRunning()) { - m_controller->AddLog(CEC_LOG_ERROR, "could not set the logical address"); - return false; + if (CreateThread()) + m_libcec->AddLog(CEC_LOG_DEBUG, "processor thread started"); + else + { + m_libcec->AddLog(CEC_LOG_ERROR, "could not create a processor thread"); + return false; + } } - if (CreateThread()) - return true; - else - m_controller->AddLog(CEC_LOG_ERROR, "could not create a processor thread"); + // mark as initialised + SetCECInitialised(true); - return false; + return true; } -void *CCECProcessor::Process(void) +void CCECProcessor::Close(void) { - m_controller->AddLog(CEC_LOG_DEBUG, "processor thread started"); + // mark as uninitialised + SetCECInitialised(false); - cec_command command; - cec_adapter_message msg; + // stop the processor + StopThread(); - while (!IsStopped()) + // close the connection + if (m_communication) { - bool bParseFrame(false); - bool bError(false); - bool bTransmitSucceeded(false); - command.clear(); - msg.clear(); + delete m_communication; + m_communication = NULL; + } +} - { - CLockObject lock(&m_mutex); - if (m_communication->IsOpen() && m_communication->Read(msg, 50)) - ParseMessage(msg, &bError, &bTransmitSucceeded, &bParseFrame); +void CCECProcessor::ResetMembers(void) +{ + // close the connection + if (m_communication) + { + delete m_communication; + m_communication = NULL; + } - bParseFrame &= !IsStopped(); - if (bParseFrame) - command = m_currentframe; - } + // reset the other members to the initial state + m_bMonitor = false; + m_iPreviousAckMask = 0; + m_iStandardLineTimeout = 3; + m_iRetryLineTimeout = 3; + m_iLastTransmission = 0; + m_busDevices->ResetDeviceStatus(); +} - if (bParseFrame) - ParseCommand(command); +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); - m_controller->CheckKeypressTimeout(); + // ensure that a previous connection is closed + Close(); - for (unsigned int iDevicePtr = 0; iDevicePtr < 16; iDevicePtr++) - m_busDevices[iDevicePtr]->PollVendorId(); + // reset all member to the initial state + ResetMembers(); - if (!IsStopped()) - Sleep(5); + // check whether the Close() method deleted any previous connection + if (m_communication) + { + m_libcec->AddLog(CEC_LOG_ERROR, "previous connection could not be closed"); + return bReturn; } - return NULL; + // create a new connection + m_communication = new CUSBCECAdapterCommunication(this, strPort, iBaudRate); + + // open a new connection + unsigned iConnectTry(0); + while (timeout.TimeLeft() > 0 && (bReturn = m_communication->Open((timeout.TimeLeft() / CEC_CONNECT_TRIES), false, bStartListening)) == false) + { + m_libcec->AddLog(CEC_LOG_ERROR, "could not open a connection (try %d)", ++iConnectTry); + m_communication->Close(); + CEvent::Sleep(CEC_DEFAULT_CONNECT_RETRY_WAIT); + } + + m_libcec->AddLog(CEC_LOG_NOTICE, "connection opened"); + + return bReturn; } -bool CCECProcessor::PowerOnDevices(cec_logical_address address /* = CECDEVICE_TV */) +bool CCECProcessor::CECInitialised(void) { - if (!IsRunning()) - return false; + CLockObject lock(m_threadMutex); + return m_bInitialised; +} - CStdString strLog; - strLog.Format("<< powering on device with logical address %d", (int8_t)address); - m_controller->AddLog(CEC_LOG_DEBUG, strLog.c_str()); +void CCECProcessor::SetCECInitialised(bool bSetTo /* = true */) +{ + { + CLockObject lock(m_mutex); + m_bInitialised = bSetTo; + } + if (!bSetTo) + UnregisterClients(); +} - cec_command command; - cec_command::format(command, m_iLogicalAddress, address, CEC_OPCODE_IMAGE_VIEW_ON); +bool CCECProcessor::TryLogicalAddress(cec_logical_address address) +{ + // 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; - return Transmit(command); + // poll the LA if not + SetAckMask(0); + return device->TryLogicalAddress(); + } + + return false; } -bool CCECProcessor::StandbyDevices(cec_logical_address address /* = CECDEVICE_BROADCAST */) +void CCECProcessor::ReplaceHandlers(void) { - if (!IsRunning()) - return false; + if (!CECInitialised()) + return; - CStdString strLog; - strLog.Format("<< putting device with logical address %d in standby mode", (int8_t)address); - m_controller->AddLog(CEC_LOG_DEBUG, strLog.c_str()); + // check each device + for (CECDEVICEMAP::iterator it = m_busDevices->Begin(); it != m_busDevices->End(); it++) + it->second->ReplaceHandler(true); +} + +bool CCECProcessor::OnCommandReceived(const cec_command &command) +{ + return m_inBuffer.Push(command); +} + +void *CCECProcessor::Process(void) +{ + m_libcec->AddLog(CEC_LOG_DEBUG, "processor thread started"); cec_command command; - cec_command::format(command, m_iLogicalAddress, address, CEC_OPCODE_STANDBY); - return Transmit(command); + // 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)) + ProcessCommand(command); + + if (CECInitialised()) + { + // check clients for keypress timeouts + m_libcec->CheckKeypressTimeout(); + + // check if we need to replace handlers + ReplaceHandlers(); + } + } + + return NULL; } -bool CCECProcessor::SetActiveView(void) +bool CCECProcessor::ActivateSource(uint16_t iStreamPath) { - if (!IsRunning()) - return false; + bool bReturn(false); - m_controller->AddLog(CEC_LOG_DEBUG, "<< setting active view"); + // 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 + m_libcec->AddLog(CEC_LOG_DEBUG, "device with PA '%04x' not found", iStreamPath); - cec_command command; - cec_command::format(command, m_iLogicalAddress, CECDEVICE_BROADCAST, CEC_OPCODE_ACTIVE_SOURCE); - command.parameters.push_back((m_iPhysicalAddress >> 8) & 0xFF); - command.parameters.push_back(m_iPhysicalAddress & 0xFF); + return bReturn; +} - return Transmit(command); +void CCECProcessor::SetStandardLineTimeout(uint8_t iTimeout) +{ + CLockObject lock(m_mutex); + m_iStandardLineTimeout = iTimeout; } -bool CCECProcessor::SetInactiveView(void) +uint8_t CCECProcessor::GetStandardLineTimeout(void) { - if (!IsRunning()) - return false; + CLockObject lock(m_mutex); + return m_iStandardLineTimeout; +} - m_controller->AddLog(CEC_LOG_DEBUG, "<< setting inactive view"); +void CCECProcessor::SetRetryLineTimeout(uint8_t iTimeout) +{ + CLockObject lock(m_mutex); + m_iRetryLineTimeout = iTimeout; +} - cec_command command; - cec_command::format(command, m_iLogicalAddress, CECDEVICE_BROADCAST, CEC_OPCODE_INACTIVE_SOURCE); - command.parameters.push_back((m_iPhysicalAddress >> 8) & 0xFF); - command.parameters.push_back(m_iPhysicalAddress & 0xFF); +uint8_t CCECProcessor::GetRetryLineTimeout(void) +{ + CLockObject lock(m_mutex); + return m_iRetryLineTimeout; +} - return Transmit(command); +bool CCECProcessor::PhysicalAddressInUse(uint16_t iPhysicalAddress) +{ + CCECBusDevice *device = GetDeviceByPhysicalAddress(iPhysicalAddress); + return device != NULL; } void CCECProcessor::LogOutput(const cec_command &data) { CStdString strTx; - strTx.Format("<< %02x:%02x", ((uint8_t)data.initiator << 4) + (uint8_t)data.destination, (uint8_t)data.opcode); + // 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_controller->AddLog(CEC_LOG_TRAFFIC, strTx.c_str()); + + // and log it + m_libcec->AddLog(CEC_LOG_TRAFFIC, strTx.c_str()); } -bool CCECProcessor::Transmit(const cec_command &data, bool bWaitForAck /* = true */) +bool CCECProcessor::SwitchMonitoring(bool bEnable) { - LogOutput(data); + m_libcec->AddLog(CEC_LOG_NOTICE, "== %s monitoring mode ==", bEnable ? "enabling" : "disabling"); - cec_adapter_message output; - output.clear(); - CAdapterCommunication::FormatAdapterMessage(data, output); + { + CLockObject lock(m_mutex); + // switch to monitoring mode, which will stop processing of incoming messages + m_bMonitor = bEnable; + // and store the current ackmask + m_iPreviousAckMask = m_communication->GetAckMask(); + } - return TransmitFormatted(output, bWaitForAck); + // set the mask to 0 when enabling monitor mode + if (bEnable) + return SetAckMask(0); + // and restore the previous mask otherwise + else + return SetAckMask(m_iPreviousAckMask); } -bool CCECProcessor::SetLogicalAddress(cec_logical_address iLogicalAddress) +bool CCECProcessor::PollDevice(cec_logical_address iAddress) { - CStdString strLog; - strLog.Format("<< setting logical address to %1x", iLogicalAddress); - m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str()); + // try to find the primary device + CCECBusDevice *primary = GetPrimaryDevice(); + // poll the destination, with the primary as source + if (primary) + return primary->TransmitPoll(iAddress); + + // try to find the destination + CCECBusDevice *device = m_busDevices->At(iAddress); + // and poll the destination, with the same LA as source + if (device) + return device->TransmitPoll(iAddress); - m_iLogicalAddress = iLogicalAddress; - return m_communication && m_communication->SetAckMask(0x1 << (uint8_t)m_iLogicalAddress); + return false; } -bool CCECProcessor::SetPhysicalAddress(uint16_t iPhysicalAddress) +CCECBusDevice *CCECProcessor::GetDeviceByPhysicalAddress(uint16_t iPhysicalAddress, bool bSuppressUpdate /* = true */) { - CStdString strLog; - strLog.Format("<< setting physical address to %2x", iPhysicalAddress); - m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str()); - - m_iPhysicalAddress = iPhysicalAddress; - return SetActiveView(); + return m_busDevices ? + m_busDevices->GetDeviceByPhysicalAddress(iPhysicalAddress, bSuppressUpdate) : + NULL; } -bool CCECProcessor::SetOSDString(cec_logical_address iLogicalAddress, cec_display_control duration, const char *strMessage) +CCECBusDevice *CCECProcessor::GetDevice(cec_logical_address address) const { - CStdString strLog; - strLog.Format("<< display message '%s'", strMessage); - m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str()); + return m_busDevices ? + m_busDevices->At(address) : + NULL; +} - cec_command command; - cec_command::format(command, m_iLogicalAddress, iLogicalAddress, CEC_OPCODE_SET_OSD_STRING); - command.parameters.push_back((uint8_t)duration); +cec_logical_address CCECProcessor::GetActiveSource(bool bRequestActiveSource /* = true */) +{ + // get the device that is marked as active source from the device map + CCECBusDevice *activeSource = m_busDevices->GetActiveSource(); + if (activeSource) + return activeSource->GetLogicalAddress(); - for (unsigned int iPtr = 0; iPtr < strlen(strMessage); iPtr++) - command.parameters.push_back(strMessage[iPtr]); + if (bRequestActiveSource) + { + // request the active source from the bus + CCECBusDevice *primary = GetPrimaryDevice(); + if (primary) + { + primary->RequestActiveSource(); + return GetActiveSource(false); + } + } - return Transmit(command); + // unknown or none + return CECDEVICE_UNKNOWN; } -bool CCECProcessor::SwitchMonitoring(bool bEnable) +bool CCECProcessor::IsActiveSource(cec_logical_address iAddress) { - CStdString strLog; - strLog.Format("== %s monitoring mode ==", bEnable ? "enabling" : "disabling"); - m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str()); - - m_bMonitor = bEnable; - if (bEnable) - return m_communication && m_communication->SetAckMask(0); - else - return m_communication && m_communication->SetAckMask(0x1 << (uint8_t)m_iLogicalAddress); + CCECBusDevice *device = m_busDevices->At(iAddress); + return device && device->IsActiveSource(); } -bool CCECProcessor::TransmitFormatted(const cec_adapter_message &data, bool bWaitForAck /* = true */) +bool CCECProcessor::Transmit(const cec_command &data) { - CLockObject lock(&m_mutex); - if (!m_communication || !m_communication->Write(data)) - return false; + 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 (bWaitForAck) + LogOutput(data); + + // find the initiator device + CCECBusDevice *initiator = m_busDevices->At(data.initiator); + if (!initiator) { - uint64_t now = GetTimeMs(); - uint64_t target = now + 1000; - bool bError(false); - bool bGotAck(false); + m_libcec->AddLog(CEC_LOG_WARNING, "invalid initiator"); + return false; + } - while (!bGotAck && now < target) + // find the destination device, if it's not the broadcast address + if (data.destination != CECDEVICE_BROADCAST) + { + // check if the device is marked as handled by libCEC + CCECBusDevice *destination = m_busDevices->At(data.destination); + if (destination && destination->IsHandledByLibCEC()) { - bGotAck = WaitForAck(&bError, (uint32_t) (target - now)); - now = GetTimeMs(); - - if (bError && now < target) - { - m_controller->AddLog(CEC_LOG_ERROR, "retransmitting previous frame"); - if (!m_communication->Write(data)) - return false; - } + // 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; } } - return true; + { + CLockObject lock(m_mutex); + m_iLastTransmission = GetTimeMs(); + // set the number of tries + iMaxTries = initiator->GetHandler()->GetTransmitRetries() + 1; + } + + // and try to send the command + while (bRetry && ++iTries < iMaxTries) + { + if (initiator->IsUnsupportedFeature(data.opcode)) + return false; + + adapterState = !IsStopped() && m_communication && m_communication->IsOpen() ? + m_communication->Write(data, bRetry, iLineTimeout) : + ADAPTER_MESSAGE_STATE_ERROR; + iLineTimeout = m_iRetryLineTimeout; + } + + return adapterState == ADAPTER_MESSAGE_STATE_SENT_ACKED; } -void CCECProcessor::TransmitAbort(cec_logical_address address, cec_opcode opcode, ECecAbortReason reason /* = CEC_ABORT_REASON_UNRECOGNIZED_OPCODE */) +void CCECProcessor::TransmitAbort(cec_logical_address source, cec_logical_address destination, cec_opcode opcode, cec_abort_reason reason /* = CEC_ABORT_REASON_UNRECOGNIZED_OPCODE */) { - m_controller->AddLog(CEC_LOG_DEBUG, "<< transmitting abort message"); + m_libcec->AddLog(CEC_LOG_DEBUG, "<< transmitting abort message"); cec_command command; - cec_command::format(command, m_iLogicalAddress, address, CEC_OPCODE_FEATURE_ABORT); - command.parameters.push_back((uint8_t)opcode); - command.parameters.push_back((uint8_t)reason); + cec_command::Format(command, source, destination, CEC_OPCODE_FEATURE_ABORT); + command.parameters.PushBack((uint8_t)opcode); + command.parameters.PushBack((uint8_t)reason); Transmit(command); } -void CCECProcessor::ReportCECVersion(cec_logical_address address /* = CECDEVICE_TV */) +void CCECProcessor::ProcessCommand(const cec_command &command) { - m_controller->AddLog(CEC_LOG_NOTICE, "<< reporting CEC version as 1.3a"); + // log the 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()); - cec_command command; - cec_command::format(command, m_iLogicalAddress, address, CEC_OPCODE_CEC_VERSION); - command.parameters.push_back(CEC_VERSION_1_3A); + // if we're not in monitor mode + if (!m_bMonitor) + { + // find the initiator + CCECBusDevice *device = m_busDevices->At(command.initiator); + // and "handle" the command + if (device) + device->HandleCommand(command); + } +} - Transmit(command); +bool CCECProcessor::IsPresentDevice(cec_logical_address address) +{ + CCECBusDevice *device = m_busDevices->At(address); + return device && device->GetStatus() == CEC_DEVICE_STATUS_PRESENT; } -void CCECProcessor::ReportPowerState(cec_logical_address address /*= CECDEVICE_TV */, bool bOn /* = true */) +bool CCECProcessor::IsPresentDeviceType(cec_device_type type) { - if (bOn) - m_controller->AddLog(CEC_LOG_NOTICE, "<< reporting \"On\" power status"); - else - m_controller->AddLog(CEC_LOG_NOTICE, "<< reporting \"Off\" power status"); + CECDEVICEVEC devices; + m_busDevices->GetByType(type, devices); + CCECDeviceMap::FilterActive(devices); + return !devices.empty(); +} - cec_command command; - cec_command::format(command, m_iLogicalAddress, address, CEC_OPCODE_REPORT_POWER_STATUS); - command.parameters.push_back(bOn ? (uint8_t) CEC_POWER_STATUS_ON : (uint8_t) CEC_POWER_STATUS_STANDBY); +uint16_t CCECProcessor::GetDetectedPhysicalAddress(void) const +{ + return m_communication ? m_communication->GetPhysicalAddress() : CEC_INVALID_PHYSICAL_ADDRESS; +} - Transmit(command); +bool CCECProcessor::SetAckMask(uint16_t iMask) +{ + return m_communication ? m_communication->SetAckMask(iMask) : false; +} + +bool CCECProcessor::StandbyDevices(const cec_logical_address initiator, const CECDEVICEVEC &devices) +{ + bool bReturn(true); + for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) + bReturn &= (*it)->Standby(initiator); + return bReturn; } -void CCECProcessor::ReportMenuState(cec_logical_address address /* = CECDEVICE_TV */, bool bActive /* = true */) +bool CCECProcessor::StandbyDevice(const cec_logical_address initiator, cec_logical_address address) { - if (bActive) - m_controller->AddLog(CEC_LOG_NOTICE, "<< reporting menu state as active"); + CCECBusDevice *device = m_busDevices->At(address); + return device ? device->Standby(initiator) : false; +} + +bool CCECProcessor::PowerOnDevices(const cec_logical_address initiator, const CECDEVICEVEC &devices) +{ + bool bReturn(true); + for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) + bReturn &= (*it)->PowerOn(initiator); + return bReturn; +} + +bool CCECProcessor::PowerOnDevice(const cec_logical_address initiator, cec_logical_address address) +{ + CCECBusDevice *device = m_busDevices->At(address); + return device ? device->PowerOn(initiator) : false; +} + +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); + CTimeout timeout(CEC_DEFAULT_CONNECT_TIMEOUT); + int iConnectTry(0); + while (timeout.TimeLeft() > 0 && (bReturn = comm->Open(timeout.TimeLeft() / CEC_CONNECT_TRIES, true)) == false) + { + m_libcec->AddLog(CEC_LOG_ERROR, "could not open a connection (try %d)", ++iConnectTry); + comm->Close(); + Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT); + } + if (comm->IsOpen()) + { + bReturn = comm->StartBootloader(); + delete comm; + } + return bReturn; + } else - m_controller->AddLog(CEC_LOG_NOTICE, "<< reporting menu state as inactive"); + { + m_communication->StartBootloader(); + Close(); + bReturn = true; + } - cec_command command; - cec_command::format(command, m_iLogicalAddress, address, CEC_OPCODE_MENU_STATUS); - command.parameters.push_back(bActive ? (uint8_t) CEC_MENU_STATE_ACTIVATED : (uint8_t) CEC_MENU_STATE_DEACTIVATED); + return bReturn; +} - Transmit(command); +bool CCECProcessor::PingAdapter(void) +{ + return m_communication->PingAdapter(); } -void CCECProcessor::ReportVendorID(cec_logical_address address /* = CECDEVICE_TV */) +void CCECProcessor::HandlePoll(cec_logical_address initiator, cec_logical_address destination) { - m_controller->AddLog(CEC_LOG_NOTICE, "<< vendor ID requested, feature abort"); - TransmitAbort(address, CEC_OPCODE_GIVE_DEVICE_VENDOR_ID); + CCECBusDevice *device = m_busDevices->At(destination); + if (device) + device->HandlePollFrom(initiator); } -void CCECProcessor::ReportOSDName(cec_logical_address address /* = CECDEVICE_TV */) +bool CCECProcessor::HandleReceiveFailed(cec_logical_address initiator) { - const char *osdname = m_strDeviceName.c_str(); - CStdString strLog; - strLog.Format("<< reporting OSD name as %s", osdname); - m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str()); + CCECBusDevice *device = m_busDevices->At(initiator); + return !device || !device->HandleReceiveFailed(); +} - cec_command command; - cec_command::format(command, m_iLogicalAddress, address, CEC_OPCODE_SET_OSD_NAME); - for (unsigned int iPtr = 0; iPtr < strlen(osdname); iPtr++) - command.parameters.push_back(osdname[iPtr]); +bool CCECProcessor::SetStreamPath(uint16_t iPhysicalAddress) +{ + // stream path changes are sent by the TV + return GetTV()->GetHandler()->TransmitSetStreamPath(iPhysicalAddress); +} - Transmit(command); +bool CCECProcessor::CanPersistConfiguration(void) +{ + return m_communication ? m_communication->GetFirmwareVersion() >= 2 : false; } -void CCECProcessor::ReportPhysicalAddress(void) +bool CCECProcessor::PersistConfiguration(const libcec_configuration &configuration) { - CStdString strLog; - strLog.Format("<< reporting physical address as %04x", m_iPhysicalAddress); - m_controller->AddLog(CEC_LOG_NOTICE, strLog.c_str()); + return m_communication ? m_communication->PersistConfiguration(configuration) : false; +} - cec_command command; - cec_command::format(command, m_iLogicalAddress, CECDEVICE_BROADCAST, CEC_OPCODE_REPORT_PHYSICAL_ADDRESS); - command.parameters.push_back((uint8_t) ((m_iPhysicalAddress >> 8) & 0xFF)); - command.parameters.push_back((uint8_t) (m_iPhysicalAddress & 0xFF)); - command.parameters.push_back((uint8_t) (CEC_DEVICE_TYPE_PLAYBACK_DEVICE)); +void CCECProcessor::RescanActiveDevices(void) +{ + for (CECDEVICEMAP::iterator it = m_busDevices->Begin(); it != m_busDevices->End(); it++) + it->second->GetStatus(true); +} - Transmit(command); +bool CCECProcessor::GetDeviceInformation(const char *strPort, libcec_configuration *config, uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */) +{ + if (!OpenConnection(strPort, CEC_SERIAL_DEFAULT_BAUDRATE, iTimeoutMs, false)) + return false; + + config->iFirmwareVersion = m_communication->GetFirmwareVersion(); + config->iPhysicalAddress = m_communication->GetPhysicalAddress(); + config->iFirmwareBuildDate = m_communication->GetFirmwareBuildDate(); + + return true; } -void CCECProcessor::BroadcastActiveSource(void) +bool CCECProcessor::TransmitPendingActiveSourceCommands(void) { - m_controller->AddLog(CEC_LOG_NOTICE, "<< broadcasting active source"); + bool bReturn(true); + for (CECDEVICEMAP::iterator it = m_busDevices->Begin(); it != m_busDevices->End(); it++) + bReturn &= it->second->TransmitPendingActiveSourceCommands(); + return bReturn; +} - cec_command command; - cec_command::format(command, m_iLogicalAddress, CECDEVICE_BROADCAST, CEC_OPCODE_ACTIVE_SOURCE); - command.parameters.push_back((uint8_t) ((m_iPhysicalAddress >> 8) & 0xFF)); - command.parameters.push_back((uint8_t) (m_iPhysicalAddress & 0xFF)); +CCECTV *CCECProcessor::GetTV(void) const +{ + return CCECBusDevice::AsTV(m_busDevices->At(CECDEVICE_TV)); +} - Transmit(command); +CCECAudioSystem *CCECProcessor::GetAudioSystem(void) const +{ + return CCECBusDevice::AsAudioSystem(m_busDevices->At(CECDEVICE_AUDIOSYSTEM)); +} + +CCECPlaybackDevice *CCECProcessor::GetPlaybackDevice(cec_logical_address address) const +{ + return CCECBusDevice::AsPlaybackDevice(m_busDevices->At(address)); +} + +CCECRecordingDevice *CCECProcessor::GetRecordingDevice(cec_logical_address address) const +{ + return CCECBusDevice::AsRecordingDevice(m_busDevices->At(address)); } -bool CCECProcessor::WaitForAck(bool *bError, uint32_t iTimeout /* = 1000 */) +CCECTuner *CCECProcessor::GetTuner(cec_logical_address address) const { - bool bTransmitSucceeded = false, bEom = false; - *bError = false; + return CCECBusDevice::AsTuner(m_busDevices->At(address)); +} + +bool CCECProcessor::RegisterClient(CCECClient *client) +{ + if (!client) + return false; - int64_t iNow = GetTimeMs(); - int64_t iTargetTime = iNow + (uint64_t) iTimeout; + // unregister the client first if it's already been marked as registered + if (client->IsRegistered()) + UnregisterClient(client); - while (!bTransmitSucceeded && !*bError && (iTimeout == 0 || iNow < iTargetTime)) + // get the configuration from the client + 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 uninitialised and unregistered + client->SetRegistered(false); + client->SetInitialised(false); + + // get the current ackmask, so we can restore it if polling fails + uint16_t iPreviousMask(m_communication->GetAckMask()); + + // find logical addresses for this client + if (!client->AllocateLogicalAddresses()) { - cec_adapter_message msg; - msg.clear(); + m_libcec->AddLog(CEC_LOG_ERROR, "failed to register the new CEC client - cannot allocate the requested device types"); + SetAckMask(iPreviousMask); + return false; + } - if (!m_communication->Read(msg, iTimeout > 0 ? (int32_t)(iTargetTime - iNow) : 1000)) - { - iNow = GetTimeMs(); - continue; - } + // register this client on the new addresses + CECDEVICEVEC devices; + m_busDevices->GetByLogicalAddresses(devices, configuration.logicalAddresses); + for (CECDEVICEVEC::const_iterator it = devices.begin(); it != devices.end(); it++) + { + // replace a previous client + CLockObject lock(m_mutex); + m_clients.erase((*it)->GetLogicalAddress()); + m_clients.insert(make_pair((*it)->GetLogicalAddress(), client)); + } - ParseMessage(msg, bError, &bTransmitSucceeded, &bEom, false); - iNow = GetTimeMs(); + // get the settings from the rom + if (configuration.bGetSettingsFromROM == 1) + { + libcec_configuration config; + m_communication->GetConfiguration(config); + + CLockObject lock(m_mutex); + if (!config.deviceTypes.IsEmpty()) + configuration.deviceTypes = config.deviceTypes; + if (CLibCEC::IsValidPhysicalAddress(config.iPhysicalAddress)) + configuration.iPhysicalAddress = config.iPhysicalAddress; + snprintf(configuration.strDeviceName, 13, "%s", config.strDeviceName); } - return bTransmitSucceeded && !*bError; + // set the firmware version and build date + configuration.serverVersion = LIBCEC_VERSION_CURRENT; + configuration.iFirmwareVersion = m_communication->GetFirmwareVersion(); + configuration.iFirmwareBuildDate = m_communication->GetFirmwareBuildDate(); + + // mark the client as registered + client->SetRegistered(true); + + // set the new ack mask + bool bReturn = SetAckMask(GetLogicalAddresses().AckMask()) && + // and initialise the client + 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 (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); + libcec_parameter param; + param.paramData = (void*)strUpgradeMessage; param.paramType = CEC_PARAMETER_TYPE_STRING; + client->Alert(CEC_ALERT_SERVICE_DEVICE, param); + } + + return bReturn; } -void CCECProcessor::ParseMessage(cec_adapter_message &msg, bool *bError, bool *bTransmitSucceeded, bool *bEom, bool bProcessMessages /* = true */) +void CCECProcessor::UnregisterClient(CCECClient *client) { - *bError = false; - *bTransmitSucceeded = false; - *bEom = false; - - if (msg.empty()) + if (!client) return; - CStdString logStr; + m_libcec->AddLog(CEC_LOG_NOTICE, "unregistering client: %s", client->GetConnectionInfo().c_str()); + + // notify the client that it will be unregistered + client->OnUnregister(); - switch(msg.message()) { - case MSGCODE_NOTHING: - m_controller->AddLog(CEC_LOG_DEBUG, "MSGCODE_NOTHING"); - break; - case MSGCODE_TIMEOUT_ERROR: - case MSGCODE_HIGH_ERROR: - case MSGCODE_LOW_ERROR: - { - if (msg.message() == MSGCODE_TIMEOUT_ERROR) - logStr = "MSGCODE_TIMEOUT"; - else if (msg.message() == MSGCODE_HIGH_ERROR) - logStr = "MSGCODE_HIGH_ERROR"; - else - logStr = "MSGCODE_LOW_ERROR"; - - int iLine = (msg.size() >= 3) ? (msg[1] << 8) | (msg[2]) : 0; - uint32_t iTime = (msg.size() >= 7) ? (msg[3] << 24) | (msg[4] << 16) | (msg[5] << 8) | (msg[6]) : 0; - logStr.AppendFormat(" line:%i", iLine); - logStr.AppendFormat(" time:%u", iTime); - m_controller->AddLog(CEC_LOG_WARNING, logStr.c_str()); - *bError = true; - } - break; - case MSGCODE_FRAME_START: + 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++) { - if (bProcessMessages) - { - logStr = "MSGCODE_FRAME_START"; - m_currentframe.clear(); - if (msg.size() >= 2) - { - logStr.AppendFormat(" initiator:%u destination:%u ack:%s %s", msg.initiator(), msg.destination(), msg.ack() ? "high" : "low", msg.eom() ? "eom" : ""); - m_currentframe.initiator = msg.initiator(); - m_currentframe.destination = msg.destination(); - m_currentframe.ack = msg.ack(); - m_currentframe.eom = msg.eom(); - } - m_controller->AddLog(CEC_LOG_DEBUG, logStr.c_str()); - } - else - { - m_frameBuffer.Push(msg); - } + // 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(); } - break; - case MSGCODE_FRAME_DATA: - { - if (bProcessMessages) - { - logStr = "MSGCODE_FRAME_DATA"; - if (msg.size() >= 2) - { - uint8_t iData = msg[1]; - logStr.AppendFormat(" %02x", iData); - m_currentframe.push_back(iData); - m_currentframe.eom = msg.eom(); - } - m_controller->AddLog(CEC_LOG_DEBUG, logStr.c_str()); - } - else - { - m_frameBuffer.Push(msg); - } - - *bEom = msg.eom(); - } - break; - case MSGCODE_COMMAND_ACCEPTED: - m_controller->AddLog(CEC_LOG_DEBUG, "MSGCODE_COMMAND_ACCEPTED"); - break; - case MSGCODE_TRANSMIT_SUCCEEDED: - m_controller->AddLog(CEC_LOG_DEBUG, "MSGCODE_TRANSMIT_SUCCEEDED"); - *bTransmitSucceeded = true; - break; - case MSGCODE_RECEIVE_FAILED: - m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_RECEIVE_FAILED"); - *bError = true; - break; - case MSGCODE_COMMAND_REJECTED: - m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_COMMAND_REJECTED"); - *bError = true; - break; - case MSGCODE_TRANSMIT_FAILED_LINE: - m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_LINE"); - *bError = true; - break; - case MSGCODE_TRANSMIT_FAILED_ACK: - m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_ACK"); - *bError = true; - break; - case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA: - m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA"); - *bError = true; - break; - case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE: - m_controller->AddLog(CEC_LOG_WARNING, "MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE"); - *bError = true; - break; - default: - break; } + + // set the new ackmask + SetAckMask(GetLogicalAddresses().AckMask()); } -void CCECProcessor::ParseVendorId(cec_logical_address device, const cec_datapacket &data) +void CCECProcessor::UnregisterClients(void) { - if (data.size < 3) - { - m_controller->AddLog(CEC_LOG_WARNING, "invalid vendor ID received"); - return; - } + m_libcec->AddLog(CEC_LOG_NOTICE, "unregistering all CEC clients"); - uint64_t iVendorId = ((uint64_t)data[0] << 3) + - ((uint64_t)data[1] << 2) + - (uint64_t)data[2]; + vector clients = m_libcec->GetClients(); + for (vector::iterator client = clients.begin(); client != clients.end(); client++) + UnregisterClient(*client); - m_busDevices[(uint8_t)device]->SetVendorId(iVendorId, data.size >= 4 ? data[3] : 0); + CLockObject lock(m_mutex); + m_clients.clear(); } -void CCECProcessor::ParseCommand(cec_command &command) +CCECClient *CCECProcessor::GetClient(const cec_logical_address address) { - CStdString dataStr; - dataStr.Format(">> %1x%1x:%02x", command.initiator, command.destination, command.opcode); - for (uint8_t iPtr = 0; iPtr < command.parameters.size; iPtr++) - dataStr.AppendFormat(":%02x", (unsigned int)command.parameters[iPtr]); - m_controller->AddLog(CEC_LOG_TRAFFIC, dataStr.c_str()); + CLockObject lock(m_mutex); + map::const_iterator client = m_clients.find(address); + if (client != m_clients.end()) + return client->second; + return NULL; +} - if (!m_bMonitor) - m_busDevices[(uint8_t)command.initiator]->HandleCommand(command); +CCECClient *CCECProcessor::GetPrimaryClient(void) +{ + CLockObject lock(m_mutex); + map::const_iterator client = m_clients.begin(); + if (client != m_clients.end()) + return client->second; + return NULL; } -void CCECProcessor::SetCurrentButton(cec_user_control_code iButtonCode) +CCECBusDevice *CCECProcessor::GetPrimaryDevice(void) { - m_controller->SetCurrentButton(iButtonCode); + return m_busDevices->At(GetLogicalAddress()); } -void CCECProcessor::AddCommand(const cec_command &command) +cec_logical_address CCECProcessor::GetLogicalAddress(void) { - m_controller->AddCommand(command); + cec_logical_addresses addresses = GetLogicalAddresses(); + return addresses.primary; +} + +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); + + return addresses; } -void CCECProcessor::AddKey(void) +bool CCECProcessor::IsHandledByLibCEC(const cec_logical_address address) const { - m_controller->AddKey(); + CCECBusDevice *device = GetDevice(address); + return device && device->IsHandledByLibCEC(); } -void CCECProcessor::AddLog(cec_log_level level, const CStdString &strMessage) +bool CCECProcessor::IsRunningLatestFirmware(void) { - m_controller->AddLog(level, strMessage); + return m_communication && m_communication->IsOpen() ? + m_communication->IsRunningLatestFirmware() : + true; }