From: Lars Op den Kamp Date: Thu, 12 Apr 2012 11:11:34 +0000 (+0200) Subject: cec: refactor USB adapter communication. less locks, shorter locks, added documentati... X-Git-Tag: upstream/2.2.0~1^2~31^2~5^2~3 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=a75e3a5a63546d6f7e670bc2a7a1931887a5d2a0;p=deb_libcec.git cec: refactor USB adapter communication. less locks, shorter locks, added documentation, lots of clean ups and no more incoming messages that are skipped --- diff --git a/src/lib/CECProcessor.cpp b/src/lib/CECProcessor.cpp index 9b44d3b..e957a37 100644 --- a/src/lib/CECProcessor.cpp +++ b/src/lib/CECProcessor.cpp @@ -187,7 +187,7 @@ bool CCECProcessor::OpenConnection(const char *strPort, uint16_t iBaudRate, uint /* open a new connection */ unsigned iConnectTry(0); - while (timeout.TimeLeft() > 0 && (bReturn = m_communication->Open(this, (timeout.TimeLeft() / CEC_CONNECT_TRIES), false, bStartListening)) == false) + while (timeout.TimeLeft() > 0 && (bReturn = m_communication->Open((timeout.TimeLeft() / CEC_CONNECT_TRIES), false, bStartListening)) == false) { CLibCEC::AddLog(CEC_LOG_ERROR, "could not open a connection (try %d)", ++iConnectTry); m_communication->Close(); @@ -514,7 +514,8 @@ bool CCECProcessor::SetActiveSource(uint16_t iStreamPath) { bool bReturn(false); - CCECBusDevice *device = GetDeviceByPhysicalAddress(iStreamPath); + // suppress polls when searching for a device + CCECBusDevice *device = GetDeviceByPhysicalAddress(iStreamPath, false, true); if (device) { device->SetActiveSource(); @@ -779,7 +780,7 @@ uint8_t CCECProcessor::MuteAudio(bool bSendRelease /* = true */) return status; } -CCECBusDevice *CCECProcessor::GetDeviceByPhysicalAddress(uint16_t iPhysicalAddress, bool bRefresh /* = false */) const +CCECBusDevice *CCECProcessor::GetDeviceByPhysicalAddress(uint16_t iPhysicalAddress, bool bRefresh /* = false */, bool bSuppressPoll /* = false */) const { if (m_busDevices[m_configuration.logicalAddresses.primary]->GetPhysicalAddress(false) == iPhysicalAddress) return m_busDevices[m_configuration.logicalAddresses.primary]; @@ -787,7 +788,7 @@ CCECBusDevice *CCECProcessor::GetDeviceByPhysicalAddress(uint16_t iPhysicalAddre CCECBusDevice *device = NULL; for (unsigned int iPtr = 0; iPtr < 16; iPtr++) { - if (m_busDevices[iPtr]->GetPhysicalAddress(bRefresh) == iPhysicalAddress) + if (m_busDevices[iPtr]->GetPhysicalAddress(bRefresh, bSuppressPoll) == iPhysicalAddress) { device = m_busDevices[iPtr]; break; @@ -1505,7 +1506,8 @@ bool CCECProcessor::PingAdapter(void) void CCECProcessor::HandlePoll(cec_logical_address initiator, cec_logical_address destination) { - m_busDevices[destination]->HandlePoll(initiator); + if (destination < CECDEVICE_BROADCAST) + m_busDevices[destination]->HandlePollFrom(initiator); } bool CCECProcessor::HandleReceiveFailed(cec_logical_address initiator) diff --git a/src/lib/CECProcessor.h b/src/lib/CECProcessor.h index 5956dc3..25b0c2a 100644 --- a/src/lib/CECProcessor.h +++ b/src/lib/CECProcessor.h @@ -109,7 +109,7 @@ namespace CEC virtual bool OnCommandReceived(const cec_command &command); virtual bool IsMonitoring(void) const { return m_bMonitor; } - virtual CCECBusDevice * GetDeviceByPhysicalAddress(uint16_t iPhysicalAddress, bool bRefresh = false) const; + virtual CCECBusDevice * GetDeviceByPhysicalAddress(uint16_t iPhysicalAddress, bool bRefresh = false, bool bSuppressPoll = false) const; virtual CCECBusDevice * GetDeviceByType(cec_device_type type) const; virtual CCECBusDevice * GetPrimaryDevice(void) const; virtual cec_version GetDeviceCecVersion(cec_logical_address iAddress); diff --git a/src/lib/LibCEC.cpp b/src/lib/LibCEC.cpp index 2e149c7..0eca6a2 100644 --- a/src/lib/LibCEC.cpp +++ b/src/lib/LibCEC.cpp @@ -587,6 +587,11 @@ const char *CLibCEC::ToString(const cec_server_version version) return m_cec->ToString(version); } +const char *CLibCEC::ToString(const cec_device_type type) +{ + return m_cec->ToString(type); +} + bool CLibCEC::GetCurrentConfiguration(libcec_configuration *configuration) { return m_cec->IsInitialised() && m_cec->GetCurrentConfiguration(configuration); @@ -712,4 +717,4 @@ bool CLibCEC::GetDeviceInformation(const char *strPort, libcec_configuration *co return false; return m_cec->GetDeviceInformation(strPort, config, iTimeoutMs); -} \ No newline at end of file +} diff --git a/src/lib/LibCEC.h b/src/lib/LibCEC.h index 2dca040..4697371 100644 --- a/src/lib/LibCEC.h +++ b/src/lib/LibCEC.h @@ -121,6 +121,7 @@ namespace CEC const char *ToString(const cec_vendor_id vendor); const char *ToString(const cec_client_version version); const char *ToString(const cec_server_version version); + const char *ToString(const cec_device_type type); static cec_device_type GetType(cec_logical_address address); static uint16_t GetMaskForType(cec_logical_address address); diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 083775f..60d6d2d 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -11,8 +11,11 @@ pkgconfig_DATA = libcec.pc libcec_la_SOURCES = CECProcessor.cpp \ LibCEC.cpp \ LibCECC.cpp \ + adapter/USBCECAdapterCommands.cpp \ adapter/USBCECAdapterCommunication.cpp \ adapter/USBCECAdapterDetection.cpp \ + adapter/USBCECAdapterMessage.cpp \ + adapter/USBCECAdapterMessageQueue.cpp \ devices/CECAudioSystem.cpp \ devices/CECBusDevice.cpp \ devices/CECPlaybackDevice.cpp \ @@ -23,7 +26,7 @@ libcec_la_SOURCES = CECProcessor.cpp \ implementations/CECCommandHandler.cpp \ implementations/SLCommandHandler.cpp \ implementations/VLCommandHandler.cpp \ - implementations/RLCommandHandler.cpp \ + implementations/RLCommandHandler.cpp \ platform/posix/serialport.cpp libcec_la_LDFLAGS = @LIBS@ -version-info @VERSION@ diff --git a/src/lib/adapter/AdapterCommunication.h b/src/lib/adapter/AdapterCommunication.h index 98f55e6..160a0fd 100644 --- a/src/lib/adapter/AdapterCommunication.h +++ b/src/lib/adapter/AdapterCommunication.h @@ -32,20 +32,10 @@ */ #include "../platform/util/StdString.h" +#include "USBCECAdapterMessage.h" namespace CEC { - typedef enum cec_adapter_message_state - { - ADAPTER_MESSAGE_STATE_UNKNOWN = 0, /**< the initial state */ - ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT, /**< waiting in the send queue of the adapter, or timed out */ - ADAPTER_MESSAGE_STATE_SENT, /**< sent and waiting on an ACK */ - ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED, /**< sent, but failed to ACK */ - ADAPTER_MESSAGE_STATE_SENT_ACKED, /**< sent, and ACK received */ - ADAPTER_MESSAGE_STATE_INCOMING, /**< received from another device */ - ADAPTER_MESSAGE_STATE_ERROR /**< an error occured */ - } cec_adapter_message_state; - class IAdapterCommunicationCallback { public: @@ -58,23 +48,40 @@ namespace CEC * @return True when it was handled by this listener, false otherwise. */ virtual bool OnCommandReceived(const cec_command &command) = 0; + + /*! + * @brief Callback method for IAdapterCommunication, called when a poll was received. + * @param initiator The initiator that sent the poll. + * @param destination The destination of the poll message. + */ + virtual void HandlePoll(cec_logical_address initiator, cec_logical_address destination) = 0; + + /*! + * @brief Callback method for IAdapterCommunication, called when a receive failed message was received. + * @param initiator The initiator that sent the receive failed message. + * @return True when this is an error, false otherwise. + */ + virtual bool HandleReceiveFailed(cec_logical_address initiator) = 0; }; class IAdapterCommunication { public: - IAdapterCommunication(void) {} + /*! + * @param callback The callback struct. if set to NULL, the Read() method has to be used to read commands. if set, OnCommandReceived() will be called for each command that was received + */ + IAdapterCommunication(IAdapterCommunicationCallback *callback) : + m_callback(callback) {} virtual ~IAdapterCommunication(void) {} /*! * @brief Open a connection to the CEC adapter - * @param cb The callback struct. if set to NULL, the Read() method has to be used to read commands. if set, OnCommandReceived() will be called for each command that was received * @param iTimeoutMs Connection timeout in ms * @param bSkipChecks Skips all initial checks of the adapter, and starts the reader/writer threads directly after connecting. * @param bStartListening Start a listener thread when true. False to just open a connection, read the device info, and close the connection. * @return True when connected, false otherwise */ - virtual bool Open(IAdapterCommunicationCallback *cb, uint32_t iTimeoutMs = 10000, bool bSkipChecks = false, bool bStartListening = true) = 0; + virtual bool Open(uint32_t iTimeoutMs = 10000, bool bSkipChecks = false, bool bStartListening = true) = 0; /*! * @brief Close an open connection @@ -91,14 +98,6 @@ namespace CEC */ virtual CStdString GetError(void) const = 0; - /*! - * @brief Reads one cec_command from the adapter - * @param command The command that will be read (output) - * @param iTimeout The read timeout - * @return True when a command has been read, false otherwise. - */ - virtual bool Read(cec_command &command, uint32_t iTimeout) = 0; - /*! * @brief Write a cec_command to the adapter * @param data The command to write @@ -168,5 +167,8 @@ namespace CEC * @return The physical address, if the adapter supports this. 0 otherwise. */ virtual uint16_t GetPhysicalAddress(void) = 0; + + protected: + IAdapterCommunicationCallback *m_callback; }; }; diff --git a/src/lib/adapter/USBCECAdapterCommands.cpp b/src/lib/adapter/USBCECAdapterCommands.cpp new file mode 100644 index 0000000..38e1950 --- /dev/null +++ b/src/lib/adapter/USBCECAdapterCommands.cpp @@ -0,0 +1,413 @@ +/* + * 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 an original work, containing original code. + * + * libCEC(R) is a trademark of Pulse-Eight Limited. + * + * This program is dual-licensed; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * Alternatively, you can license this library under a commercial license, + * please contact Pulse-Eight Licensing for more information. + * + * For more information contact: + * Pulse-Eight Licensing + * http://www.pulse-eight.com/ + * http://www.pulse-eight.net/ + */ + +#include "USBCECAdapterCommands.h" +#include "../LibCEC.h" +#include "../CECProcessor.h" + +using namespace CEC; +using namespace PLATFORM; + +cec_datapacket CUSBCECAdapterCommands::RequestSetting(cec_adapter_messagecode msgCode) +{ + cec_datapacket retVal; + retVal.Clear(); + + CCECAdapterMessage params; + CCECAdapterMessage *message = m_comm->SendCommand(msgCode, params); + if (message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED) + { + retVal = message->response; + retVal.Shift(2); // shift out start and msgcode + retVal.size -= 1; // remove end + } + delete message; + return retVal; +} + +uint16_t CUSBCECAdapterCommands::RequestFirmwareVersion(void) +{ + m_iFirmwareVersion = CEC_FW_VERSION_UNKNOWN; + unsigned int iFwVersionTry(0); + + while (m_iFirmwareVersion == CEC_FW_VERSION_UNKNOWN && iFwVersionTry++ < 3) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting the firmware version"); + cec_datapacket response = RequestSetting(MSGCODE_FIRMWARE_VERSION); + if (response.size == 2) + m_iFirmwareVersion = (response[0] << 8 | response[1]); + else + { + CLibCEC::AddLog(CEC_LOG_WARNING, "the adapter did not respond with a correct firmware version (try %d)", iFwVersionTry); + CEvent::Sleep(500); + } + } + + if (m_iFirmwareVersion == CEC_FW_VERSION_UNKNOWN) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "defaulting to firmware version 1"); + m_iFirmwareVersion = 1; + } + + return m_iFirmwareVersion; +} + +bool CUSBCECAdapterCommands::RequestSettingAutoEnabled(bool &enabled) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting autonomous mode setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_AUTO_ENABLED); + if (response.size == 1) + { + enabled = response[0] == 1; + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingCECVersion(cec_version &version) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting CEC version setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_HDMI_VERSION); + if (response.size == 1) + { + version = (cec_version)response[0]; + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingDefaultLogicalAddress(cec_logical_address &address) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting default logical address setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS); + if (response.size == 1) + { + address = (cec_logical_address)response[0]; + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingDeviceType(cec_device_type &value) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting device type setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_DEVICE_TYPE); + if (response.size == 1) + { + value = (cec_device_type)response[0]; + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingLogicalAddressMask(uint16_t &iMask) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting logical address mask setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_LOGICAL_ADDRESS_MASK); + if (response.size == 2) + { + iMask = ((uint16_t)response[0] << 8) | ((uint16_t)response[1]); + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingOSDName(CStdString &strOSDName) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting OSD name setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_OSD_NAME); + if (response.size == 0) + return false; + + char buf[14]; + for (uint8_t iPtr = 0; iPtr < response.size && iPtr < 13; iPtr++) + buf[iPtr] = (char)response[iPtr]; + buf[response.size] = 0; + + strOSDName.Format("%s", buf); + return true; +} + +bool CUSBCECAdapterCommands::RequestSettingPhysicalAddress(uint16_t &iPhysicalAddress) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting physical address setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_PHYSICAL_ADDRESS); + if (response.size == 2) + { + iPhysicalAddress = ((uint16_t)response[0] << 8) | ((uint16_t)response[1]); + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::SetSettingAutoEnabled(bool enabled) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "turning autonomous mode %s", enabled ? "on" : "off"); + + CCECAdapterMessage params; + params.PushEscaped(enabled ? 1 : 0); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_AUTO_ENABLED, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingDeviceType(cec_device_type type) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the device type to %1X", (uint8_t)type); + + CCECAdapterMessage params; + params.PushEscaped((uint8_t)type); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_DEVICE_TYPE, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingDefaultLogicalAddress(cec_logical_address address) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the default logical address to %1X", address); + + CCECAdapterMessage params; + params.PushEscaped((uint8_t)address); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingLogicalAddressMask(uint16_t iMask) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the logical address mask to %2X", iMask); + + CCECAdapterMessage params; + params.PushEscaped(iMask >> 8); + params.PushEscaped((uint8_t)iMask); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_LOGICAL_ADDRESS_MASK, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingPhysicalAddress(uint16_t iPhysicalAddress) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the physical address to %04X", iPhysicalAddress); + + CCECAdapterMessage params; + params.PushEscaped(iPhysicalAddress >> 8); + params.PushEscaped((uint8_t)iPhysicalAddress); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_PHYSICAL_ADDRESS, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingCECVersion(cec_version version) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the CEC version to %s", CLibCEC::GetInstance()->ToString(version)); + + CCECAdapterMessage params; + params.PushEscaped((uint8_t)version); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_HDMI_VERSION, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingOSDName(const char *strOSDName) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the OSD name to %s", strOSDName); + + CCECAdapterMessage params; + for (size_t iPtr = 0; iPtr < strlen(strOSDName); iPtr++) + params.PushEscaped(strOSDName[iPtr]); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_OSD_NAME, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::WriteEEPROM(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "writing settings in the EEPROM"); + + CCECAdapterMessage params; + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_WRITE_EEPROM, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::PersistConfiguration(libcec_configuration *configuration) +{ + if (m_iFirmwareVersion < 2) + return false; + + bool bReturn(true); + bReturn &= SetSettingAutoEnabled(true); + bReturn &= SetSettingDeviceType(CLibCEC::GetType(configuration->logicalAddresses.primary)); + bReturn &= SetSettingDefaultLogicalAddress(configuration->logicalAddresses.primary); + bReturn &= SetSettingLogicalAddressMask(CLibCEC::GetMaskForType(configuration->logicalAddresses.primary)); + bReturn &= SetSettingPhysicalAddress(configuration->iPhysicalAddress); + bReturn &= SetSettingCECVersion(CEC_VERSION_1_3A); + bReturn &= SetSettingOSDName(configuration->strDeviceName); + if (bReturn) + bReturn = WriteEEPROM(); + return bReturn; +} + +bool CUSBCECAdapterCommands::GetConfiguration(libcec_configuration *configuration) +{ + configuration->iFirmwareVersion = m_iFirmwareVersion; + if (m_iFirmwareVersion < 2) + return false; + + bool bReturn(true); + cec_device_type type; + if (RequestSettingDeviceType(type)) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted device type setting %s", CLibCEC::GetInstance()->ToString(type)); + configuration->deviceTypes.Clear(); + configuration->deviceTypes.Add(type); + } + else + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted device type setting"); + bReturn = false; + } + + if (RequestSettingPhysicalAddress(configuration->iPhysicalAddress)) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted physical address setting %4x", configuration->iPhysicalAddress); + } + else + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted physical address setting"); + bReturn = false; + } + + CStdString strDeviceName; + if (RequestSettingOSDName(strDeviceName)) + { + snprintf(configuration->strDeviceName, 13, "%s", strDeviceName.c_str()); + CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted device name setting %s", configuration->strDeviceName); + } + else + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted device name setting"); + bReturn = false; + } + + // don't read the following settings: + // - auto enabled (always enabled) + // - default logical address (autodetected) + // - logical address mask (autodetected) + // - CEC version (1.3a) + + // TODO to be added to the firmware: + // - base device (4 bits) + // - HDMI port number (4 bits) + // - TV vendor id (12 bits) + // - wake devices (8 bits) + // - standby devices (8 bits) + // - use TV menu language (1 bit) + // - activate source (1 bit) + // - power off screensaver (1 bit) + // - power off on standby (1 bit) + // - send inactive source (1 bit) + return bReturn; +} + +bool CUSBCECAdapterCommands::PingAdapter(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "sending ping"); + + CCECAdapterMessage params; + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_PING, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetAckMask(uint16_t iMask) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting ackmask to %2x", iMask); + + CCECAdapterMessage params; + params.PushEscaped(iMask >> 8); + params.PushEscaped((uint8_t)iMask); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_ACK_MASK, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::StartBootloader(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "starting the bootloader"); + + CCECAdapterMessage params; + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_START_BOOTLOADER, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetLineTimeout(uint8_t iTimeout) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the line timeout to %d", iTimeout); + CCECAdapterMessage params; + params.PushEscaped(iTimeout); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_TRANSMIT_IDLETIME, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} + +bool CUSBCECAdapterCommands::SetControlledMode(bool controlled) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "turning controlled mode %s", controlled ? "on" : "off"); + + CCECAdapterMessage params; + params.PushEscaped(controlled ? 1 : 0); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_CONTROLLED, params); + bool bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + return bReturn; +} diff --git a/src/lib/adapter/USBCECAdapterCommands.h b/src/lib/adapter/USBCECAdapterCommands.h new file mode 100644 index 0000000..b5f12fb --- /dev/null +++ b/src/lib/adapter/USBCECAdapterCommands.h @@ -0,0 +1,220 @@ +#pragma once +/* + * 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 an original work, containing original code. + * + * libCEC(R) is a trademark of Pulse-Eight Limited. + * + * This program is dual-licensed; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * Alternatively, you can license this library under a commercial license, + * please contact Pulse-Eight Licensing for more information. + * + * For more information contact: + * Pulse-Eight Licensing + * http://www.pulse-eight.com/ + * http://www.pulse-eight.net/ + */ + +#include "USBCECAdapterCommunication.h" + +namespace CEC +{ + class CUSBCECAdapterCommands + { + public: + CUSBCECAdapterCommands(CUSBCECAdapterCommunication *comm) : + m_comm(comm), + m_iFirmwareVersion(CEC_FW_VERSION_UNKNOWN) {} + + /*! + * @brief Request the firmware version from the adapter. + * @return The firmware version, or 1 (default) if it couldn't be retrieved. + */ + uint16_t RequestFirmwareVersion(void); + + /*! + * @brief Request a setting value from the adapter. + * @param msgCode The setting to retrieve. + * @return The response from the adapter. + */ + cec_datapacket RequestSetting(cec_adapter_messagecode msgCode); + + /*! + * @brief Change the value of the "auto enabled" setting. + * @param enabled The new value. + * @return True when set, false otherwise. + */ + bool SetSettingAutoEnabled(bool enabled); + + /*! + * @brief Request the value of the "auto enabled" setting from the adapter. + * @param enabled The current value. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingAutoEnabled(bool &enabled); + + /*! + * @brief Change the value of the "device type" setting, used when the device is in autonomous mode. + * @param type The new value. + * @return True when set, false otherwise. + */ + bool SetSettingDeviceType(cec_device_type type); + + /*! + * @brief Request the value of the "device type" setting from the adapter. + * @param type The current value. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingDeviceType(cec_device_type &type); + + /*! + * @brief Change the value of the "default logical address" setting, used when the device is in autonomous mode. + * @param address The new value. + * @return True when set, false otherwise. + */ + bool SetSettingDefaultLogicalAddress(cec_logical_address address); + + /*! + * @brief Request the value of the "default logical address" setting from the adapter. + * @param address The current value. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingDefaultLogicalAddress(cec_logical_address &address); + + /*! + * @brief Change the value of the "logical address mask" setting, used when the device is in autonomous mode. + * @param iMask The new value. + * @return True when set, false otherwise. + */ + bool SetSettingLogicalAddressMask(uint16_t iMask); + + /*! + * @brief Request the value of the "logical address mask" setting from the adapter. + * @param iMask The current value. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingLogicalAddressMask(uint16_t &iMask); + + /*! + * @brief Change the value of the "physical address" setting, used when the device is in autonomous mode. + * @param iPhysicalAddress The new value. + * @return True when set, false otherwise. + */ + bool SetSettingPhysicalAddress(uint16_t iPhysicalAddress); + + /*! + * @brief Request the value of the "physical address" setting from the adapter. + * @param iPhysicalAddress The current value. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingPhysicalAddress(uint16_t &iPhysicalAddress); + + /*! + * @brief Change the value of the "CEC version" setting, used when the device is in autonomous mode. + * @param version The new value. + * @return True when set, false otherwise. + */ + bool SetSettingCECVersion(cec_version version); + + /*! + * @brief Request the value of the "CEC version" setting from the adapter. + * @param version The current value. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingCECVersion(cec_version &version); + + /*! + * @brief Change the value of the "OSD name" setting, used when the device is in autonomous mode. + * @param strOSDName The new value. + * @return True when set, false otherwise. + */ + bool SetSettingOSDName(const char *strOSDName); + + /*! + * @brief Request the value of the "OSD name" setting from the adapter. + * @param strOSDName The current value. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingOSDName(CStdString &strOSDName); + + /*! + * @brief Persist the current settings in the EEPROM + * @return True when persisted, false otherwise. + */ + bool WriteEEPROM(void); + + /*! + * @return The firmware version of the adapter, retrieved when the connection is opened. + */ + uint16_t GetFirmwareVersion(void) const { return m_iFirmwareVersion; }; + + /*! + * @brief Persist the current configuration in the EEPROM. + * @attention Not all settings are persisted at this time. + * @param configuration The configuration to persist. + * @return True when persisted, false otherwise. + */ + bool PersistConfiguration(libcec_configuration *configuration); + + /*! + * @brief Get the persisted configuration from the EEPROM. + * @param configuration The persisted configuration. + * @return True when retrieved, false otherwise. + */ + bool GetConfiguration(libcec_configuration *configuration); + + /*! + * @brief Send a ping command to the adapter. + * @return True when acked by the adapter, false otherwise. + */ + bool PingAdapter(void); + + /*! + * @brief Change the ackmask of the adapter. + * @param iMask The new mask. + * @return True when the change was acked by the adapter, false otherwise. + */ + bool SetAckMask(uint16_t iMask); + + /*! + * @brief Put the adapter in bootloader mode. + * @attention The connection needs to be closed after this call, since the adapter will no longer be available. + * @return True when the command was sent, false otherwise. + */ + bool StartBootloader(void); + + /*! + * @brief Change the current CEC line timeout. + * @param iTimeout The new timeout. + * @return True when the change was acked by the adapter, false otherwise. + */ + bool SetLineTimeout(uint8_t iTimeout); + + /*! + * @brief Put the adapter in controlled or autonomous mode. + * @param controlled True to switch to controlled mode, false to switch to auto mode. + * @return True when acked by the controller, false otherwise. + */ + bool SetControlledMode(bool controlled); + + private: + CUSBCECAdapterCommunication *m_comm; /**< the communication handler */ + uint16_t m_iFirmwareVersion; /**< the firwmare version that was retrieved while opening the connection */ + }; +} diff --git a/src/lib/adapter/USBCECAdapterCommunication.cpp b/src/lib/adapter/USBCECAdapterCommunication.cpp index e9d4d93..a741943 100644 --- a/src/lib/adapter/USBCECAdapterCommunication.cpp +++ b/src/lib/adapter/USBCECAdapterCommunication.cpp @@ -31,6 +31,8 @@ */ #include "USBCECAdapterCommunication.h" +#include "USBCECAdapterCommands.h" +#include "USBCECAdapterMessageQueue.h" #include "../platform/sockets/serialport.h" #include "../platform/util/timeutils.h" #include "../LibCEC.h" @@ -42,217 +44,138 @@ using namespace PLATFORM; #define CEC_ADAPTER_PING_TIMEOUT 15000 -CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(CCECProcessor *processor, const char *strPort, uint16_t iBaudRate /* = 38400 */) : +CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate /* = 38400 */) : + IAdapterCommunication(callback), m_port(NULL), - m_processor(processor), - m_bHasData(false), m_iLineTimeout(0), - m_iFirmwareVersion(CEC_FW_VERSION_UNKNOWN), - m_lastDestination(CECDEVICE_UNKNOWN), - m_bNextIsEscaped(false), - m_bGotStart(false), + m_lastPollDestination(CECDEVICE_UNKNOWN), m_bInitialised(false), - m_pingThread(NULL) + m_pingThread(NULL), + m_commands(NULL), + m_adapterMessageQueue(NULL) { for (unsigned int iPtr = 0; iPtr < 15; iPtr++) m_bWaitingForAck[iPtr] = false; m_port = new CSerialPort(strPort, iBaudRate); } -bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */) +CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void) { - bool bReturn(false); - uint64_t iNow = GetTimeMs(); - uint64_t iTarget = iTimeoutMs > 0 ? iNow + iTimeoutMs : iNow + CEC_DEFAULT_TRANSMIT_WAIT; - - /* try to ping the adapter */ - bool bPinged(false); - unsigned iPingTry(0); - while (iNow < iTarget && (bPinged = PingAdapter()) == false) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry); - CEvent::Sleep(500); - iNow = GetTimeMs(); - } - - /* try to read the firmware version */ - m_iFirmwareVersion = CEC_FW_VERSION_UNKNOWN; - unsigned iFwVersionTry(0); - while (bPinged && iNow < iTarget && (m_iFirmwareVersion = GetFirmwareVersion()) == CEC_FW_VERSION_UNKNOWN && iFwVersionTry < 3) - { - CLibCEC::AddLog(CEC_LOG_WARNING, "the adapter did not respond with a correct firmware version (try %d)", ++iFwVersionTry); - CEvent::Sleep(500); - iNow = GetTimeMs(); - } - - if (m_iFirmwareVersion == CEC_FW_VERSION_UNKNOWN) - { - CLibCEC::AddLog(CEC_LOG_DEBUG, "defaulting to firmware version 1"); - m_iFirmwareVersion = 1; - } - - if (m_iFirmwareVersion >= 2) - { - /* try to set controlled mode */ - unsigned iControlledTry(0); - bool bControlled(false); - while (iNow < iTarget && (bControlled = SetControlledMode(true)) == false) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry); - CEvent::Sleep(500); - iNow = GetTimeMs(); - } - bReturn = bControlled; - } - else - bReturn = true; - - { - CLockObject lock(m_mutex); - m_bInitialised = bReturn; - } - - return bReturn; + Close(); + delete m_commands; + delete m_adapterMessageQueue; } -bool CUSBCECAdapterCommunication::Open(IAdapterCommunicationCallback *cb, uint32_t iTimeoutMs /* = 10000 */, bool bSkipChecks /* = false */, bool bStartListening /* = true */) +bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */, bool bSkipChecks /* = false */, bool bStartListening /* = true */) { - uint64_t iNow = GetTimeMs(); - uint64_t iTimeout = iNow + iTimeoutMs; - + bool bConnectionOpened(false); { CLockObject lock(m_mutex); + /* we need the port settings here */ if (!m_port) { CLibCEC::AddLog(CEC_LOG_ERROR, "port is NULL"); - return false; + return bConnectionOpened; } + /* return true when the port is already open */ if (IsOpen()) { - CLibCEC::AddLog(CEC_LOG_ERROR, "port is already open"); + CLibCEC::AddLog(CEC_LOG_WARNING, "port is already open"); return true; } - m_callback = cb; + /* adapter commands */ + if (!m_commands) + m_commands = new CUSBCECAdapterCommands(this); + + if (!m_adapterMessageQueue) + m_adapterMessageQueue = new CCECAdapterMessageQueue(this); + + /* try to open the connection */ CStdString strError; - bool bConnected(false); - while (!bConnected && iNow < iTimeout) + CTimeout timeout(iTimeoutMs); + while (!bConnectionOpened && timeout.TimeLeft() > 0) { - if ((bConnected = m_port->Open(iTimeout)) == false) + if ((bConnectionOpened = m_port->Open(timeout.TimeLeft())) == false) { strError.Format("error opening serial port '%s': %s", m_port->GetName().c_str(), m_port->GetError().c_str()); Sleep(250); - iNow = GetTimeMs(); } + /* and retry every 250ms until the timeout passed */ } - if (!bConnected) + /* return false when we couldn't connect */ + if (!bConnectionOpened) { CLibCEC::AddLog(CEC_LOG_ERROR, strError); return false; } CLibCEC::AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting"); - - if (!bSkipChecks) - { - //clear any input bytes - uint8_t buff[1024]; - ssize_t iBytesRead(0); - bool bGotMsgStart(false), bGotMsgEnd(false); - while ((iBytesRead = m_port->Read(buff, 1024, 100)) > 0 || (bGotMsgStart && !bGotMsgEnd)) - { - if (!bGotMsgStart) - CLibCEC::AddLog(CEC_LOG_DEBUG, "data received, clearing it"); - // if something was received, wait for MSGEND - for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++) - { - if (buff[iPtr] == MSGSTART) - bGotMsgStart = true; - else if (buff[iPtr] == MSGEND) - bGotMsgEnd = true; - } - Sleep(250); - } - } + ClearInputBytes(); } - if (!bSkipChecks && !CheckAdapter()) + if (!CreateThread()) { + bConnectionOpened = false; + CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a communication thread"); + } + else if (!bSkipChecks && !CheckAdapter()) + { + bConnectionOpened = false; CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks"); - delete m_port; - m_port = NULL; - return false; } else if (bStartListening) { + /* start a ping thread, that will ping the adapter every 15 seconds + if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */ m_pingThread = new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT); - if (CreateThread() && m_pingThread->CreateThread()) + if (m_pingThread->CreateThread()) { - CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread started"); - return true; + bConnectionOpened = true; } else { - delete m_port; - m_port = NULL; - CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a communication thread"); - return false; + bConnectionOpened = false; + CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a ping thread"); } } - else + + if (!bConnectionOpened || !bStartListening) + StopThread(0); + if (!bConnectionOpened) { delete m_port; m_port = NULL; } - return true; + return bConnectionOpened; } void CUSBCECAdapterCommunication::Close(void) { + /* set the ackmask to 0 before closing the connection */ + if (IsRunning()) + { + SetAckMask(0); + if (m_commands->GetFirmwareVersion() >= 2) + SetControlledMode(false); + } + + /* stop and delete the ping thread */ if (m_pingThread) m_pingThread->StopThread(0); delete m_pingThread; m_pingThread = NULL; - StopThread(0); -} -void *CUSBCECAdapterCommunication::Process(void) -{ - cec_command command; - command.Clear(); - bool bCommandReceived(false); - while (!IsStopped()) - { - { - CLockObject lock(m_mutex); - bCommandReceived = m_callback && Read(command, 50) && m_bInitialised; - } - - /* push the next command to the callback method if there is one */ - if (!IsStopped() && bCommandReceived) - m_callback->OnCommandReceived(command); - - Sleep(5); - } - - /* set the ackmask to 0 before closing the connection */ - SetAckMaskInternal(0, true); - - if (m_iFirmwareVersion >= 2) - SetControlledMode(false); - - if (m_port) - { - delete m_port; - m_port = NULL; - } + /* stop the reader thread */ + StopThread(0); - m_rcvCondition.Broadcast(); - return NULL; + /* close and delete the com port connection */ + delete m_port; + m_port = NULL; } cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, uint8_t iMaxTries, uint8_t iLineTimeout /* = 3 */, uint8_t iRetryLineTimeout /* = 3 */) @@ -261,28 +184,16 @@ cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command & if (!IsRunning()) return retVal; - CCECAdapterMessage *output = new CCECAdapterMessage(data); - - /* set the number of retries */ - if (data.opcode == CEC_OPCODE_NONE) //TODO - output->maxTries = 1; - else if (data.initiator != CECDEVICE_BROADCAST) - output->maxTries = iMaxTries; + CCECAdapterMessage *output = new CCECAdapterMessage(data, iMaxTries, iLineTimeout, iRetryLineTimeout); - output->lineTimeout = iLineTimeout; - output->retryTimeout = iRetryLineTimeout; - output->tries = 0; - - if (data.destination < 15) - { - CLockObject lock(m_mutex); - m_bWaitingForAck[data.destination] = true; - } + /* mark as waiting for an ack from the destination */ + MarkAsWaiting(data.destination); + /* send the message */ bool bRetry(true); while (bRetry && ++output->tries < output->maxTries) { - bRetry = (!Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0; + bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0; if (bRetry) Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT); } @@ -292,738 +203,286 @@ cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command & return retVal; } -bool CUSBCECAdapterCommunication::Write(CCECAdapterMessage *data) +void *CUSBCECAdapterCommunication::Process(void) { - data->state = ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT; - SendMessageToAdapter(data); + CCECAdapterMessage msg; + CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread started"); - if ((data->expectControllerAck && data->state != ADAPTER_MESSAGE_STATE_SENT_ACKED) || - (!data->expectControllerAck && data->state != ADAPTER_MESSAGE_STATE_SENT)) + while (!IsStopped()) { - CLibCEC::AddLog(CEC_LOG_DEBUG, "command was not %s", data->state == ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED ? "acked" : "sent"); - return false; + /* read from the serial port */ + if (!ReadFromDevice(50, 5)) + break; + + /* TODO sleep 5 ms so other threads can get a lock */ + Sleep(5); } - return true; + m_adapterMessageQueue->Clear(); + CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread ended"); + return NULL; } -bool CUSBCECAdapterCommunication::Read(cec_command &command, uint32_t iTimeout) +bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage &msg) { - if (!IsRunning()) - return false; + bool bIsError(msg.IsError()); + cec_adapter_messagecode messageCode(msg.Message()); + CLockObject lock(m_mutex); - CCECAdapterMessage msg; - if (Read(msg, iTimeout)) + if (messageCode == MSGCODE_FRAME_START && msg.IsACK()) { - if (ParseMessage(msg)) + m_lastPollDestination = msg.Destination(); + if (msg.Destination() < CECDEVICE_BROADCAST) { - command = m_currentframe; - m_currentframe.Clear(); - return true; + if (!m_bWaitingForAck[msg.Destination()] && !msg.IsEOM()) + { + if (m_callback) + m_callback->HandlePoll(msg.Initiator(), msg.Destination()); + } + else + m_bWaitingForAck[msg.Destination()] = false; } } - return false; -} - -bool CUSBCECAdapterCommunication::Read(CCECAdapterMessage &msg, uint32_t iTimeout, size_t iLen) -{ - CLockObject lock(m_mutex); - ReadFromDevice(iTimeout, iLen); - - msg.Clear(); - CCECAdapterMessage *buf(NULL); - - if (!m_inBuffer.Pop(buf)) - { - if (iTimeout == 0 || !m_rcvCondition.Wait(m_mutex, m_bHasData, iTimeout)) - return false; - m_inBuffer.Pop(buf); - m_bHasData = !m_inBuffer.IsEmpty(); - } - - if (buf) + else if (messageCode == MSGCODE_RECEIVE_FAILED) { - msg.packet = buf->packet; - msg.state = ADAPTER_MESSAGE_STATE_INCOMING; - delete buf; - return true; + /* hack to suppress warnings when an LG is polling */ + if (m_lastPollDestination != CECDEVICE_UNKNOWN) + bIsError = m_callback->HandleReceiveFailed(m_lastPollDestination); } - return false; -} -CStdString CUSBCECAdapterCommunication::GetError(void) const -{ - CStdString strError; - strError = m_port->GetError(); - return strError; + return bIsError; } -bool CUSBCECAdapterCommunication::StartBootloader(void) +void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest) { - bool bReturn(false); - if (!IsRunning()) - return bReturn; - - CLibCEC::AddLog(CEC_LOG_DEBUG, "starting the bootloader"); - - CCECAdapterMessage params; - return SendCommand(MSGCODE_START_BOOTLOADER, params, false); -} - -bool CUSBCECAdapterCommunication::PingAdapter(void) -{ - CLibCEC::AddLog(CEC_LOG_DEBUG, "sending ping"); - - CCECAdapterMessage params; - return SendCommand(MSGCODE_PING, params); -} - -bool CUSBCECAdapterCommunication::ParseMessage(const CCECAdapterMessage &msg) -{ - bool bEom(false); - bool bIsError(msg.IsError()); - - if (msg.IsEmpty()) - return bEom; - - CLockObject adapterLock(m_mutex); - switch(msg.Message()) + /* mark as waiting for an ack from the destination */ + if (dest < CECDEVICE_BROADCAST) { - case MSGCODE_FRAME_START: - { - m_currentframe.Clear(); - if (msg.Size() >= 2) - { - m_currentframe.initiator = msg.Initiator(); - m_currentframe.destination = msg.Destination(); - m_currentframe.ack = msg.IsACK(); - m_currentframe.eom = msg.IsEOM(); - } - if (m_currentframe.ack == 0x1) - { - m_lastDestination = m_currentframe.destination; - if (m_currentframe.destination < 15) - { - if (!m_bWaitingForAck[m_currentframe.destination]) - m_processor->HandlePoll(m_currentframe.initiator, m_currentframe.destination); - else - m_bWaitingForAck[m_currentframe.destination] = false; - } - } - } - break; - case MSGCODE_RECEIVE_FAILED: - { - m_currentframe.Clear(); - if (m_lastDestination != CECDEVICE_UNKNOWN) - bIsError = m_processor->HandleReceiveFailed(m_lastDestination); - } - break; - case MSGCODE_FRAME_DATA: - { - if (msg.Size() >= 2) - { - m_currentframe.PushBack(msg[1]); - m_currentframe.eom = msg.IsEOM(); - } - } - break; - default: - break; + CLockObject lock(m_mutex); + m_bWaitingForAck[dest] = true; } - - CLibCEC::AddLog(bIsError ? CEC_LOG_WARNING : CEC_LOG_DEBUG, msg.ToString()); - return msg.IsEOM(); } -uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void) +void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = 1000 */) { - uint16_t iReturn(m_iFirmwareVersion); + CTimeout timeout(iTimeout); + uint8_t buff[1024]; + ssize_t iBytesRead(0); + bool bGotMsgEnd(false); - if (iReturn == CEC_FW_VERSION_UNKNOWN) + while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd)) { - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting the firmware version"); - cec_datapacket response = GetSetting(MSGCODE_FIRMWARE_VERSION, 2); - if (response.size == 2) - { - m_iFirmwareVersion = (response[0] << 8 | response[1]); - iReturn = m_iFirmwareVersion; - CLibCEC::AddLog(CEC_LOG_DEBUG, "firmware version %d", m_iFirmwareVersion); - } + /* if something was received, wait for MSGEND */ + for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++) + bGotMsgEnd = buff[iPtr] == MSGEND; } - - return iReturn; } bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout) { bool bReturn(true); + bool bChanged(false); - if (m_iLineTimeout != iTimeout) + /* only send the command if the timeout changed */ { - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the line timeout to %d", iTimeout); - CCECAdapterMessage params; - params.PushEscaped(iTimeout); - bReturn = SendCommand(MSGCODE_TRANSMIT_IDLETIME, params); - if (bReturn) - m_iLineTimeout = iTimeout; + CLockObject lock(m_mutex); + bChanged = (m_iLineTimeout != iTimeout); + m_iLineTimeout = iTimeout; } - return bReturn; -} + if (bChanged) + bReturn = m_commands->SetLineTimeout(iTimeout); -bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask) -{ - return SetAckMaskInternal(iMask, IsRunning()); + return bReturn; } -bool CUSBCECAdapterCommunication::SetAckMaskInternal(uint16_t iMask, bool bWriteDirectly /* = false */) +bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message) { - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting ackmask to %2x", iMask); - - CCECAdapterMessage params; - params.PushEscaped(iMask >> 8); - params.PushEscaped((uint8_t)iMask); - return SendCommand(MSGCODE_SET_ACK_MASK, params, true, false, bWriteDirectly); -} + CLockObject adapterLock(m_mutex); + if (!m_port->IsOpen()) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "error writing command '%s' to the serial port: the connection is closed", CCECAdapterMessage::ToString(message->Message())); + message->state = ADAPTER_MESSAGE_STATE_ERROR; + return false; + } -bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration) -{ - if (m_iFirmwareVersion < 2) + /* write the message */ + if (m_port->Write(message->packet.data, message->Size()) != (ssize_t) message->Size()) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "error writing command '%s' to the serial port: %s", CCECAdapterMessage::ToString(message->Message()), m_port->GetError().c_str()); + message->state = ADAPTER_MESSAGE_STATE_ERROR; return false; + } - bool bReturn(true); - bReturn &= SetSettingAutoEnabled(true); - bReturn &= SetSettingDeviceType(CLibCEC::GetType(configuration->logicalAddresses.primary)); - bReturn &= SetSettingDefaultLogicalAddress(configuration->logicalAddresses.primary); - bReturn &= SetSettingLogicalAddressMask(CLibCEC::GetMaskForType(configuration->logicalAddresses.primary)); - bReturn &= SetSettingPhysicalAddress(configuration->iPhysicalAddress); - bReturn &= SetSettingCECVersion(CEC_VERSION_1_3A); - bReturn &= SetSettingOSDName(configuration->strDeviceName); - if (bReturn) - bReturn = WriteEEPROM(); - return bReturn; + CLibCEC::AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message())); + message->state = ADAPTER_MESSAGE_STATE_SENT; + return true; } -bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration) +bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */) { - configuration->iFirmwareVersion = m_iFirmwareVersion; - if (m_iFirmwareVersion < 2) - return false; - - bool bReturn(true); - cec_device_type type; - if (GetSettingDeviceType(type)) - { - CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted device type setting %s", m_processor->ToString(type)); - configuration->deviceTypes.Clear(); - configuration->deviceTypes.Add(type); - } - else - { - CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted device type setting"); - bReturn = false; - } + ssize_t iBytesRead(0); + uint8_t buff[256]; + if (iSize > 256) + iSize = 256; - if (GetSettingPhysicalAddress(configuration->iPhysicalAddress)) + /* read from the serial port */ { - CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted physical address setting %4x", configuration->iPhysicalAddress); - } - else - { - CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted physical address setting"); - bReturn = false; + CLockObject lock(m_mutex); + if (!m_port) + return false; + iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout); } - CStdString strDeviceName; - if (GetSettingOSDName(strDeviceName)) + if (iBytesRead < 0 || iBytesRead > 256) { - snprintf(configuration->strDeviceName, 13, "%s", strDeviceName.c_str()); - CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted device name setting %s", configuration->strDeviceName); + CLibCEC::AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str()); + StopThread(false); + return false; } - else + else if (iBytesRead > 0) { - CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted device name setting"); - bReturn = false; + /* add the data to the current frame */ + m_adapterMessageQueue->AddData(buff, iBytesRead); } - // don't read the following settings: - // - auto enabled (always enabled) - // - default logical address (autodetected) - // - logical address mask (autodetected) - // - CEC version (1.3a) - - // TODO to be added to the firmware: - // - base device (4 bits) - // - HDMI port number (4 bits) - // - TV vendor id (12 bits) - // - wake devices (8 bits) - // - standby devices (8 bits) - // - use TV menu language (1 bit) - // - activate source (1 bit) - // - power off screensaver (1 bit) - // - power off on standby (1 bit) - // - send inactive source (1 bit) - return bReturn; -} - -bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "turning controlled mode %s", controlled ? "on" : "off"); - - CCECAdapterMessage params; - params.PushEscaped(controlled ? 1 : 0); - return SendCommand(MSGCODE_SET_CONTROLLED, params); + return true; } -bool CUSBCECAdapterCommunication::SetSettingAutoEnabled(bool enabled) +CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage ¶ms, bool bIsRetry /* = false */) { - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "turning autonomous mode %s", enabled ? "on" : "off"); + if (!m_port || !m_port->IsOpen() || + !m_adapterMessageQueue) + return NULL; - CCECAdapterMessage params; - params.PushEscaped(enabled ? 1 : 0); - return SendCommand(MSGCODE_SET_AUTO_ENABLED, params); -} - -bool CUSBCECAdapterCommunication::GetSettingAutoEnabled(bool &enabled) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting autonomous mode setting"); + /* create the adapter message for this command */ + CCECAdapterMessage *output = new CCECAdapterMessage; + output->PushBack(MSGSTART); + output->PushEscaped((uint8_t)msgCode); + output->Append(params); + output->PushBack(MSGEND); - cec_datapacket response = GetSetting(MSGCODE_GET_AUTO_ENABLED, 1); - if (response.size == 1) + /* write the command */ + if (!m_adapterMessageQueue->Write(output)) { - enabled = response[0] == 1; - return true; + // timed out + return output; } - return false; -} - -bool CUSBCECAdapterCommunication::SetSettingDeviceType(cec_device_type type) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the device type to %1X", (uint8_t)type); - - CCECAdapterMessage params; - params.PushEscaped((uint8_t)type); - return SendCommand(MSGCODE_SET_DEVICE_TYPE, params); -} - -bool CUSBCECAdapterCommunication::GetSettingDeviceType(cec_device_type &value) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting device type setting"); - - cec_datapacket response = GetSetting(MSGCODE_GET_DEVICE_TYPE, 1); - if (response.size == 1) + else { - value = (cec_device_type)response[0]; - return true; + if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED) + { + /* if the controller reported that the command was rejected, and we didn't send the command + to set controlled mode, then the controller probably switched to auto mode. set controlled + mode and retry */ + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying"); + delete output; + if (SetControlledMode(true)) + return SendCommand(msgCode, params, true); + } } - return false; -} -bool CUSBCECAdapterCommunication::SetSettingDefaultLogicalAddress(cec_logical_address address) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the default logical address to %1X", address); - - CCECAdapterMessage params; - params.PushEscaped((uint8_t)address); - return SendCommand(MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS, params); + return output; } -bool CUSBCECAdapterCommunication::GetSettingDefaultLogicalAddress(cec_logical_address &address) +bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */) { - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting default logical address setting"); + bool bReturn(false); + CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT); - cec_datapacket response = GetSetting(MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS, 1); - if (response.size == 1) + /* try to ping the adapter */ + bool bPinged(false); + unsigned iPingTry(0); + while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false) { - address = (cec_logical_address)response[0]; - return true; + CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry); + CEvent::Sleep(500); } - return false; -} - -bool CUSBCECAdapterCommunication::SetSettingLogicalAddressMask(uint16_t iMask) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the logical address mask to %2X", iMask); - CCECAdapterMessage params; - params.PushEscaped(iMask >> 8); - params.PushEscaped((uint8_t)iMask); - return SendCommand(MSGCODE_SET_LOGICAL_ADDRESS_MASK, params); -} - -bool CUSBCECAdapterCommunication::GetSettingLogicalAddressMask(uint16_t &iMask) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting logical address mask setting"); - - cec_datapacket response = GetSetting(MSGCODE_GET_LOGICAL_ADDRESS_MASK, 2); - if (response.size == 2) + /* try to read the firmware version */ + if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2) { - iMask = ((uint16_t)response[0] << 8) | ((uint16_t)response[1]); - return true; + /* try to set controlled mode for v2+ firmwares */ + unsigned iControlledTry(0); + bool bControlled(false); + while (timeout.TimeLeft() > 0 && (bControlled = SetControlledMode(true)) == false) + { + CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry); + CEvent::Sleep(500); + } + bReturn = bControlled; } - return false; -} - -bool CUSBCECAdapterCommunication::SetSettingPhysicalAddress(uint16_t iPhysicalAddress) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the physical address to %04X", iPhysicalAddress); + else + bReturn = true; - CCECAdapterMessage params; - params.PushEscaped(iPhysicalAddress >> 8); - params.PushEscaped((uint8_t)iPhysicalAddress); - return SendCommand(MSGCODE_SET_PHYSICAL_ADDRESS, params); + SetInitialised(bReturn); + return bReturn; } -bool CUSBCECAdapterCommunication::GetSettingPhysicalAddress(uint16_t &iPhysicalAddress) +bool CUSBCECAdapterCommunication::IsOpen(void) { - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting physical address setting"); - - cec_datapacket response = GetSetting(MSGCODE_GET_PHYSICAL_ADDRESS, 2); - if (response.size == 2) - { - iPhysicalAddress = ((uint16_t)response[0] << 8) | ((uint16_t)response[1]); - return true; - } - return false; + /* thread is not being stopped, the port is open and the thread is running */ + return !IsStopped() && m_port->IsOpen() && IsRunning(); } -bool CUSBCECAdapterCommunication::SetSettingCECVersion(cec_version version) +CStdString CUSBCECAdapterCommunication::GetError(void) const { - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the CEC version to %s", CLibCEC::GetInstance()->ToString(version)); - - CCECAdapterMessage params; - params.PushEscaped((uint8_t)version); - return SendCommand(MSGCODE_SET_HDMI_VERSION, params); + return m_port->GetError(); } -bool CUSBCECAdapterCommunication::GetSettingCECVersion(cec_version &version) +void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */) { CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting CEC version setting"); - - cec_datapacket response = GetSetting(MSGCODE_GET_HDMI_VERSION, 1); - if (response.size == 1) - { - version = (cec_version)response[0]; - return true; - } - return false; + m_bInitialised = bSetTo; } -bool CUSBCECAdapterCommunication::SetSettingOSDName(const char *strOSDName) +bool CUSBCECAdapterCommunication::IsInitialised(void) { CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the OSD name to %s", strOSDName); - - CCECAdapterMessage params; - for (size_t iPtr = 0; iPtr < strlen(strOSDName); iPtr++) - params.PushEscaped(strOSDName[iPtr]); - return SendCommand(MSGCODE_SET_OSD_NAME, params); + return m_bInitialised; } -bool CUSBCECAdapterCommunication::GetSettingOSDName(CStdString &strOSDName) +bool CUSBCECAdapterCommunication::StartBootloader(void) { - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting OSD name setting"); - - cec_datapacket response = GetSetting(MSGCODE_GET_OSD_NAME, 13); - if (response.size == 0) + if (!IsRunning()) return false; - char buf[14]; - for (uint8_t iPtr = 0; iPtr < response.size && iPtr < 13; iPtr++) - buf[iPtr] = (char)response[iPtr]; - buf[response.size] = 0; - - strOSDName.Format("%s", buf); - return true; + return m_commands->StartBootloader(); } -bool CUSBCECAdapterCommunication::WriteEEPROM(void) -{ - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "writing settings in the EEPROM"); - - CCECAdapterMessage params; - return SendCommand(MSGCODE_WRITE_EEPROM, params); -} - -bool CUSBCECAdapterCommunication::IsOpen(void) +bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask) { - return !IsStopped() && m_port->IsOpen() && IsRunning(); + return m_commands->SetAckMask(iMask); } -bool CUSBCECAdapterCommunication::WaitForAck(CCECAdapterMessage &message) +bool CUSBCECAdapterCommunication::PingAdapter(void) { - bool bError(false); - bool bTransmitSucceeded(false); - uint8_t iPacketsLeft(message.isTransmission ? message.Size() / 4 : 1); - - int64_t iNow = GetTimeMs(); - int64_t iTargetTime = iNow + (message.transmit_timeout <= 5 ? CEC_DEFAULT_TRANSMIT_WAIT : message.transmit_timeout); - - while (!bTransmitSucceeded && !bError && iNow < iTargetTime) - { - CCECAdapterMessage msg; - if (!Read(msg, 50)) - { - iNow = GetTimeMs(); - continue; - } - - if (msg.Message() == MSGCODE_FRAME_START && msg.IsACK()) - { - if (msg.Initiator() < 15 && m_bWaitingForAck[msg.Initiator()]) - m_bWaitingForAck[msg.Initiator()] = false; - else if (msg.Initiator() < 15) - { - m_processor->HandlePoll(msg.Initiator(), msg.Destination()); - m_lastDestination = msg.Initiator(); - } - iNow = GetTimeMs(); - continue; - } - - if (msg.Message() == MSGCODE_RECEIVE_FAILED && - m_lastDestination != CECDEVICE_UNKNOWN && - m_processor->HandleReceiveFailed(m_lastDestination)) - { - iNow = GetTimeMs(); - continue; - } - - bError = msg.IsError(); - if (bError) - { - message.reply = msg.Message(); - CLibCEC::AddLog(CEC_LOG_DEBUG, msg.ToString()); - } - else - { - switch(msg.Message()) - { - case MSGCODE_COMMAND_ACCEPTED: - if (iPacketsLeft > 0) - iPacketsLeft--; - if (!message.isTransmission && iPacketsLeft == 0) - bTransmitSucceeded = true; - CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - waiting for %d more", msg.ToString().c_str(), iPacketsLeft); - break; - case MSGCODE_TRANSMIT_SUCCEEDED: - CLibCEC::AddLog(CEC_LOG_DEBUG, msg.ToString()); - bTransmitSucceeded = (iPacketsLeft == 0); - bError = !bTransmitSucceeded; - message.reply = MSGCODE_TRANSMIT_SUCCEEDED; - break; - default: - // ignore other data while waiting - break; - } - - iNow = GetTimeMs(); - } - } - - message.state = bTransmitSucceeded && !bError ? - ADAPTER_MESSAGE_STATE_SENT_ACKED : - ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED; - - return bTransmitSucceeded && !bError; + return m_commands->PingAdapter(); } -void CUSBCECAdapterCommunication::AddData(uint8_t *data, size_t iLen) +uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void) { - CLockObject lock(m_mutex); - for (size_t iPtr = 0; iPtr < iLen; iPtr++) - { - if (!m_bGotStart) - { - if (data[iPtr] == MSGSTART) - m_bGotStart = true; - } - else if (data[iPtr] == MSGSTART) //we found a msgstart before msgend, this is not right, remove - { - if (m_currentAdapterMessage.Size() > 0) - CLibCEC::AddLog(CEC_LOG_WARNING, "received MSGSTART before MSGEND, removing previous buffer contents"); - m_currentAdapterMessage.Clear(); - m_bGotStart = true; - } - else if (data[iPtr] == MSGEND) - { - CCECAdapterMessage *newMessage = new CCECAdapterMessage; - newMessage->packet = m_currentAdapterMessage.packet; - m_inBuffer.Push(newMessage); - m_currentAdapterMessage.Clear(); - m_bGotStart = false; - m_bNextIsEscaped = false; - m_bHasData = true; - m_rcvCondition.Broadcast(); - } - else if (m_bNextIsEscaped) - { - m_currentAdapterMessage.PushBack(data[iPtr] + (uint8_t)ESCOFFSET); - m_bNextIsEscaped = false; - } - else if (data[iPtr] == MSGESC) - { - m_bNextIsEscaped = true; - } - else - { - m_currentAdapterMessage.PushBack(data[iPtr]); - } - } + return m_commands->GetFirmwareVersion(); } -bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */) +bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration) { - ssize_t iBytesRead; - uint8_t buff[256]; - if (!m_port) - return false; - if (iSize > 256) - iSize = 256; - - CLockObject lock(m_mutex); - iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout); - if (iBytesRead < 0 || iBytesRead > 256) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str()); - StopThread(false); - return false; - } - else if (iBytesRead > 0) - { - AddData(buff, iBytesRead); - } - - return iBytesRead > 0; + return m_commands->PersistConfiguration(configuration); } -void CUSBCECAdapterCommunication::SendMessageToAdapter(CCECAdapterMessage *msg) +bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration) { - CLockObject adapterLock(m_mutex); - if (!m_port->IsOpen()) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "error writing to serial port: the connection is closed"); - msg->state = ADAPTER_MESSAGE_STATE_ERROR; - return; - } - - if (msg->isTransmission && (msg->Size() < 2 || msg->At(1) != MSGCODE_TRANSMIT_IDLETIME)) - { - if (msg->tries == 1) - SetLineTimeout(msg->lineTimeout); - else - SetLineTimeout(msg->retryTimeout); - } - - if (m_port->Write(msg->packet.data, msg->Size()) != (ssize_t) msg->Size()) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "error writing to serial port: %s", m_port->GetError().c_str()); - msg->state = ADAPTER_MESSAGE_STATE_ERROR; - } - else - { - CLibCEC::AddLog(CEC_LOG_DEBUG, "command sent"); - msg->state = ADAPTER_MESSAGE_STATE_SENT; - - if (msg->expectControllerAck) - { - if (!WaitForAck(*msg)) - CLibCEC::AddLog(CEC_LOG_DEBUG, "did not receive ack"); - } - } + return m_commands->GetConfiguration(configuration); } CStdString CUSBCECAdapterCommunication::GetPortName(void) { - CStdString strName; - strName = m_port->GetName(); - return strName; -} - -bool CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage ¶ms, bool bExpectAck /* = true */, bool bIsTransmission /* = false */, bool bSendDirectly /* = true */, bool bIsRetry /* = false */) -{ - CLockObject lock(m_mutex); - - CCECAdapterMessage *output = new CCECAdapterMessage; - - output->PushBack(MSGSTART); - output->PushEscaped((uint8_t)msgCode); - output->Append(params); - output->PushBack(MSGEND); - output->isTransmission = bIsTransmission; - output->expectControllerAck = bExpectAck; - - if (bSendDirectly) - SendMessageToAdapter(output); - else - Write(output); - - bool bWriteOk = output->state == (output->expectControllerAck ? ADAPTER_MESSAGE_STATE_SENT_ACKED : ADAPTER_MESSAGE_STATE_SENT); - cec_adapter_messagecode reply = output->reply; - delete output; - - if (!bWriteOk) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "'%s' failed", CCECAdapterMessage::ToString(msgCode)); - - if (!bIsRetry && reply == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED) - { - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying"); - if (SetControlledMode(true)) - return SendCommand(msgCode, params, bExpectAck, bIsTransmission, bSendDirectly, true); - } - return false; - } - - return true; + return m_port->GetName(); } -cec_datapacket CUSBCECAdapterCommunication::GetSetting(cec_adapter_messagecode msgCode, uint8_t iResponseLength) +bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled) { - cec_datapacket retVal; - retVal.Clear(); - - CCECAdapterMessage params; - if (!SendCommand(msgCode, params, false)) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "%s failed", CCECAdapterMessage::ToString(msgCode)); - return retVal; - } - - CCECAdapterMessage input; - if (Read(input, CEC_DEFAULT_TRANSMIT_WAIT, iResponseLength + 3 /* start + msgcode + iResponseLength + end */)) - { - if (input.Message() != msgCode) - CLibCEC::AddLog(CEC_LOG_ERROR, "invalid response to %s received (%s)", CCECAdapterMessage::ToString(msgCode), CCECAdapterMessage::ToString(input.Message())); - else - { - for (uint8_t iPtr = 1; iPtr < input.Size(); iPtr++) - retVal.PushBack(input[iPtr]); - } - } - else - { - CLibCEC::AddLog(CEC_LOG_ERROR, "no response to %s received", CCECAdapterMessage::ToString(msgCode)); - } - - return retVal; + return m_commands->SetControlledMode(controlled); } void *CAdapterPingThread::Process(void) @@ -1032,8 +491,33 @@ void *CAdapterPingThread::Process(void) { if (m_timeout.TimeLeft() == 0) { + /* reinit the timeout */ m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT); - m_com->PingAdapter(); + + /* send a ping to the adapter */ + bool bPinged(false); + int iFailedCounter(0); + while (!bPinged && iFailedCounter < 3) + { + if (!m_com->PingAdapter()) + { + /* sleep 1 second and retry */ + Sleep(1000); + ++iFailedCounter; + } + else + { + bPinged = true; + } + } + + if (iFailedCounter == 3) + { + /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */ + CLibCEC::AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection."); + m_com->StopThread(false); + break; + } } Sleep(500); diff --git a/src/lib/adapter/USBCECAdapterCommunication.h b/src/lib/adapter/USBCECAdapterCommunication.h index c41f081..9d125a7 100644 --- a/src/lib/adapter/USBCECAdapterCommunication.h +++ b/src/lib/adapter/USBCECAdapterCommunication.h @@ -46,88 +46,123 @@ namespace CEC { class CCECProcessor; class CAdapterPingThread; + class CUSBCECAdapterCommands; + class CCECAdapterMessageQueue; - class CUSBCECAdapterCommunication : public IAdapterCommunication, private PLATFORM::CThread + class CUSBCECAdapterCommunication : public IAdapterCommunication, public PLATFORM::CThread { - public: - CUSBCECAdapterCommunication(CCECProcessor *processor, const char *strPort, uint16_t iBaudRate = 38400); - virtual ~CUSBCECAdapterCommunication() {}; - - virtual bool Open(IAdapterCommunicationCallback *cb, uint32_t iTimeoutMs = 10000, bool bSkipChecks = false, bool bStartListening = true); - virtual void Close(void); - virtual bool IsOpen(void); - virtual CStdString GetError(void) const; + friend class CUSBCECAdapterCommands; + friend class CCECAdapterMessageQueue; - bool Read(cec_command &command, uint32_t iTimeout); + public: + /*! + * @brief Create a new USB-CEC communication handler. + * @param callback The callback to use for incoming CEC commands. + * @param strPort The name of the com port to use. + * @param iBaudRate The baudrate to use on the com port connection. + */ + CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate = 38400); + virtual ~CUSBCECAdapterCommunication(void); + + /** @name IAdapterCommunication implementation */ + ///{ + bool Open(uint32_t iTimeoutMs = 10000, bool bSkipChecks = false, bool bStartListening = true); + void Close(void); + bool IsOpen(void); + CStdString GetError(void) const; cec_adapter_message_state Write(const cec_command &data, uint8_t iMaxTries, uint8_t iLineTimeout = 3, uint8_t iRetryLineTimeout = 3); - virtual bool SetLineTimeout(uint8_t iTimeout); - virtual bool StartBootloader(void); - virtual bool SetAckMask(uint16_t iMask); - virtual bool PingAdapter(void); - virtual uint16_t GetFirmwareVersion(void); - virtual bool SetControlledMode(bool controlled); - virtual bool PersistConfiguration(libcec_configuration *configuration); - virtual bool GetConfiguration(libcec_configuration *configuration); - virtual CStdString GetPortName(void); - virtual uint16_t GetPhysicalAddress(void) { return 0; } + bool StartBootloader(void); + bool SetAckMask(uint16_t iMask); + bool PingAdapter(void); + uint16_t GetFirmwareVersion(void); + bool PersistConfiguration(libcec_configuration *configuration); + bool GetConfiguration(libcec_configuration *configuration); + CStdString GetPortName(void); + uint16_t GetPhysicalAddress(void) { return 0; } + bool SetControlledMode(bool controlled); + ///} void *Process(void); - private: - bool SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage ¶ms, bool bExpectAck = true, bool bIsTransmission = false, bool bSendDirectly = true, bool bIsRetry = false); - cec_datapacket GetSetting(cec_adapter_messagecode msgCode, uint8_t iResponseLength); - - bool SetSettingAutoEnabled(bool enabled); - bool GetSettingAutoEnabled(bool &enabled); - - bool SetSettingDeviceType(cec_device_type type); - bool GetSettingDeviceType(cec_device_type &type); - - bool SetSettingDefaultLogicalAddress(cec_logical_address address); - bool GetSettingDefaultLogicalAddress(cec_logical_address &address); - - bool SetSettingLogicalAddressMask(uint16_t iMask); - bool GetSettingLogicalAddressMask(uint16_t &iMask); - - bool SetSettingPhysicalAddress(uint16_t iPhysicalAddress); - bool GetSettingPhysicalAddress(uint16_t &iPhysicalAddress); - - bool SetSettingCECVersion(cec_version version); - bool GetSettingCECVersion(cec_version &version); - - bool SetSettingOSDName(const char *strOSDName); - bool GetSettingOSDName(CStdString &strOSDName); - - bool WriteEEPROM(void); - - bool SetAckMaskInternal(uint16_t iMask, bool bWriteDirectly = false); + private: + /*! + * @brief Clear all input bytes. + * @param iTimeout Timeout when anything was received. + */ + void ClearInputBytes(uint32_t iTimeout = 1000); + + /*! + * @brief Change the current CEC line timeout. + * @param iTimeout The new timeout. + * @return True when acked by the controller, false otherwise. + */ + bool SetLineTimeout(uint8_t iTimeout); + + /*! + * @brief Send a command to the controller and wait for an ack. + * @param msgCode The command to send. + * @param params The parameters to the command. + * @param bIsRetry True when this command is being retried, false otherwise. + * @return The message. Delete when done with it. + */ + CCECAdapterMessage *SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage ¶ms, bool bIsRetry = false); + + /*! + * @brief Change the "initialised" status. + * @param bSetTo The new value. + */ + void SetInitialised(bool bSetTo = true); + + /*! + * @return True when initialised, false otherwise. + */ + bool IsInitialised(void); + + /*! + * @brief Pings the adapter, checks the firmware version and sets controlled mode. + * @param iTimeoutMs The timeout after which this fails if no proper data was received. + * @return True when the checks passed, false otherwise. + */ bool CheckAdapter(uint32_t iTimeoutMs = 10000); - bool Write(CCECAdapterMessage *data); - bool Read(CCECAdapterMessage &msg, uint32_t iTimeout = 1000, size_t iLen = 64); - bool ParseMessage(const CCECAdapterMessage &msg); - void SendMessageToAdapter(CCECAdapterMessage *msg); - void AddData(uint8_t *data, size_t iLen); + + /*! + * @brief Handle a poll message inside the adapter message (checks if one is present). + * @param msg The adapter message to parse. + * @return True when the message resulted in a CEC error, false otherwise. + */ + bool HandlePoll(const CCECAdapterMessage &msg); + + /*! + * @brief Read data from the device. + * @param iTimeout The read timeout to use. + * @param iSize The maximum read size. + * @return True when something was read, false otherwise. + */ bool ReadFromDevice(uint32_t iTimeout, size_t iSize = 256); - bool WaitForAck(CCECAdapterMessage &message); - - PLATFORM::ISocket * m_port; - CCECProcessor * m_processor; - PLATFORM::SyncedBuffer m_inBuffer; - PLATFORM::CMutex m_mutex; - PLATFORM::CCondition m_rcvCondition; - volatile bool m_bHasData; - uint8_t m_iLineTimeout; - uint16_t m_iFirmwareVersion; - cec_command m_currentframe; - cec_logical_address m_lastDestination; - CCECAdapterMessage m_currentAdapterMessage; - bool m_bNextIsEscaped; - bool m_bGotStart; - IAdapterCommunicationCallback * m_callback; - bool m_bInitialised; - bool m_bWaitingForAck[15]; - CAdapterPingThread * m_pingThread; + + /*! + * @brief Writes a message to the serial port. + * @param message The message to write. + * @return True when written, false otherwise. + */ + bool WriteToDevice(CCECAdapterMessage *message); + + /*! + * @brief Called before sending a CEC command over the line, so we know we're expecting an ack. + * @param dest The destination of the CEC command. + */ + void MarkAsWaiting(const cec_logical_address dest); + + PLATFORM::ISocket * m_port; /**< the com port connection */ + PLATFORM::CMutex m_mutex; /**< mutex for changes in this class */ + uint8_t m_iLineTimeout; /**< the current line timeout on the CEC line */ + cec_logical_address m_lastPollDestination; /**< the destination of the last poll message that was received */ + bool m_bInitialised; /**< true when the connection is initialised, false otherwise */ + bool m_bWaitingForAck[15]; /**< array in which we store from which devices we're expecting acks */ + CAdapterPingThread * m_pingThread; /**< ping thread, that pings the adapter every 15 seconds */ + CUSBCECAdapterCommands * m_commands; /**< commands that can be sent to the adapter */ + CCECAdapterMessageQueue * m_adapterMessageQueue; /**< the incoming and outgoing message queue */ }; class CAdapterPingThread : public PLATFORM::CThread diff --git a/src/lib/adapter/USBCECAdapterMessage.cpp b/src/lib/adapter/USBCECAdapterMessage.cpp new file mode 100644 index 0000000..c23c21b --- /dev/null +++ b/src/lib/adapter/USBCECAdapterMessage.cpp @@ -0,0 +1,450 @@ +/* + * 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 an original work, containing original code. + * + * libCEC(R) is a trademark of Pulse-Eight Limited. + * + * This program is dual-licensed; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * Alternatively, you can license this library under a commercial license, + * please contact Pulse-Eight Licensing for more information. + * + * For more information contact: + * Pulse-Eight Licensing + * http://www.pulse-eight.com/ + * http://www.pulse-eight.net/ + */ + +#include "USBCECAdapterMessage.h" +#include "../LibCEC.h" + +using namespace CEC; +using namespace PLATFORM; + +CCECAdapterMessage::CCECAdapterMessage(void) +{ + Clear(); +} + +CCECAdapterMessage::CCECAdapterMessage(const cec_command &command, uint8_t iMaxTries /* = 1 */, uint8_t iLineTimeout /* = 3 */, uint8_t iRetryLineTimeout /* = 3 */) +{ + Clear(); + + //set ack polarity to high when transmitting to the broadcast address + //set ack polarity low when transmitting to any other address + PushBack(MSGSTART); + PushEscaped(MSGCODE_TRANSMIT_ACK_POLARITY); + if (command.destination == CECDEVICE_BROADCAST) + PushEscaped(CEC_TRUE); + else + PushEscaped(CEC_FALSE); + PushBack(MSGEND); + + // add source and destination + PushBack(MSGSTART); + PushEscaped(command.opcode_set == 0 ? (uint8_t)MSGCODE_TRANSMIT_EOM : (uint8_t)MSGCODE_TRANSMIT); + PushBack(((uint8_t)command.initiator << 4) + (uint8_t)command.destination); + PushBack(MSGEND); + + // add opcode + if (command.opcode_set == 1) + { + PushBack(MSGSTART); + PushEscaped(command.parameters.IsEmpty() ? (uint8_t)MSGCODE_TRANSMIT_EOM : (uint8_t)MSGCODE_TRANSMIT); + PushBack((uint8_t) command.opcode); + PushBack(MSGEND); + + // add parameters + for (int8_t iPtr = 0; iPtr < command.parameters.size; iPtr++) + { + PushBack(MSGSTART); + + if (iPtr == command.parameters.size - 1) + PushEscaped( MSGCODE_TRANSMIT_EOM); + else + PushEscaped(MSGCODE_TRANSMIT); + + PushEscaped(command.parameters[iPtr]); + + PushBack(MSGEND); + } + } + + // set timeout + transmit_timeout = command.transmit_timeout; + + /* set the number of retries */ + if (command.opcode == CEC_OPCODE_NONE) //TODO + maxTries = 1; + else if (command.initiator != CECDEVICE_BROADCAST) + maxTries = iMaxTries; + + lineTimeout = iLineTimeout; + retryTimeout = iRetryLineTimeout; + tries = 0; +} + +CStdString CCECAdapterMessage::ToString(void) const +{ + CStdString strMsg; + if (Size() == 0) + { + strMsg = "empty message"; + } + else + { + strMsg = ToString(Message()); + + switch (Message()) + { + case MSGCODE_TIMEOUT_ERROR: + case MSGCODE_HIGH_ERROR: + case MSGCODE_LOW_ERROR: + { + uint32_t iLine = (Size() >= 4) ? (At(2) << 8) | At(3) : 0; + uint32_t iTime = (Size() >= 8) ? (At(4) << 24) | (At(5) << 16) | (At(6) << 8) | At(7) : 0; + strMsg.AppendFormat(" line:%u", iLine); + strMsg.AppendFormat(" time:%u", iTime); + } + break; + case MSGCODE_FRAME_START: + if (Size() >= 3) + strMsg.AppendFormat(" initiator:%1x destination:%1x ack:%s %s", Initiator(), Destination(), IsACK() ? "high" : "low", IsEOM() ? "eom" : ""); + break; + case MSGCODE_FRAME_DATA: + if (Size() >= 3) + strMsg.AppendFormat(" %02x %s", At(2), IsEOM() ? "eom" : ""); + break; + default: + break; + } + } + + return strMsg; +} + +const char *CCECAdapterMessage::ToString(cec_adapter_messagecode msgCode) +{ + switch (msgCode) + { + case MSGCODE_NOTHING: + return "NOTHING"; + case MSGCODE_PING: + return "PING"; + case MSGCODE_TIMEOUT_ERROR: + return "TIMEOUT"; + case MSGCODE_HIGH_ERROR: + return "HIGH_ERROR"; + case MSGCODE_LOW_ERROR: + return "LOW_ERROR"; + case MSGCODE_FRAME_START: + return "FRAME_START"; + case MSGCODE_FRAME_DATA: + return "FRAME_DATA"; + case MSGCODE_RECEIVE_FAILED: + return "RECEIVE_FAILED"; + case MSGCODE_COMMAND_ACCEPTED: + return "COMMAND_ACCEPTED"; + case MSGCODE_COMMAND_REJECTED: + return "COMMAND_REJECTED"; + case MSGCODE_SET_ACK_MASK: + return "SET_ACK_MASK"; + case MSGCODE_TRANSMIT: + return "TRANSMIT"; + case MSGCODE_TRANSMIT_EOM: + return "TRANSMIT_EOM"; + case MSGCODE_TRANSMIT_IDLETIME: + return "TRANSMIT_IDLETIME"; + case MSGCODE_TRANSMIT_ACK_POLARITY: + return "TRANSMIT_ACK_POLARITY"; + case MSGCODE_TRANSMIT_LINE_TIMEOUT: + return "TRANSMIT_LINE_TIMEOUT"; + case MSGCODE_TRANSMIT_SUCCEEDED: + return "TRANSMIT_SUCCEEDED"; + case MSGCODE_TRANSMIT_FAILED_LINE: + return "TRANSMIT_FAILED_LINE"; + case MSGCODE_TRANSMIT_FAILED_ACK: + return "TRANSMIT_FAILED_ACK"; + case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA: + return "TRANSMIT_FAILED_TIMEOUT_DATA"; + case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE: + return "TRANSMIT_FAILED_TIMEOUT_LINE"; + case MSGCODE_FIRMWARE_VERSION: + return "FIRMWARE_VERSION"; + case MSGCODE_START_BOOTLOADER: + return "START_BOOTLOADER"; + case MSGCODE_FRAME_EOM: + return "FRAME_EOM"; + case MSGCODE_FRAME_ACK: + return "FRAME_ACK"; + case MSGCODE_SET_POWERSTATE: + return "SET_POWERSTATE"; + case MSGCODE_SET_CONTROLLED: + return "SET_CONTROLLED"; + case MSGCODE_GET_AUTO_ENABLED: + return "GET_AUTO_ENABLED"; + case MSGCODE_SET_AUTO_ENABLED: + return "SET_AUTO_ENABLED"; + case MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS: + return "GET_DEFAULT_LOGICAL_ADDRESS"; + case MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS: + return "SET_DEFAULT_LOGICAL_ADDRESS"; + case MSGCODE_GET_LOGICAL_ADDRESS_MASK: + return "GET_LOGICAL_ADDRESS_MASK"; + case MSGCODE_SET_LOGICAL_ADDRESS_MASK: + return "SET_LOGICAL_ADDRESS_MASK"; + case MSGCODE_GET_PHYSICAL_ADDRESS: + return "GET_PHYSICAL_ADDRESS"; + case MSGCODE_SET_PHYSICAL_ADDRESS: + return "SET_PHYSICAL_ADDRESS"; + case MSGCODE_GET_DEVICE_TYPE: + return "GET_DEVICE_TYPE"; + case MSGCODE_SET_DEVICE_TYPE: + return "SET_DEVICE_TYPE"; + case MSGCODE_GET_HDMI_VERSION: + return "GET_HDMI_VERSION"; + case MSGCODE_SET_HDMI_VERSION: + return "SET_HDMI_VERSION"; + case MSGCODE_GET_OSD_NAME: + return "GET_OSD_NAME"; + case MSGCODE_SET_OSD_NAME: + return "SET_OSD_NAME"; + case MSGCODE_WRITE_EEPROM: + return "WRITE_EEPROM"; + } + + return "unknown"; +} + +uint8_t CCECAdapterMessage::operator[](uint8_t pos) const +{ + return pos < packet.size ? packet[pos] : 0; +} + +uint8_t CCECAdapterMessage::At(uint8_t pos) const +{ + return pos < packet.size ? packet[pos] : 0; +} + +uint8_t CCECAdapterMessage::Size(void) const +{ + return packet.size; +} + +bool CCECAdapterMessage::IsEmpty(void) const +{ + return packet.IsEmpty(); +} + +void CCECAdapterMessage::Clear(void) +{ + state = ADAPTER_MESSAGE_STATE_UNKNOWN; + transmit_timeout = CEC_DEFAULT_TRANSMIT_TIMEOUT; + response.Clear(); + packet.Clear(); + maxTries = CEC_DEFAULT_TRANSMIT_RETRIES + 1; + tries = 0; + lineTimeout = 3; + retryTimeout = 3; + bNextByteIsEscaped = false; +} + +void CCECAdapterMessage::Shift(uint8_t iShiftBy) +{ + packet.Shift(iShiftBy); +} + +void CCECAdapterMessage::Append(CCECAdapterMessage &data) +{ + Append(data.packet); +} + +void CCECAdapterMessage::Append(cec_datapacket &data) +{ + for (uint8_t iPtr = 0; iPtr < data.size; iPtr++) + PushBack(data[iPtr]); +} + +void CCECAdapterMessage::PushBack(uint8_t byte) +{ + packet.PushBack(byte); +} + +void CCECAdapterMessage::PushEscaped(uint8_t byte) +{ + if (byte >= MSGESC) + { + PushBack(MSGESC); + PushBack(byte - ESCOFFSET); + } + else + { + PushBack(byte); + } +} + +bool CCECAdapterMessage::PushReceivedByte(uint8_t byte) +{ + if (byte == MSGSTART) + { + if (HasStartMessage()) + { + CLibCEC::AddLog(CEC_LOG_WARNING, "received MSGSTART before MSGEND, removing previous buffer contents"); + Clear(); + } + PushBack(byte); + } + else + { + if (bNextByteIsEscaped) + { + PushBack(byte + (uint8_t)ESCOFFSET); + bNextByteIsEscaped = false; + } + else if (byte == MSGESC) + bNextByteIsEscaped = true; + else + PushBack(byte); + } + + return byte == MSGEND; +} + +cec_adapter_messagecode CCECAdapterMessage::Message(void) const +{ + return packet.size >= 2 ? + (cec_adapter_messagecode) (packet.At(1) & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK)) : + MSGCODE_NOTHING; +} + +bool CCECAdapterMessage::IsTranmission(void) const +{ + cec_adapter_messagecode msgCode = Message(); + return msgCode == MSGCODE_FRAME_ACK || + msgCode == MSGCODE_FRAME_DATA || + msgCode == MSGCODE_FRAME_EOM || + msgCode == MSGCODE_FRAME_START || + msgCode == MSGCODE_HIGH_ERROR || + msgCode == MSGCODE_LOW_ERROR || + msgCode == MSGCODE_RECEIVE_FAILED || + msgCode == MSGCODE_TRANSMIT_ACK_POLARITY || + msgCode == MSGCODE_TRANSMIT_EOM || + msgCode == MSGCODE_TRANSMIT_FAILED_ACK || + msgCode == MSGCODE_TRANSMIT_FAILED_LINE || + msgCode == MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA || + msgCode == MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE || + msgCode == MSGCODE_TRANSMIT_LINE_TIMEOUT || + msgCode == MSGCODE_TRANSMIT_SUCCEEDED; +} + +bool CCECAdapterMessage::IsEOM(void) const +{ + return packet.size >= 2 ? + (packet.At(1) & MSGCODE_FRAME_EOM) != 0 : + false; +} + +bool CCECAdapterMessage::IsACK(void) const +{ + return packet.size >= 2 ? + (packet.At(1) & MSGCODE_FRAME_ACK) != 0 : + false; +} + +bool CCECAdapterMessage::IsError(void) const +{ + cec_adapter_messagecode code = Message(); + return (code == MSGCODE_HIGH_ERROR || + code == MSGCODE_LOW_ERROR || + code == MSGCODE_RECEIVE_FAILED || + code == MSGCODE_COMMAND_REJECTED || + code == MSGCODE_TRANSMIT_LINE_TIMEOUT || + code == MSGCODE_TRANSMIT_FAILED_LINE || + code == MSGCODE_TRANSMIT_FAILED_ACK || + code == MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA || + code == MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE); +} + +bool CCECAdapterMessage::NeedsRetry(void) const +{ + return Reply() == MSGCODE_NOTHING || + Reply() == MSGCODE_RECEIVE_FAILED || + Reply() == MSGCODE_TIMEOUT_ERROR || + Reply() == MSGCODE_TRANSMIT_FAILED_LINE || + Reply() == MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA || + Reply() == MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE || + Reply() == MSGCODE_TRANSMIT_LINE_TIMEOUT; +} + +cec_logical_address CCECAdapterMessage::Initiator(void) const +{ + return packet.size >= 3 ? + (cec_logical_address) (packet.At(2) >> 4) : + CECDEVICE_UNKNOWN; +} + +cec_logical_address CCECAdapterMessage::Destination(void) const +{ + return packet.size >= 3 ? + (cec_logical_address) (packet.At(2) & 0xF) : + CECDEVICE_UNKNOWN; +} + +bool CCECAdapterMessage::HasStartMessage(void) const +{ + return packet.size >= 1 && packet.At(0) == MSGSTART; +} + +bool CCECAdapterMessage::PushToCecCommand(cec_command &command) const +{ + // empty message + if (IsEmpty()) + return false; + + cec_adapter_messagecode msgCode = Message(); + if (msgCode == MSGCODE_FRAME_START) + { + command.Clear(); + if (Size() >= 3) + { + command.initiator = Initiator(); + command.destination = Destination(); + command.ack = IsACK(); + command.eom = IsEOM(); + } + return IsEOM() && !IsError(); + } + else if (msgCode == MSGCODE_FRAME_DATA) + { + if (Size() >= 3) + { + command.PushBack(At(2)); + command.eom = IsEOM(); + } + return IsEOM() && !IsError(); + } + + return false; +} + +cec_adapter_messagecode CCECAdapterMessage::Reply(void) const +{ + return response.size >= 2 ? + (cec_adapter_messagecode) (response.At(1) & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK)) : + MSGCODE_NOTHING; +} diff --git a/src/lib/adapter/USBCECAdapterMessage.h b/src/lib/adapter/USBCECAdapterMessage.h index 4ff920a..2b12260 100644 --- a/src/lib/adapter/USBCECAdapterMessage.h +++ b/src/lib/adapter/USBCECAdapterMessage.h @@ -32,344 +32,179 @@ */ #include "../platform/util/StdString.h" +#include "../platform/util/buffer.h" +#include "../platform/threads/mutex.h" +#include "../../../include/cectypes.h" namespace CEC { + typedef enum cec_adapter_message_state + { + ADAPTER_MESSAGE_STATE_UNKNOWN = 0, /**< the initial state */ + ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT, /**< waiting in the send queue of the adapter, or timed out */ + ADAPTER_MESSAGE_STATE_SENT, /**< sent and waiting on an ACK */ + ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED, /**< sent, but failed to ACK */ + ADAPTER_MESSAGE_STATE_SENT_ACKED, /**< sent, and ACK received */ + ADAPTER_MESSAGE_STATE_INCOMING, /**< received from another device */ + ADAPTER_MESSAGE_STATE_ERROR /**< an error occured */ + } cec_adapter_message_state; + class CCECAdapterMessage { public: - CCECAdapterMessage(void) : - event(false) - { - Clear(); - } - - CCECAdapterMessage(const cec_command &command) - { - Clear(); - - //set ack polarity to high when transmitting to the broadcast address - //set ack polarity low when transmitting to any other address - PushBack(MSGSTART); - PushEscaped(MSGCODE_TRANSMIT_ACK_POLARITY); - if (command.destination == CECDEVICE_BROADCAST) - PushEscaped(CEC_TRUE); - else - PushEscaped(CEC_FALSE); - PushBack(MSGEND); - - // add source and destination - PushBack(MSGSTART); - PushEscaped(command.opcode_set == 0 ? (uint8_t)MSGCODE_TRANSMIT_EOM : (uint8_t)MSGCODE_TRANSMIT); - PushBack(((uint8_t)command.initiator << 4) + (uint8_t)command.destination); - PushBack(MSGEND); - - // add opcode - if (command.opcode_set == 1) - { - PushBack(MSGSTART); - PushEscaped(command.parameters.IsEmpty() ? (uint8_t)MSGCODE_TRANSMIT_EOM : (uint8_t)MSGCODE_TRANSMIT); - PushBack((uint8_t) command.opcode); - PushBack(MSGEND); - - // add parameters - for (int8_t iPtr = 0; iPtr < command.parameters.size; iPtr++) - { - PushBack(MSGSTART); - - if (iPtr == command.parameters.size - 1) - PushEscaped( MSGCODE_TRANSMIT_EOM); - else - PushEscaped(MSGCODE_TRANSMIT); - - PushEscaped(command.parameters[iPtr]); - - PushBack(MSGEND); - } - } - - // set timeout - transmit_timeout = command.transmit_timeout; - //TODO - } - - CCECAdapterMessage &operator=(const CCECAdapterMessage &msg) - { - packet = msg.packet; - state = msg.state; - return *this; - } - - CStdString ToString(void) const - { - CStdString strMsg; - if (Size() == 0) - { - strMsg = "empty message"; - } - else - { - strMsg = ToString(Message()); - - switch (Message()) - { - case MSGCODE_TIMEOUT_ERROR: - case MSGCODE_HIGH_ERROR: - case MSGCODE_LOW_ERROR: - { - uint32_t iLine = (Size() >= 3) ? (At(1) << 8) | At(2) : 0; - uint32_t iTime = (Size() >= 7) ? (At(3) << 24) | (At(4) << 16) | (At(5) << 8) | At(6) : 0; - strMsg.AppendFormat(" line:%u", iLine); - strMsg.AppendFormat(" time:%u", iTime); - } - break; - case MSGCODE_FRAME_START: - if (Size() >= 2) - strMsg.AppendFormat(" initiator:%1x destination:%1x ack:%s %s", Initiator(), Destination(), IsACK() ? "high" : "low", IsEOM() ? "eom" : ""); - break; - case MSGCODE_FRAME_DATA: - if (Size() >= 2) - strMsg.AppendFormat(" %02x %s", At(1), IsEOM() ? "eom" : ""); - break; - default: - break; - } - } - - return strMsg; - } - - static const char *ToString(cec_adapter_messagecode msgCode) - { - switch (msgCode) - { - case MSGCODE_NOTHING: - return "NOTHING"; - case MSGCODE_PING: - return "PING"; - case MSGCODE_TIMEOUT_ERROR: - return "TIMEOUT"; - case MSGCODE_HIGH_ERROR: - return "HIGH_ERROR"; - case MSGCODE_LOW_ERROR: - return "LOW_ERROR"; - case MSGCODE_FRAME_START: - return "FRAME_START"; - case MSGCODE_FRAME_DATA: - return "FRAME_DATA"; - case MSGCODE_RECEIVE_FAILED: - return "RECEIVE_FAILED"; - case MSGCODE_COMMAND_ACCEPTED: - return "COMMAND_ACCEPTED"; - case MSGCODE_COMMAND_REJECTED: - return "COMMAND_REJECTED"; - case MSGCODE_SET_ACK_MASK: - return "SET_ACK_MASK"; - case MSGCODE_TRANSMIT: - return "TRANSMIT"; - case MSGCODE_TRANSMIT_EOM: - return "TRANSMIT_EOM"; - case MSGCODE_TRANSMIT_IDLETIME: - return "TRANSMIT_IDLETIME"; - case MSGCODE_TRANSMIT_ACK_POLARITY: - return "TRANSMIT_ACK_POLARITY"; - case MSGCODE_TRANSMIT_LINE_TIMEOUT: - return "TRANSMIT_LINE_TIMEOUT"; - case MSGCODE_TRANSMIT_SUCCEEDED: - return "TRANSMIT_SUCCEEDED"; - case MSGCODE_TRANSMIT_FAILED_LINE: - return "TRANSMIT_FAILED_LINE"; - case MSGCODE_TRANSMIT_FAILED_ACK: - return "TRANSMIT_FAILED_ACK"; - case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA: - return "TRANSMIT_FAILED_TIMEOUT_DATA"; - case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE: - return "TRANSMIT_FAILED_TIMEOUT_LINE"; - case MSGCODE_FIRMWARE_VERSION: - return "FIRMWARE_VERSION"; - case MSGCODE_START_BOOTLOADER: - return "START_BOOTLOADER"; - case MSGCODE_FRAME_EOM: - return "FRAME_EOM"; - case MSGCODE_FRAME_ACK: - return "FRAME_ACK"; - case MSGCODE_SET_POWERSTATE: - return "SET_POWERSTATE"; - case MSGCODE_SET_CONTROLLED: - return "SET_CONTROLLED"; - case MSGCODE_GET_AUTO_ENABLED: - return "GET_AUTO_ENABLED"; - case MSGCODE_SET_AUTO_ENABLED: - return "SET_AUTO_ENABLED"; - case MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS: - return "GET_DEFAULT_LOGICAL_ADDRESS"; - case MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS: - return "SET_DEFAULT_LOGICAL_ADDRESS"; - case MSGCODE_GET_LOGICAL_ADDRESS_MASK: - return "GET_LOGICAL_ADDRESS_MASK"; - case MSGCODE_SET_LOGICAL_ADDRESS_MASK: - return "SET_LOGICAL_ADDRESS_MASK"; - case MSGCODE_GET_PHYSICAL_ADDRESS: - return "GET_PHYSICAL_ADDRESS"; - case MSGCODE_SET_PHYSICAL_ADDRESS: - return "SET_PHYSICAL_ADDRESS"; - case MSGCODE_GET_DEVICE_TYPE: - return "GET_DEVICE_TYPE"; - case MSGCODE_SET_DEVICE_TYPE: - return "SET_DEVICE_TYPE"; - case MSGCODE_GET_HDMI_VERSION: - return "GET_HDMI_VERSION"; - case MSGCODE_SET_HDMI_VERSION: - return "SET_HDMI_VERSION"; - case MSGCODE_GET_OSD_NAME: - return "GET_OSD_NAME"; - case MSGCODE_SET_OSD_NAME: - return "SET_OSD_NAME"; - case MSGCODE_WRITE_EEPROM: - return "WRITE_EEPROM"; - } - - return "unknown"; - } - - uint8_t operator[](uint8_t pos) const - { - return packet[pos]; - } - - uint8_t At(uint8_t pos) const - { - return packet[pos]; - } - - uint8_t Size(void) const - { - return packet.size; - } - - bool IsEmpty(void) const - { - return packet.IsEmpty(); - } - - void Clear(void) - { - state = ADAPTER_MESSAGE_STATE_UNKNOWN; - transmit_timeout = CEC_DEFAULT_TRANSMIT_TIMEOUT; - packet.Clear(); - maxTries = CEC_DEFAULT_TRANSMIT_RETRIES + 1; - tries = 0; - reply = MSGCODE_NOTHING; - isTransmission = true; - expectControllerAck = true; - lineTimeout = 3; - retryTimeout = 3; - } - - void Shift(uint8_t iShiftBy) - { - packet.Shift(iShiftBy); - } - - void Append(CCECAdapterMessage &data) - { - Append(data.packet); - } - - void Append(cec_datapacket &data) - { - for (uint8_t iPtr = 0; iPtr < data.size; iPtr++) - PushBack(data[iPtr]); - } - - void PushBack(uint8_t add) - { - packet.PushBack(add); - } - - void PushEscaped(uint8_t byte) - { - if (byte >= MSGESC) - { - PushBack(MSGESC); - PushBack(byte - ESCOFFSET); - } - else - { - PushBack(byte); - } - } - - cec_adapter_messagecode Message(void) const - { - return packet.size >= 1 ? - (cec_adapter_messagecode) (packet.At(0) & ~(MSGCODE_FRAME_EOM | MSGCODE_FRAME_ACK)) : - MSGCODE_NOTHING; - } - - bool IsEOM(void) const - { - return packet.size >= 1 ? - (packet.At(0) & MSGCODE_FRAME_EOM) != 0 : - false; - } - - bool IsACK(void) const - { - return packet.size >= 1 ? - (packet.At(0) & MSGCODE_FRAME_ACK) != 0 : - false; - } - - bool IsError(void) const - { - cec_adapter_messagecode code = Message(); - return (code == MSGCODE_HIGH_ERROR || - code == MSGCODE_LOW_ERROR || - code == MSGCODE_RECEIVE_FAILED || - code == MSGCODE_COMMAND_REJECTED || - code == MSGCODE_TRANSMIT_LINE_TIMEOUT || - code == MSGCODE_TRANSMIT_FAILED_LINE || - code == MSGCODE_TRANSMIT_FAILED_ACK || - code == MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA || - code == MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE); - } - - bool NeedsRetry(void) const - { - return reply == MSGCODE_NOTHING || - reply == MSGCODE_RECEIVE_FAILED || - reply == MSGCODE_TIMEOUT_ERROR || - reply == MSGCODE_TRANSMIT_FAILED_LINE || - reply == MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA || - reply == MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE || - reply == MSGCODE_TRANSMIT_LINE_TIMEOUT; - } - - cec_logical_address Initiator(void) const - { - return packet.size >= 2 ? - (cec_logical_address) (packet.At(1) >> 4) : - CECDEVICE_UNKNOWN; - } - - cec_logical_address Destination(void) const - { - return packet.size >= 2 ? - (cec_logical_address) (packet.At(1) & 0xF) : - CECDEVICE_UNKNOWN; - } - - uint8_t maxTries; - uint8_t tries; - cec_adapter_messagecode reply; - cec_datapacket packet; - cec_adapter_message_state state; - int32_t transmit_timeout; - bool isTransmission; - bool expectControllerAck; - uint8_t lineTimeout; - uint8_t retryTimeout; - PLATFORM::CEvent event; + /*! + * @brief Create an empty message. + */ + CCECAdapterMessage(void); + + /*! + * @brief Create a message with a command that is to be transmitted over the CEC line. + * @param command The command to transmit. + * @param iMaxTries The maximum number of tries. + * @param iLineTimeout The line timeout to use when sending this message the first time. + * @param iRetryLineTimeout The line timeout to use when retrying to send this message. + */ + CCECAdapterMessage(const cec_command &command, uint8_t iMaxTries = 1, uint8_t iLineTimeout = 3, uint8_t iRetryLineTimeout = 3); + + /*! + * @return the message as human readable string. + */ + CStdString ToString(void) const; + + /*! + * @brief Translate the messagecode into a human readable string. + * @param msgCode The messagecode to translate. + * @return The messagecode as string. + */ + static const char *ToString(cec_adapter_messagecode msgCode); + + /*! + * @brief Get the byte at the given position. + * @param pos The position to get. + * @return The requested byte, or 0 when it's out of range. + */ + uint8_t At(uint8_t pos) const; + uint8_t operator[](uint8_t pos) const; + + /*! + * @return The size of the packet in bytes. + */ + uint8_t Size(void) const; + + /*! + * @return True when empty, false otherwise. + */ + bool IsEmpty(void) const; + + /*! + * @brief Clear this message and reset everything to the initial values. + */ + void Clear(void); + + /*! + * @brief Shift the message by the given number of bytes. + * @param iShiftBy The number of bytes to shift. + */ + void Shift(uint8_t iShiftBy); + + /*! + * @brief Append the given message to this message. + * @param data The message to append. + */ + void Append(CCECAdapterMessage &data); + + /*! + * @brief Append the given datapacket to this message. + * @param data The packet to add. + */ + void Append(cec_datapacket &data); + + /*! + * @brief Adds a byte to this message. Does not escape the byte. + * @param byte The byte to add. + */ + void PushBack(uint8_t byte); + + /*! + * @brief Adds a byte to this message and escapes the byte if needed. + * @param byte The byte to add. + */ + void PushEscaped(uint8_t byte); + + /*! + * @brief Adds a byte to this message. + * @param byte The byte to add. + * @return True when a full message was received, false otherwise. + */ + bool PushReceivedByte(uint8_t byte); + + /*! + * @return The messagecode inside this adapter message, or MSGCODE_NOTHING if there is none. + */ + cec_adapter_messagecode Message(void) const; + + /*! + * @return True when this message is a transmission, false otherwise. + */ + bool IsTranmission(void) const; + + /*! + * @return True when the EOM bit is set, false otherwise. + */ + bool IsEOM(void) const; + + /*! + * @return True when the ACK bit is set, false otherwise. + */ + bool IsACK(void) const; + + /*! + * @return True when this message has been replied with an error code, false otherwise. + */ + bool IsError(void) const; + + /*! + * @return True when this message has been replied with an error code and needs to be retried, false otherwise. + */ + bool NeedsRetry(void) const; + + /*! + * @return The logical address of the initiator, or CECDEVICE_UNKNOWN if unknown. + */ + cec_logical_address Initiator(void) const; + + /*! + * @return The logical address of the destination, or CECDEVICE_UNKNOWN if unknown. + */ + cec_logical_address Destination(void) const; + + /*! + * @return True when this message contains a start message, false otherwise. + */ + bool HasStartMessage(void) const; + + /*! + * @brief Push this adapter message to the end of the given cec_command. + * @param command The command to push this message to. + * @return True when a full CEC message was received, false otherwise. + */ + bool PushToCecCommand(cec_command &command) const; + + /*! + * @return The response messagecode. + */ + cec_adapter_messagecode Reply(void) const; + + uint8_t maxTries; /**< the maximum number of times to try to send this message */ + uint8_t tries; /**< the amount of times this message has been sent */ + cec_datapacket response; /**< the response to this message */ + cec_datapacket packet; /**< the actual data */ + cec_adapter_message_state state; /**< the current state of this message */ + int32_t transmit_timeout; /**< the timeout to use when sending this message */ + uint8_t lineTimeout; /**< the default CEC line timeout to use when sending this message */ + uint8_t retryTimeout; /**< the CEC line timeout to use when retrying to send this message */ + + private: + bool bNextByteIsEscaped; /**< true when the next byte that is added will be escaped, false otherwise */ }; } diff --git a/src/lib/adapter/USBCECAdapterMessageQueue.cpp b/src/lib/adapter/USBCECAdapterMessageQueue.cpp new file mode 100644 index 0000000..e95a8a7 --- /dev/null +++ b/src/lib/adapter/USBCECAdapterMessageQueue.cpp @@ -0,0 +1,333 @@ +/* + * 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 an original work, containing original code. + * + * libCEC(R) is a trademark of Pulse-Eight Limited. + * + * This program is dual-licensed; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * Alternatively, you can license this library under a commercial license, + * please contact Pulse-Eight Licensing for more information. + * + * For more information contact: + * Pulse-Eight Licensing + * http://www.pulse-eight.com/ + * http://www.pulse-eight.net/ + */ + +#include "USBCECAdapterMessageQueue.h" +#include "USBCECAdapterCommunication.h" +#include "../platform/sockets/socket.h" +#include "../LibCEC.h" + +using namespace CEC; +using namespace PLATFORM; + +CCECAdapterMessageQueueEntry::CCECAdapterMessageQueueEntry(CCECAdapterMessage *message) : + m_message(message), + m_iPacketsLeft(message->IsTranmission() ? message->Size() / 4 : 1), + m_bSucceeded(false), + m_bWaiting(true) {} + +CCECAdapterMessageQueueEntry::~CCECAdapterMessageQueueEntry(void) { } + +void CCECAdapterMessageQueueEntry::Broadcast(void) +{ + CLockObject lock(m_mutex); + m_condition.Broadcast(); +} + +bool CCECAdapterMessageQueueEntry::MessageReceived(const CCECAdapterMessage &message) +{ + bool bSendSignal(false); + bool bHandled(false); + + PLATFORM::CLockObject lock(m_mutex); + if (!IsResponse(message)) + { + /* we received a message from the adapter that's not a response to this command */ + if (!message.IsTranmission()) + { + /* we received something that's not a transmission while waiting for an ack to this command, so this command failed */ + + //TODO verify whether we're not failing too soon + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - %s - not a response %s - failed", __FUNCTION__, ToString(), CCECAdapterMessage::ToString(message.Message())); + m_message->state = ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED; + bSendSignal = true; + } + } + else + { + /* we received a response, so this message is handled */ + bHandled = true; + switch (message.Message()) + { + case MSGCODE_COMMAND_ACCEPTED: + bSendSignal = MessageReceivedCommandAccepted(message); + break; + case MSGCODE_TRANSMIT_SUCCEEDED: + bSendSignal = MessageReceivedTransmitSucceeded(message); + break; + default: + bSendSignal = MessageReceivedResponse(message); + break; + } + } + + /* signal the waiting thread when we're done */ + if (bSendSignal) + { + m_bSucceeded = true; + m_condition.Signal(); + } + + return bHandled; +} + +bool CCECAdapterMessageQueueEntry::Wait(uint32_t iTimeout) +{ + bool bReturn(false); + /* wait until we receive a signal when the tranmission succeeded */ + { + CLockObject lock(m_mutex); + bReturn = m_bSucceeded ? true : m_condition.Wait(m_mutex, m_bSucceeded, iTimeout); + m_bWaiting = false; + } + return bReturn; +} + +bool CCECAdapterMessageQueueEntry::IsWaiting(void) +{ + CLockObject lock(m_mutex); + return m_bWaiting; +} + +cec_adapter_messagecode CCECAdapterMessageQueueEntry::MessageCode(void) +{ + return m_message->Message(); +} + +bool CCECAdapterMessageQueueEntry::IsResponse(const CCECAdapterMessage &msg) +{ + cec_adapter_messagecode msgCode = msg.Message(); + return msgCode == MessageCode() || + msgCode == MSGCODE_TIMEOUT_ERROR || + msgCode == MSGCODE_COMMAND_ACCEPTED || + msgCode == MSGCODE_COMMAND_REJECTED || + (m_message->IsTranmission() && msgCode == MSGCODE_HIGH_ERROR) || + (m_message->IsTranmission() && msgCode == MSGCODE_LOW_ERROR) || + (m_message->IsTranmission() && msgCode == MSGCODE_RECEIVE_FAILED) || + (m_message->IsTranmission() && msgCode == MSGCODE_TRANSMIT_FAILED_LINE) || + (m_message->IsTranmission() && msgCode == MSGCODE_TRANSMIT_FAILED_ACK) || + (m_message->IsTranmission() && msgCode == MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA) || + (m_message->IsTranmission() && msgCode == MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE) || + (m_message->IsTranmission() && msgCode == MSGCODE_TRANSMIT_SUCCEEDED); +} + +const char *CCECAdapterMessageQueueEntry::ToString(void) const +{ + /* CEC transmissions got the 'set ack polarity' msgcode, which doesn't look nice */ + if (m_message->IsTranmission()) + return "CEC transmission"; + else + return CCECAdapterMessage::ToString(m_message->Message()); +} + +bool CCECAdapterMessageQueueEntry::MessageReceivedCommandAccepted(const CCECAdapterMessage &message) +{ + bool bSendSignal(false); + if (m_iPacketsLeft == 0) + { + /* we received a "command accepted", but we're not waiting for one anymore */ + CLibCEC::AddLog(CEC_LOG_ERROR, "%s - received unexpected 'command accepted' message", ToString()); + m_message->state = ADAPTER_MESSAGE_STATE_ERROR; + bSendSignal = true; + } + else + { + /* decrease number of acks we're waiting on by 1 */ + if (m_iPacketsLeft > 0) + m_iPacketsLeft--; + + /* log this message */ + CStdString strLog; + strLog.Format("%s - command accepted", ToString()); + if (m_iPacketsLeft > 0) + strLog.AppendFormat(" - waiting for %d more", m_iPacketsLeft); + CLibCEC::AddLog(CEC_LOG_DEBUG, strLog); + + /* no more packets left and not a transmission, so we're done */ + if (!m_message->IsTranmission() && m_iPacketsLeft == 0) + { + m_message->state = ADAPTER_MESSAGE_STATE_SENT_ACKED; + m_message->response = message.packet; + bSendSignal = true; + } + } + return bSendSignal; +} + +bool CCECAdapterMessageQueueEntry::MessageReceivedTransmitSucceeded(const CCECAdapterMessage &message) +{ + if (m_iPacketsLeft == 0) + { + /* transmission succeeded, so we're done */ + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - transmit succeeded", ToString()); + m_message->state = ADAPTER_MESSAGE_STATE_SENT_ACKED; + m_message->response = message.packet; + } + else + { + /* error, we expected more acks */ + CLibCEC::AddLog(CEC_LOG_WARNING, "%s - received 'transmit succeeded' but not enough 'command accepted' messages (%d left)", ToString(), m_iPacketsLeft); + m_message->state = ADAPTER_MESSAGE_STATE_ERROR; + } + return true; +} + +bool CCECAdapterMessageQueueEntry::MessageReceivedResponse(const CCECAdapterMessage &message) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - received response", ToString()); + m_message->response = message.packet; + if (m_message->IsTranmission()) + m_message->state = message.Message() == MSGCODE_TRANSMIT_SUCCEEDED ? ADAPTER_MESSAGE_STATE_SENT_ACKED : ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED; + else + m_message->state = ADAPTER_MESSAGE_STATE_SENT_ACKED; + return true; +} + + +CCECAdapterMessageQueue::~CCECAdapterMessageQueue(void) +{ + Clear(); +} + +void CCECAdapterMessageQueue::Clear(void) +{ + CLockObject lock(m_mutex); + CCECAdapterMessageQueueEntry *message(NULL); + while (m_messages.Pop(message)) + message->Broadcast(); +} + +void CCECAdapterMessageQueue::MessageReceived(const CCECAdapterMessage &msg) +{ + CLockObject lock(m_mutex); + CCECAdapterMessageQueueEntry *message = GetNextQueuedEntry(); + + /* send the received message to the first entry in the queue */ + bool bHandled = message ? message->MessageReceived(msg) : false; + + if (!message || !bHandled) + { + /* the message wasn't handled */ + bool bIsError(m_com->HandlePoll(msg)); + CLibCEC::AddLog(bIsError ? CEC_LOG_WARNING : CEC_LOG_DEBUG, msg.ToString()); + + /* push this message to the current frame */ + if (!bIsError && msg.PushToCecCommand(m_currentCECFrame)) + { + /* and push the current frame back over the callback method when a full command was received */ + if (m_com->IsInitialised()) + m_com->m_callback->OnCommandReceived(m_currentCECFrame); + + /* clear the current frame */ + m_currentCECFrame.Clear(); + } + } +} + +void CCECAdapterMessageQueue::AddData(uint8_t *data, size_t iLen) +{ + for (size_t iPtr = 0; iPtr < iLen; iPtr++) + { + bool bFullMessage(false); + { + CLockObject lock(m_mutex); + bFullMessage = m_incomingAdapterMessage.PushReceivedByte(data[iPtr]); + } + + if (bFullMessage) + { + /* a full message was received */ + CCECAdapterMessage newMessage; + newMessage.packet = m_incomingAdapterMessage.packet; + MessageReceived(newMessage); + + /* clear the current message */ + CLockObject lock(m_mutex); + m_incomingAdapterMessage.Clear(); + } + } +} + +bool CCECAdapterMessageQueue::Write(CCECAdapterMessage *msg) +{ + msg->state = ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT; + + /* set the correct line timeout */ + if (msg->IsTranmission()) + { + if (msg->tries == 1) + m_com->SetLineTimeout(msg->lineTimeout); + else + m_com->SetLineTimeout(msg->retryTimeout); + } + + CCECAdapterMessageQueueEntry *entry(NULL); + /* add to the wait for ack queue */ + if (msg->Message() != MSGCODE_START_BOOTLOADER) + { + entry = new CCECAdapterMessageQueueEntry(msg); + PLATFORM::CLockObject lock(m_mutex); + m_messages.Push(entry); + } + + /* TODO write the message async */ + if (!m_com->WriteToDevice(msg)) + { + /* error! */ + Clear(); + return false; + } + + if (entry && !entry->Wait(msg->transmit_timeout <= 5 ? CEC_DEFAULT_TRANSMIT_WAIT : msg->transmit_timeout)) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "command '%s' was not acked by the controller", CCECAdapterMessage::ToString(msg->Message())); + msg->state = ADAPTER_MESSAGE_STATE_SENT_NOT_ACKED; + return false; + } + + return true; +} + +CCECAdapterMessageQueueEntry *CCECAdapterMessageQueue::GetNextQueuedEntry(void) +{ + CCECAdapterMessageQueueEntry *message(NULL); + while (message == NULL && m_messages.Peek(message)) + { + if (!message->IsWaiting()) + { + /* delete old messages */ + m_messages.Pop(message); + delete message; + message = NULL; + } + } + return message; +} diff --git a/src/lib/adapter/USBCECAdapterMessageQueue.h b/src/lib/adapter/USBCECAdapterMessageQueue.h new file mode 100644 index 0000000..5163090 --- /dev/null +++ b/src/lib/adapter/USBCECAdapterMessageQueue.h @@ -0,0 +1,169 @@ +#pragma once +/* + * 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 an original work, containing original code. + * + * libCEC(R) is a trademark of Pulse-Eight Limited. + * + * This program is dual-licensed; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * Alternatively, you can license this library under a commercial license, + * please contact Pulse-Eight Licensing for more information. + * + * For more information contact: + * Pulse-Eight Licensing + * http://www.pulse-eight.com/ + * http://www.pulse-eight.net/ + */ + +#include "USBCECAdapterMessage.h" + +namespace CEC +{ + class CUSBCECAdapterCommunication; + + class CCECAdapterMessageQueueEntry + { + public: + CCECAdapterMessageQueueEntry(CCECAdapterMessage *message); + virtual ~CCECAdapterMessageQueueEntry(void); + + /*! + * @brief Signal waiting threads + */ + void Broadcast(void); + + /*! + * @brief Called when a message was received. + * @param message The message that was received. + * @return True when this message was handled by this entry, false otherwise. + */ + bool MessageReceived(const CCECAdapterMessage &message); + + /*! + * @brief Wait for a response to this command. + * @param iTimeout The timeout to use while waiting. + * @return True when a response was received before the timeout passed, false otherwise. + */ + bool Wait(uint32_t iTimeout); + + /*! + * @return True while a thread is waiting for a signal or isn't waiting yet, false otherwise. + */ + bool IsWaiting(void); + + /*! + * @return The msgcode of the command that was sent. + */ + cec_adapter_messagecode MessageCode(void); + + /*! + * @brief Check whether a message is a response to this command. + * @param msg The message to check. + * @return True when it's a response, false otherwise. + */ + bool IsResponse(const CCECAdapterMessage &msg); + + /*! + * @return The command that was sent in human readable form. + */ + const char *ToString(void) const; + + private: + /*! + * @brief Called when a 'command accepted' message was received. + * @param message The message that was received. + * @return True when the waiting thread need to be signaled, false otherwise. + */ + bool MessageReceivedCommandAccepted(const CCECAdapterMessage &message); + + /*! + * @brief Called when a 'transmit succeeded' message was received. + * @param message The message that was received. + * @return True when the waiting thread need to be signaled, false otherwise. + */ + bool MessageReceivedTransmitSucceeded(const CCECAdapterMessage &message); + + /*! + * @brief Called when a message that's not a 'command accepted' or 'transmit succeeded' message was received. + * @param message The message that was received. + * @return True when the waiting thread need to be signaled, false otherwise. + */ + bool MessageReceivedResponse(const CCECAdapterMessage &message); + + CCECAdapterMessage * m_message; /**< the message that was sent */ + uint8_t m_iPacketsLeft; /**< the amount of acks that we're waiting on */ + bool m_bSucceeded; /**< true when the command received a response, false otherwise */ + bool m_bWaiting; /**< true while a thread is waiting or when it hasn't started waiting yet */ + PLATFORM::CCondition m_condition; /**< the condition to wait on */ + PLATFORM::CMutex m_mutex; /**< mutex for changes to this class */ + }; + + class CCECAdapterMessageQueue + { + friend class CUSBCECAdapterCommunication; + + public: + /*! + * @brief Create a new message queue. + * @param com The communication handler callback to use. + * @param iQueueSize The outgoing message queue size. + */ + CCECAdapterMessageQueue(CUSBCECAdapterCommunication *com, size_t iQueueSize = 64) : + m_com(com), + m_messages(iQueueSize) {} + virtual ~CCECAdapterMessageQueue(void); + + /*! + * @brief Signal and delete everything in the queue + */ + void Clear(void); + + /*! + * @brief Called when a message was received from the adapter. + * @param msg The message that was received. + */ + void MessageReceived(const CCECAdapterMessage &msg); + + /*! + * @brief Adds received data to the current frame. + * @param data The data to add. + * @param iLen The length of the data to add. + */ + void AddData(uint8_t *data, size_t iLen); + + /*! + * @brief Transmit a command to the adapter and wait for a response. + * @param msg The command to send. + * @return True when written, false otherwise. + */ + bool Write(CCECAdapterMessage *msg); + + private: + /*! + * @return The next message in the queue, or NULL if there is none. + */ + CCECAdapterMessageQueueEntry *GetNextQueuedEntry(void); + + CUSBCECAdapterCommunication * m_com; /**< the communication handler */ + PLATFORM::CMutex m_mutex; /**< mutex for changes to this class */ + PLATFORM::SyncedBuffer m_messages; /**< the outgoing message queue */ + CCECAdapterMessage m_incomingAdapterMessage; /**< the current incoming message that's being assembled */ + cec_command m_currentCECFrame; /**< the current incoming CEC command that's being assembled */ + }; +} diff --git a/src/lib/devices/CECBusDevice.cpp b/src/lib/devices/CECBusDevice.cpp index 5b9d421..cba201b 100644 --- a/src/lib/devices/CECBusDevice.cpp +++ b/src/lib/devices/CECBusDevice.cpp @@ -170,7 +170,7 @@ cec_version CCECBusDevice::GetCecVersion(bool bUpdate /* = false */) return m_cecVersion; } -bool CCECBusDevice::RequestCecVersion(void) +bool CCECBusDevice::RequestCecVersion(bool bWaitForResponse /* = true */) { bool bReturn(false); @@ -180,7 +180,7 @@ bool CCECBusDevice::RequestCecVersion(void) MarkBusy(); CLibCEC::AddLog(CEC_LOG_NOTICE, "<< requesting CEC version of '%s' (%X)", GetLogicalAddressName(), m_iLogicalAddress); - bReturn = m_handler->TransmitRequestCecVersion(GetMyLogicalAddress(), m_iLogicalAddress); + bReturn = m_handler->TransmitRequestCecVersion(GetMyLogicalAddress(), m_iLogicalAddress, bWaitForResponse); MarkReady(); } return bReturn; @@ -211,7 +211,7 @@ cec_menu_language &CCECBusDevice::GetMenuLanguage(bool bUpdate /* = false */) return m_menuLanguage; } -bool CCECBusDevice::RequestMenuLanguage(void) +bool CCECBusDevice::RequestMenuLanguage(bool bWaitForResponse /* = true */) { bool bReturn(false); @@ -220,7 +220,7 @@ bool CCECBusDevice::RequestMenuLanguage(void) { MarkBusy(); CLibCEC::AddLog(CEC_LOG_NOTICE, "<< requesting menu language of '%s' (%X)", GetLogicalAddressName(), m_iLogicalAddress); - bReturn = m_handler->TransmitRequestMenuLanguage(GetMyLogicalAddress(), m_iLogicalAddress); + bReturn = m_handler->TransmitRequestMenuLanguage(GetMyLogicalAddress(), m_iLogicalAddress, bWaitForResponse); MarkReady(); } return bReturn; @@ -263,7 +263,7 @@ CStdString CCECBusDevice::GetOSDName(bool bUpdate /* = false */) return m_strDeviceName; } -bool CCECBusDevice::RequestOSDName(void) +bool CCECBusDevice::RequestOSDName(bool bWaitForResponse /* = true */) { bool bReturn(false); @@ -272,15 +272,15 @@ bool CCECBusDevice::RequestOSDName(void) { MarkBusy(); CLibCEC::AddLog(CEC_LOG_NOTICE, "<< requesting OSD name of '%s' (%X)", GetLogicalAddressName(), m_iLogicalAddress); - bReturn = m_handler->TransmitRequestOSDName(GetMyLogicalAddress(), m_iLogicalAddress); + bReturn = m_handler->TransmitRequestOSDName(GetMyLogicalAddress(), m_iLogicalAddress, bWaitForResponse); MarkReady(); } return bReturn; } -uint16_t CCECBusDevice::GetPhysicalAddress(bool bUpdate /* = false */) +uint16_t CCECBusDevice::GetPhysicalAddress(bool bUpdate /* = false */, bool bSuppressPoll /* = false */) { - bool bIsPresent(GetStatus() == CEC_DEVICE_STATUS_PRESENT); + bool bIsPresent(GetStatus(false, bSuppressPoll) == CEC_DEVICE_STATUS_PRESENT); bool bRequestUpdate(false); { CLockObject lock(m_mutex); @@ -299,7 +299,7 @@ uint16_t CCECBusDevice::GetPhysicalAddress(bool bUpdate /* = false */) return m_iPhysicalAddress; } -bool CCECBusDevice::RequestPhysicalAddress(void) +bool CCECBusDevice::RequestPhysicalAddress(bool bWaitForResponse /* = true */) { bool bReturn(false); @@ -307,7 +307,7 @@ bool CCECBusDevice::RequestPhysicalAddress(void) { MarkBusy(); CLibCEC::AddLog(CEC_LOG_NOTICE, "<< requesting physical address of '%s' (%X)", GetLogicalAddressName(), m_iLogicalAddress); - bReturn = m_handler->TransmitRequestPhysicalAddress(GetMyLogicalAddress(), m_iLogicalAddress); + bReturn = m_handler->TransmitRequestPhysicalAddress(GetMyLogicalAddress(), m_iLogicalAddress, bWaitForResponse); MarkReady(); } return bReturn; @@ -336,7 +336,7 @@ cec_power_status CCECBusDevice::GetPowerStatus(bool bUpdate /* = false */) return m_powerStatus; } -bool CCECBusDevice::RequestPowerStatus(void) +bool CCECBusDevice::RequestPowerStatus(bool bWaitForResponse /* = true */) { bool bReturn(false); @@ -345,7 +345,7 @@ bool CCECBusDevice::RequestPowerStatus(void) { MarkBusy(); CLibCEC::AddLog(CEC_LOG_NOTICE, "<< requesting power status of '%s' (%X)", GetLogicalAddressName(), m_iLogicalAddress); - bReturn = m_handler->TransmitRequestPowerStatus(GetMyLogicalAddress(), m_iLogicalAddress); + bReturn = m_handler->TransmitRequestPowerStatus(GetMyLogicalAddress(), m_iLogicalAddress, bWaitForResponse); MarkReady(); } return bReturn; @@ -368,7 +368,7 @@ cec_vendor_id CCECBusDevice::GetVendorId(bool bUpdate /* = false */) return m_vendor; } -bool CCECBusDevice::RequestVendorId(void) +bool CCECBusDevice::RequestVendorId(bool bWaitForResponse /* = true */) { bool bReturn(false); @@ -376,10 +376,11 @@ bool CCECBusDevice::RequestVendorId(void) { MarkBusy(); CLibCEC::AddLog(CEC_LOG_NOTICE, "<< requesting vendor ID of '%s' (%X)", GetLogicalAddressName(), m_iLogicalAddress); - bReturn = m_handler->TransmitRequestVendorId(GetMyLogicalAddress(), m_iLogicalAddress); + bReturn = m_handler->TransmitRequestVendorId(GetMyLogicalAddress(), m_iLogicalAddress, bWaitForResponse); MarkReady(); - ReplaceHandler(true); + if (bWaitForResponse) + ReplaceHandler(true); } return bReturn; } @@ -455,7 +456,7 @@ bool CCECBusDevice::NeedsPoll(void) return bSendPoll; } -cec_bus_device_status CCECBusDevice::GetStatus(bool bForcePoll /* = false */) +cec_bus_device_status CCECBusDevice::GetStatus(bool bForcePoll /* = false */, bool bSuppressPoll /* = false */) { cec_bus_device_status status(CEC_DEVICE_STATUS_UNKNOWN); bool bNeedsPoll(false); @@ -463,8 +464,8 @@ cec_bus_device_status CCECBusDevice::GetStatus(bool bForcePoll /* = false */) { CLockObject lock(m_mutex); status = m_deviceStatus; - bNeedsPoll = (m_deviceStatus != CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC && - (m_deviceStatus == CEC_DEVICE_STATUS_UNKNOWN || bForcePoll)); + bNeedsPoll = !bSuppressPoll && + (bForcePoll || m_deviceStatus == CEC_DEVICE_STATUS_UNKNOWN); } if (bNeedsPoll) @@ -609,7 +610,7 @@ void CCECBusDevice::SetDeviceStatus(const cec_bus_device_status newStatus) } if (newStatus == CEC_DEVICE_STATUS_PRESENT) - RequestVendorId(); + RequestVendorId(false); } void CCECBusDevice::SetPhysicalAddress(uint16_t iNewAddress) @@ -630,7 +631,8 @@ void CCECBusDevice::SetStreamPath(uint16_t iNewAddress, uint16_t iOldAddress /* CLibCEC::AddLog(CEC_LOG_DEBUG, ">> %s (%X): stream path changed from %04x to %04x", GetLogicalAddressName(), m_iLogicalAddress, iOldAddress == 0 ? m_iStreamPath : iOldAddress, iNewAddress); m_iStreamPath = iNewAddress; - CCECBusDevice *device = m_processor->GetDeviceByPhysicalAddress(iNewAddress, false); + // suppress polls when searching for a device + CCECBusDevice *device = m_processor->GetDeviceByPhysicalAddress(iNewAddress, false, true); if (device) { // if a device is found with the new physical address, mark it as active, which will automatically mark all other devices as inactive @@ -639,7 +641,7 @@ void CCECBusDevice::SetStreamPath(uint16_t iNewAddress, uint16_t iOldAddress /* else { // try to find the device with the old address, and mark it as inactive when found - device = m_processor->GetDeviceByPhysicalAddress(iOldAddress, false); + device = m_processor->GetDeviceByPhysicalAddress(iOldAddress, false, true); if (device) device->SetInactiveSource(); } @@ -1032,9 +1034,19 @@ bool CCECBusDevice::ActivateSource(void) return bReturn; } -void CCECBusDevice::HandlePoll(cec_logical_address iDestination) +void CCECBusDevice::HandlePoll(cec_logical_address destination) +{ + if (destination >= 0 && destination < CECDEVICE_BROADCAST) + { + CCECBusDevice *device = m_processor->m_busDevices[destination]; + if (device) + device->HandlePollFrom(m_iLogicalAddress); + } +} + +void CCECBusDevice::HandlePollFrom(cec_logical_address initiator) { - CLibCEC::AddLog(CEC_LOG_DEBUG, "<< POLL: %s (%x) -> %s (%x)", ToString(m_iLogicalAddress), m_iLogicalAddress, ToString(iDestination), iDestination); + CLibCEC::AddLog(CEC_LOG_DEBUG, "<< POLL: %s (%x) -> %s (%x)", ToString(initiator), initiator, ToString(m_iLogicalAddress), m_iLogicalAddress); m_bAwaitingReceiveFailed = true; } diff --git a/src/lib/devices/CECBusDevice.h b/src/lib/devices/CECBusDevice.h index 09d3648..2bd9730 100644 --- a/src/lib/devices/CECBusDevice.h +++ b/src/lib/devices/CECBusDevice.h @@ -63,18 +63,19 @@ namespace CEC virtual cec_logical_address GetMyLogicalAddress(void) const; virtual uint16_t GetMyPhysicalAddress(void) const; virtual CStdString GetOSDName(bool bUpdate = false); - virtual uint16_t GetPhysicalAddress(bool bUpdate = false); + virtual uint16_t GetPhysicalAddress(bool bUpdate = false, bool bSuppressPoll = false); virtual cec_power_status GetPowerStatus(bool bUpdate = false); virtual CCECProcessor * GetProcessor(void) const { return m_processor; } virtual cec_device_type GetType(void) const { return m_type; } virtual cec_vendor_id GetVendorId(bool bUpdate = false); virtual const char * GetVendorName(bool bUpdate = false); virtual bool MyLogicalAddressContains(cec_logical_address address) const; - virtual cec_bus_device_status GetStatus(bool bForcePoll = false); + virtual cec_bus_device_status GetStatus(bool bForcePoll = false, bool bSuppressPoll = false); virtual bool IsActiveSource(void) const { return m_bActiveSource; } virtual bool IsUnsupportedFeature(cec_opcode opcode) const; virtual void SetUnsupportedFeature(cec_opcode opcode); - virtual void HandlePoll(cec_logical_address initiator); + virtual void HandlePoll(cec_logical_address destination); + virtual void HandlePollFrom(cec_logical_address initiator); virtual bool HandleReceiveFailed(void); virtual void SetInactiveSource(void); @@ -114,12 +115,12 @@ namespace CEC void MarkBusy(void); void MarkReady(void); - bool RequestCecVersion(void); - bool RequestMenuLanguage(void); - bool RequestPowerStatus(void); - bool RequestVendorId(void); - bool RequestPhysicalAddress(void); - bool RequestOSDName(void); + bool RequestCecVersion(bool bWaitForResponse = true); + bool RequestMenuLanguage(bool bWaitForResponse = true); + bool RequestPowerStatus(bool bWaitForResponse = true); + bool RequestVendorId(bool bWaitForResponse = true); + bool RequestPhysicalAddress(bool bWaitForResponse = true); + bool RequestOSDName(bool bWaitForResponse = true); bool NeedsPoll(void); diff --git a/src/lib/implementations/CECCommandHandler.cpp b/src/lib/implementations/CECCommandHandler.cpp index eb7d58e..eed6087 100644 --- a/src/lib/implementations/CECCommandHandler.cpp +++ b/src/lib/implementations/CECCommandHandler.cpp @@ -392,7 +392,7 @@ bool CCECCommandHandler::HandleMenuRequest(const cec_command &command) bool CCECCommandHandler::HandlePoll(const cec_command &command) { - m_busDevice->HandlePoll(command.initiator); + m_busDevice->HandlePoll(command.destination); return true; } @@ -769,52 +769,52 @@ bool CCECCommandHandler::TransmitStandby(const cec_logical_address iInitiator, c return Transmit(command, false); } -bool CCECCommandHandler::TransmitRequestCecVersion(const cec_logical_address iInitiator, const cec_logical_address iDestination) +bool CCECCommandHandler::TransmitRequestCecVersion(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */) { cec_command command; cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GET_CEC_VERSION); - return Transmit(command, true, CEC_OPCODE_CEC_VERSION); + return Transmit(command, bWaitForResponse, CEC_OPCODE_CEC_VERSION); } -bool CCECCommandHandler::TransmitRequestMenuLanguage(const cec_logical_address iInitiator, const cec_logical_address iDestination) +bool CCECCommandHandler::TransmitRequestMenuLanguage(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */) { cec_command command; cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GET_MENU_LANGUAGE); - return Transmit(command, true, CEC_OPCODE_SET_MENU_LANGUAGE); + return Transmit(command, bWaitForResponse, CEC_OPCODE_SET_MENU_LANGUAGE); } -bool CCECCommandHandler::TransmitRequestOSDName(const cec_logical_address iInitiator, const cec_logical_address iDestination) +bool CCECCommandHandler::TransmitRequestOSDName(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */) { cec_command command; cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_OSD_NAME); - return Transmit(command, true, CEC_OPCODE_SET_OSD_NAME); + return Transmit(command, bWaitForResponse, CEC_OPCODE_SET_OSD_NAME); } -bool CCECCommandHandler::TransmitRequestPhysicalAddress(const cec_logical_address iInitiator, const cec_logical_address iDestination) +bool CCECCommandHandler::TransmitRequestPhysicalAddress(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */) { cec_command command; cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_PHYSICAL_ADDRESS); - return Transmit(command, true, CEC_OPCODE_REPORT_PHYSICAL_ADDRESS); + return Transmit(command, bWaitForResponse, CEC_OPCODE_REPORT_PHYSICAL_ADDRESS); } -bool CCECCommandHandler::TransmitRequestPowerStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination) +bool CCECCommandHandler::TransmitRequestPowerStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */) { cec_command command; cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_DEVICE_POWER_STATUS); - return Transmit(command, true, CEC_OPCODE_REPORT_POWER_STATUS); + return Transmit(command, bWaitForResponse, CEC_OPCODE_REPORT_POWER_STATUS); } -bool CCECCommandHandler::TransmitRequestVendorId(const cec_logical_address iInitiator, const cec_logical_address iDestination) +bool CCECCommandHandler::TransmitRequestVendorId(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */) { cec_command command; cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_DEVICE_VENDOR_ID); - return Transmit(command, true, CEC_OPCODE_DEVICE_VENDOR_ID); + return Transmit(command, bWaitForResponse, CEC_OPCODE_DEVICE_VENDOR_ID); } bool CCECCommandHandler::TransmitActiveSource(const cec_logical_address iInitiator, uint16_t iPhysicalAddress) diff --git a/src/lib/implementations/CECCommandHandler.h b/src/lib/implementations/CECCommandHandler.h index da2d921..9389270 100644 --- a/src/lib/implementations/CECCommandHandler.h +++ b/src/lib/implementations/CECCommandHandler.h @@ -132,12 +132,12 @@ namespace CEC virtual bool PowerOn(const cec_logical_address iInitiator, const cec_logical_address iDestination); virtual bool TransmitImageViewOn(const cec_logical_address iInitiator, const cec_logical_address iDestination); virtual bool TransmitStandby(const cec_logical_address iInitiator, const cec_logical_address iDestination); - virtual bool TransmitRequestCecVersion(const cec_logical_address iInitiator, const cec_logical_address iDestination); - virtual bool TransmitRequestMenuLanguage(const cec_logical_address iInitiator, const cec_logical_address iDestination); - virtual bool TransmitRequestOSDName(const cec_logical_address iInitiator, const cec_logical_address iDestination); - virtual bool TransmitRequestPhysicalAddress(const cec_logical_address iInitiator, const cec_logical_address iDestination); - virtual bool TransmitRequestPowerStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination); - virtual bool TransmitRequestVendorId(const cec_logical_address iInitiator, const cec_logical_address iDestination); + virtual bool TransmitRequestCecVersion(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse = true); + virtual bool TransmitRequestMenuLanguage(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse = true); + virtual bool TransmitRequestOSDName(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse = true); + virtual bool TransmitRequestPhysicalAddress(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse = true); + virtual bool TransmitRequestPowerStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse = true); + virtual bool TransmitRequestVendorId(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse = true); virtual bool TransmitActiveSource(const cec_logical_address iInitiator, uint16_t iPhysicalAddress); virtual bool TransmitCECVersion(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_version cecVersion); virtual bool TransmitInactiveSource(const cec_logical_address iInitiator, uint16_t iPhysicalAddress); diff --git a/src/lib/implementations/SLCommandHandler.cpp b/src/lib/implementations/SLCommandHandler.cpp index 9d938c2..957d296 100644 --- a/src/lib/implementations/SLCommandHandler.cpp +++ b/src/lib/implementations/SLCommandHandler.cpp @@ -152,7 +152,7 @@ bool CSLCommandHandler::HandleDeviceVendorId(const cec_command &command) { cec_command response; cec_command::Format(response, m_processor->GetLogicalAddress(), command.initiator, CEC_OPCODE_FEATURE_ABORT); - return Transmit(response); + return Transmit(response, false); } return true; } @@ -418,7 +418,7 @@ bool CSLCommandHandler::PowerOn(const cec_logical_address iInitiator, const cec_ cec_command::Format(command, CECDEVICE_TV, iDestination, CEC_OPCODE_VENDOR_COMMAND); command.PushBack(SL_COMMAND_POWER_ON); command.PushBack(0); - return Transmit(command); + return Transmit(command, false); } return CCECCommandHandler::PowerOn(iInitiator, iDestination); diff --git a/src/lib/platform/util/buffer.h b/src/lib/platform/util/buffer.h index 56ffd64..8777661 100644 --- a/src/lib/platform/util/buffer.h +++ b/src/lib/platform/util/buffer.h @@ -90,6 +90,18 @@ namespace PLATFORM return bReturn; } + bool Peek(_BType &entry) + { + bool bReturn(false); + CLockObject lock(m_mutex); + if (!m_buffer.empty()) + { + entry = m_buffer.front(); + bReturn = true; + } + return bReturn; + } + private: size_t m_maxSize; std::queue<_BType> m_buffer;