From: Lars Op den Kamp Date: Mon, 16 Apr 2012 16:45:01 +0000 (+0200) Subject: Merge branch 'master' into release X-Git-Tag: upstream/2.2.0~1^2~31 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=1bede530033f40a81400c7a1e4ed0e8755d4ec59;hp=00a02d1d839c2155ee8abc3a37d10ab12b9881db;p=deb_libcec.git Merge branch 'master' into release --- diff --git a/ChangeLog b/ChangeLog index 15d5a73..d3b160e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,135 @@ +libcec (1.6-1) unstable; urgency=medium + + * changed/added: + * full firmware V2 support + * -o/--osdname argument for cec-client to set a custom osd name + * added the firmware version to cec-client's -l / --list-devices command. + bugzid: 631 + * added power on command for Samsung AVR devices. bugzid: 361 + * added buttoncode for 'channels list' on Samsung (0x96) + * don't check for the windows ddk when a prebuilt driver installer is + present + * respond to Get Menu Language. bugzid: 547. In the event that the menu + language is undefined in libcec (currently the default), the response + will be a feature abort. + + * interface changes: + * added the firmware version to libcec_configuration (read-only). bumped + server version to 1.6.0. fixed 'unknown server version' message on + startup. bugzid: 631 + * added a new setting to control whether to put the TV in standby when the + player is put in standby. added some missing bits (version numbers, + config) to LibCecSharp. bugzid: 558 + * added an alert callback. bugzid: 462 + * added bShutdownOnStandby to libcec_configuration. bugzid: 660. This + setting tells the client to shutdown when the TV switches off and is + complimentary to bPowerOffOnStandby, which tells the PC to suspend. + They are kept separate to maintain backwards compatability. + + * fixed + * gcc 4.7 compilation + * poll doesn't have an opcode. bugzid: 591 + * wait for MSGEND when data was received when opening the connection. + bugzid: 536 + * mark the correct device as active source after a stream path change. + if the new address is not found, but the old address is, then mark the + old address as inactive. fixes TV switching back to the old active source + when it scans for devices. bugzid: 592 + * ensure that the vendor ID is sent before trying to activate any SL device + bugzid: 574 + * fixed possible crash when in CLibCEC::IsLibCECActiveSource() when libCEC + doesn't know which device is the active source. bugzid: 479 + * correct handling CEC_USER_CONTROL_CODE_POWER. This ensures that the power + code always operates as a toggle, depending on the current state, and + that SetCurrentButton is always called for a valid user control code. + bugzid: 570 + * frequency wasn't checked in GetTimeMs(), leading to incorrect wait times + on some windows systems + * refactored USB adapter communication. less locks, shorter locks, added + documentation, lots of clean ups and no more incoming messages that are + skipped + * the destructor of CSerialSocket didn't call Close() + * added guards in CSerialPort + * reset m_socket to INVALID_SERIAL_SOCKET_VALUE after closing the + connection + * always wait for thread exit in CThread's desctructor + * crash on exit after GetDeviceInformation() + * check whether the destination is valid before setting anything in + m_bWaitingForAck. fixes heap corruption and crash on exit. bugzid: 479 + * bUseTVMenuLanguage from libcec_configuration wasn't copied in + SetConfiguration(), so this setting was reset to the default value + (enabled) every time. bugzid: 617 + * request the vendor id of a device if needed when the device status is + changed into 'present'. bugzid: 361 + * give priority to messages from the TV. removed the global lock in + CCECProcessor when sending. this is no longer needed. bugzid: 238 + * add some bounds checking to the HDMI port number. bugzid: 508 + * fixed usbser.sys copying in the driver .INF. bugzid: 503 + * only update the physical address when it has actually changed. bugzid: + 672 + * moved the static variables in os-threads from the header to a separate + cpp file, or it could lead to problems when included multiple times + * don't send an active source command when the physical address couldn't be + set, or it might confuse other CEC devices + * serial socket timeouts. bugzid: 654 + * fixed possible crash when trying to request a vendor id of a device when + the address of libCEC isn't known yet. bugzid: 654 + * extra guard so no commands are transmitted without a valid initiator. + bugzid: 654 + * moved the timed ping to a separate thread. bugzid: 654 + * persist settings directly when they're changed, only persist settings + that actually changed, only instruct the device to persist the settings + in eeprom when something changed, and don't persist settings on exit. + bugzid: 715 + * cec-config-gui: persist settings both in the eeprom and in the settings + xml file + * validate the input in CCECProcessor::IsActiveSource(). fixes potential + crash when the active source isn't known. bugzid: 671 + + -- Pulse-Eight Packaging Mon, 16 Apr 2012 18:03:00 +0100 + +libcec (1.5-4) unstable; urgency=low + + * changed/added: + * OS-X installation requirements and pointer + * full v2 firmware support: + * ping the adapter every 15 seconds. bugzid: 541 + * added v2 msgcodes. bugzid: 543 + * implemented the write methods for the v2 configuration. bugzid: 543 + * included the logical addresses in the persisted configuration for v2. + bugzid: 543 + * persist the configuration before closing the connection. only try to + persist the configuration when talking to a v2 firmware. bugzid: 543 + * added GetSetting() to CUSBCECAdapterCommunication. bugzid: 543 + * read the persisted settings from the ROM, and update it in + libcec_configuration if found. bugzid: 543 + * cec-client: set bGetSettingsFromROM to 1 in cec-client. bugzid: 543 + * cec-client: only read persisted EEPROM settings when -r or --rom is + provided as cmdline arg. bugzid: 543 + * call SetControlledMode(false) as last command when closing the + connection in v2. bugzid: 542 + * initial FreeBSD support: simply try to use ttyU* + + * fixed: + * set controlled mode and retry to send the previous command if it failed + with MSGCODE_COMMAND_REJECTED + * m_iLineTimeout was never initialised and used. fixes MSGCODE_REJECTED + when transmitting a message and incorrect line timeouts being used + * mac configure compile bugzid: 157 + * cec-config-gui: fixed application exit when the user clicked 'no' when + asked to reconnect. bugzid: 556 + * only wait for multiple 'command accepted' response when sending a + transmit command, and only to 1 in other cases. bugzid: 543 + * set the correct logical address mask before switching to autonomous mode. + bugzid: 543 + * re-added SetLineTimeout(). bugzid: 543 + * CUSBCECAdapterCommunication::SetControlledMode checked for the wrong sent + state + * os-x: don't add 0.5 before dividing in GetTimeMs() + * os-x: struct timespec now values weren't set correctly + + -- Pulse-Eight Packaging Fri, 16 Mar 2012 10:12:00 +0100 + libcec (1.5-3) unstable; urgency=low * fixed: diff --git a/README b/README index c60a921..4b0621c 100644 --- a/README +++ b/README @@ -30,7 +30,7 @@ To compile libCEC on OS-X, you'll need the following dependencies: * autoconf 2.13 or later * automake 1.11 or later * pkg-config -* xcode (TODO: version?) +* xcode 3.2.6 or later To compile, execute the following commands (TODO: please verify): # autoreconf -vif @@ -38,6 +38,8 @@ To compile, execute the following commands (TODO: please verify): # make # sudo make install +# Note: You may need to copy pkg.m4 to your m4 sources directory + =============================================================================== === Windows === =============================================================================== diff --git a/configure.ac b/configure.ac index 23b1a4e..f8b8701 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([libcec], 1:5:0) +AC_INIT([libcec], 1:6:0) AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION) AC_PROG_CXX @@ -7,13 +7,16 @@ AC_PROG_LIBTOOL has_libudev="yes" case "${host}" in *-*-linux*) - PKG_CHECK_MODULES([UDEV],[libudev],,[has_libudev="no";AC_MSG_WARN("library 'udev' is missing - adapter detection will not be available")]) + PKG_CHECK_MODULES([UDEV],[libudev],,[has_libudev="no"]; AC_MSG_WARN("library 'udev' is missing - adapter detection will not be available")) LIBS+=" -lrt" ;; *-apple-darwin*) has_libudev="no"; LIBS+="-framework CoreVideo -framework IOKit" ;; + *-freebsd*) + has_libudev="no" + ;; esac if test "x$has_libudev" != "xno"; then diff --git a/debian/changelog b/debian/changelog index 15d5a73..d3b160e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,135 @@ +libcec (1.6-1) unstable; urgency=medium + + * changed/added: + * full firmware V2 support + * -o/--osdname argument for cec-client to set a custom osd name + * added the firmware version to cec-client's -l / --list-devices command. + bugzid: 631 + * added power on command for Samsung AVR devices. bugzid: 361 + * added buttoncode for 'channels list' on Samsung (0x96) + * don't check for the windows ddk when a prebuilt driver installer is + present + * respond to Get Menu Language. bugzid: 547. In the event that the menu + language is undefined in libcec (currently the default), the response + will be a feature abort. + + * interface changes: + * added the firmware version to libcec_configuration (read-only). bumped + server version to 1.6.0. fixed 'unknown server version' message on + startup. bugzid: 631 + * added a new setting to control whether to put the TV in standby when the + player is put in standby. added some missing bits (version numbers, + config) to LibCecSharp. bugzid: 558 + * added an alert callback. bugzid: 462 + * added bShutdownOnStandby to libcec_configuration. bugzid: 660. This + setting tells the client to shutdown when the TV switches off and is + complimentary to bPowerOffOnStandby, which tells the PC to suspend. + They are kept separate to maintain backwards compatability. + + * fixed + * gcc 4.7 compilation + * poll doesn't have an opcode. bugzid: 591 + * wait for MSGEND when data was received when opening the connection. + bugzid: 536 + * mark the correct device as active source after a stream path change. + if the new address is not found, but the old address is, then mark the + old address as inactive. fixes TV switching back to the old active source + when it scans for devices. bugzid: 592 + * ensure that the vendor ID is sent before trying to activate any SL device + bugzid: 574 + * fixed possible crash when in CLibCEC::IsLibCECActiveSource() when libCEC + doesn't know which device is the active source. bugzid: 479 + * correct handling CEC_USER_CONTROL_CODE_POWER. This ensures that the power + code always operates as a toggle, depending on the current state, and + that SetCurrentButton is always called for a valid user control code. + bugzid: 570 + * frequency wasn't checked in GetTimeMs(), leading to incorrect wait times + on some windows systems + * refactored USB adapter communication. less locks, shorter locks, added + documentation, lots of clean ups and no more incoming messages that are + skipped + * the destructor of CSerialSocket didn't call Close() + * added guards in CSerialPort + * reset m_socket to INVALID_SERIAL_SOCKET_VALUE after closing the + connection + * always wait for thread exit in CThread's desctructor + * crash on exit after GetDeviceInformation() + * check whether the destination is valid before setting anything in + m_bWaitingForAck. fixes heap corruption and crash on exit. bugzid: 479 + * bUseTVMenuLanguage from libcec_configuration wasn't copied in + SetConfiguration(), so this setting was reset to the default value + (enabled) every time. bugzid: 617 + * request the vendor id of a device if needed when the device status is + changed into 'present'. bugzid: 361 + * give priority to messages from the TV. removed the global lock in + CCECProcessor when sending. this is no longer needed. bugzid: 238 + * add some bounds checking to the HDMI port number. bugzid: 508 + * fixed usbser.sys copying in the driver .INF. bugzid: 503 + * only update the physical address when it has actually changed. bugzid: + 672 + * moved the static variables in os-threads from the header to a separate + cpp file, or it could lead to problems when included multiple times + * don't send an active source command when the physical address couldn't be + set, or it might confuse other CEC devices + * serial socket timeouts. bugzid: 654 + * fixed possible crash when trying to request a vendor id of a device when + the address of libCEC isn't known yet. bugzid: 654 + * extra guard so no commands are transmitted without a valid initiator. + bugzid: 654 + * moved the timed ping to a separate thread. bugzid: 654 + * persist settings directly when they're changed, only persist settings + that actually changed, only instruct the device to persist the settings + in eeprom when something changed, and don't persist settings on exit. + bugzid: 715 + * cec-config-gui: persist settings both in the eeprom and in the settings + xml file + * validate the input in CCECProcessor::IsActiveSource(). fixes potential + crash when the active source isn't known. bugzid: 671 + + -- Pulse-Eight Packaging Mon, 16 Apr 2012 18:03:00 +0100 + +libcec (1.5-4) unstable; urgency=low + + * changed/added: + * OS-X installation requirements and pointer + * full v2 firmware support: + * ping the adapter every 15 seconds. bugzid: 541 + * added v2 msgcodes. bugzid: 543 + * implemented the write methods for the v2 configuration. bugzid: 543 + * included the logical addresses in the persisted configuration for v2. + bugzid: 543 + * persist the configuration before closing the connection. only try to + persist the configuration when talking to a v2 firmware. bugzid: 543 + * added GetSetting() to CUSBCECAdapterCommunication. bugzid: 543 + * read the persisted settings from the ROM, and update it in + libcec_configuration if found. bugzid: 543 + * cec-client: set bGetSettingsFromROM to 1 in cec-client. bugzid: 543 + * cec-client: only read persisted EEPROM settings when -r or --rom is + provided as cmdline arg. bugzid: 543 + * call SetControlledMode(false) as last command when closing the + connection in v2. bugzid: 542 + * initial FreeBSD support: simply try to use ttyU* + + * fixed: + * set controlled mode and retry to send the previous command if it failed + with MSGCODE_COMMAND_REJECTED + * m_iLineTimeout was never initialised and used. fixes MSGCODE_REJECTED + when transmitting a message and incorrect line timeouts being used + * mac configure compile bugzid: 157 + * cec-config-gui: fixed application exit when the user clicked 'no' when + asked to reconnect. bugzid: 556 + * only wait for multiple 'command accepted' response when sending a + transmit command, and only to 1 in other cases. bugzid: 543 + * set the correct logical address mask before switching to autonomous mode. + bugzid: 543 + * re-added SetLineTimeout(). bugzid: 543 + * CUSBCECAdapterCommunication::SetControlledMode checked for the wrong sent + state + * os-x: don't add 0.5 before dividing in GetTimeMs() + * os-x: struct timespec now values weren't set correctly + + -- Pulse-Eight Packaging Fri, 16 Mar 2012 10:12:00 +0100 + libcec (1.5-3) unstable; urgency=low * fixed: diff --git a/driver/p8usb-cec.inf b/driver/p8usb-cec.inf index 1731e8d..9c89626 100644 --- a/driver/p8usb-cec.inf +++ b/driver/p8usb-cec.inf @@ -13,6 +13,7 @@ DriverVer=07/04/2011,1.0.0.0 %MFGNAME%=DeviceList, NTamd64 [DestinationDirs] +FakeModemCopyFileSection=12 DefaultDestDir=12 @@ -22,12 +23,9 @@ DefaultDestDir=12 [DriverInstall.nt] include=mdmcpq.inf -CopyFiles=DriverCopyFiles.nt +CopyFiles=FakeModemCopyFileSection AddReg=DriverInstall.nt.AddReg -[DriverCopyFiles.nt] -usbser.sys,,,0x20 - [DriverInstall.nt.AddReg] HKR,,DevLoader,,*ntkern HKR,,NTMPDriver,,%DRIVERFILENAME%.sys @@ -49,12 +47,9 @@ ServiceBinary=%12%\%DRIVERFILENAME%.sys [DriverInstall.NTamd64] include=mdmcpq.inf -CopyFiles=DriverCopyFiles.NTamd64 +CopyFiles=FakeModemCopyFileSection AddReg=DriverInstall.NTamd64.AddReg -[DriverCopyFiles.NTamd64] -%DRIVERFILENAME%.sys,,,0x20 - [DriverInstall.NTamd64.AddReg] HKR,,DevLoader,,*ntkern HKR,,NTMPDriver,,%DRIVERFILENAME%.sys diff --git a/include/cec.h b/include/cec.h index 0332088..20e103b 100644 --- a/include/cec.h +++ b/include/cec.h @@ -424,6 +424,15 @@ namespace CEC * @return true when libCEC is the active source on the bus, false otherwise. */ virtual bool IsLibCECActiveSource(void) = 0; + + /*! + * @brief Get information about the given device + * @param strPort The port to which the device is connected + * @param config The device configuration + * @param iTimeoutMs The timeout in milliseconds + * @return True when the device was found, false otherwise + */ + virtual bool GetDeviceInformation(const char *strPort, libcec_configuration *config, uint32_t iTimeoutMs = 10000) = 0; }; }; diff --git a/include/cecc.h b/include/cecc.h index 7e3512f..779f211 100644 --- a/include/cecc.h +++ b/include/cecc.h @@ -296,6 +296,12 @@ extern DECLSPEC void cec_rescan_devices(void); extern DECLSPEC int cec_is_libcec_active_source(void); +#ifdef __cplusplus +extern DECLSPEC int cec_get_device_information(const char *strPort, CEC::libcec_configuration *config, uint32_t iTimeoutMs); +#else +extern DECLSPEC int cec_get_device_information(const char *strPort, libcec_configuration *config, uint32_t iTimeoutMs); +#endif + #ifdef __cplusplus }; #endif diff --git a/include/cectypes.h b/include/cectypes.h index 095416e..28c8d92 100644 --- a/include/cectypes.h +++ b/include/cectypes.h @@ -79,7 +79,9 @@ namespace CEC { #define CEC_DEFAULT_SETTING_POWER_OFF_SHUTDOWN 1 #define CEC_DEFAULT_SETTING_POWER_OFF_SCREENSAVER 1 #define CEC_DEFAULT_SETTING_POWER_OFF_ON_STANDBY 1 +#define CEC_DEFAULT_SETTING_SHUTDOWN_ON_STANDBY 0 #define CEC_DEFAULT_SETTING_SEND_INACTIVE_SOURCE 1 +#define CEC_DEFAULT_SETTING_POWER_OFF_DEVICES_STANDBY 1 #define CEC_DEFAULT_TRANSMIT_RETRY_WAIT 500 #define CEC_DEFAULT_TRANSMIT_TIMEOUT 1000 @@ -469,7 +471,8 @@ typedef enum cec_user_control_code CEC_USER_CONTROL_CODE_F5 = 0x75, CEC_USER_CONTROL_CODE_DATA = 0x76, CEC_USER_CONTROL_CODE_AN_RETURN = 0x91, - CEC_USER_CONTROL_CODE_MAX = 0x91, + CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST = 0x96, + CEC_USER_CONTROL_CODE_MAX = 0x96, CEC_USER_CONTROL_CODE_UNKNOWN } cec_user_control_code; @@ -600,6 +603,21 @@ typedef enum cec_adapter_messagecode MSGCODE_START_BOOTLOADER, MSGCODE_SET_POWERSTATE, MSGCODE_SET_CONTROLLED, + MSGCODE_GET_AUTO_ENABLED, + MSGCODE_SET_AUTO_ENABLED, + MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS, + MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS, + MSGCODE_GET_LOGICAL_ADDRESS_MASK, + MSGCODE_SET_LOGICAL_ADDRESS_MASK, + MSGCODE_GET_PHYSICAL_ADDRESS, + MSGCODE_SET_PHYSICAL_ADDRESS, + MSGCODE_GET_DEVICE_TYPE, + MSGCODE_SET_DEVICE_TYPE, + MSGCODE_GET_HDMI_VERSION, + MSGCODE_SET_HDMI_VERSION, + MSGCODE_GET_OSD_NAME, + MSGCODE_SET_OSD_NAME, + MSGCODE_WRITE_EEPROM, MSGCODE_FRAME_EOM = 0x80, MSGCODE_FRAME_ACK = 0x40, } cec_adapter_messagecode; @@ -818,7 +836,7 @@ typedef struct cec_device_type_list return bReturn; } - bool IsEmpty() + bool IsEmpty() const { bool bReturn(true); for (unsigned int iPtr = 0; bReturn && iPtr < 5; iPtr++) @@ -906,12 +924,29 @@ typedef struct cec_logical_addresses #endif } cec_logical_addresses; +typedef enum libcec_alert +{ + CEC_ALERT_SERVICE_DEVICE +} libcec_alert; + +typedef enum libcec_parameter_type +{ + CEC_PARAMETER_TYPE_STRING +} libcec_parameter_type; + +struct libcec_parameter +{ + libcec_parameter_type paramType; + void* paramData; +}; + struct libcec_configuration; typedef int (CEC_CDECL* CBCecLogMessageType)(void *param, const cec_log_message &); typedef int (CEC_CDECL* CBCecKeyPressType)(void *param, const cec_keypress &); typedef int (CEC_CDECL* CBCecCommandType)(void *param, const cec_command &); typedef int (CEC_CDECL* CBCecConfigurationChangedType)(void *param, const libcec_configuration &); +typedef int (CEC_CDECL* CBCecAlertType)(void *param, const libcec_alert, const libcec_parameter &); typedef struct ICECCallbacks { @@ -942,6 +977,14 @@ typedef struct ICECCallbacks * @return 1 when ok, 0 otherwise */ CBCecConfigurationChangedType CBCecConfigurationChanged; + + /*! + * @Brief Transfer a libcec alert message from libCEC to the client + * @Param alert The alert type transfer. + * @Param data Misc. additional information. + * @return 1 when ok, 0 otherwise + */ + CBCecAlertType CBCecAlert; } ICECCallbacks; typedef enum cec_client_version @@ -949,7 +992,9 @@ typedef enum cec_client_version CEC_CLIENT_VERSION_PRE_1_5 = 0, CEC_CLIENT_VERSION_1_5_0 = 0x1500, CEC_CLIENT_VERSION_1_5_1 = 0x1501, - CEC_CLIENT_VERSION_1_5_2 = 0x1502 + CEC_CLIENT_VERSION_1_5_2 = 0x1502, + CEC_CLIENT_VERSION_1_5_3 = 0x1503, + CEC_CLIENT_VERSION_1_6_0 = 0x1600 } cec_client_version; typedef enum cec_server_version @@ -957,7 +1002,9 @@ typedef enum cec_server_version CEC_SERVER_VERSION_PRE_1_5 = 0, CEC_SERVER_VERSION_1_5_0 = 0x1500, CEC_SERVER_VERSION_1_5_1 = 0x1501, - CEC_SERVER_VERSION_1_5_2 = 0x1502 + CEC_SERVER_VERSION_1_5_2 = 0x1502, + CEC_SERVER_VERSION_1_5_3 = 0x1503, + CEC_SERVER_VERSION_1_6_0 = 0x1600 } cec_server_version; typedef struct libcec_configuration @@ -980,12 +1027,17 @@ typedef struct libcec_configuration uint8_t bUseTVMenuLanguage; /*!< use the menu language of the TV in the player application */ uint8_t bActivateSource; /*!< make libCEC the active source on the bus when starting the player application */ uint8_t bPowerOffScreensaver; /*!< put devices in standby mode when activating the screensaver */ - uint8_t bPowerOffOnStandby; /*!< put this PC in standby mode when the TV is switched off */ + uint8_t bPowerOffOnStandby; /*!< put this PC in standby mode when the TV is switched off. only used when bShutdownOnStandby = 0 */ uint8_t bSendInactiveSource; /*!< send an 'inactive source' message when stopping the player. added in 1.5.1 */ void * callbackParam; /*!< the object to pass along with a call of the callback methods. NULL to ignore */ ICECCallbacks * callbacks; /*!< the callback methods to use. set this to NULL when not using callbacks */ + cec_logical_addresses logicalAddresses; /*!< the current logical addresses. read-only. added in 1.5.3 */ + uint16_t iFirmwareVersion; /*!< the firmware version of the adapter. added in 1.6.0 */ + uint8_t bPowerOffDevicesOnStandby; /*!< put devices in standby when the PC/player is put in standby. added in 1.6.0 */ + uint8_t bShutdownOnStandby; /*!< shutdown this PC when the TV is switched off. only used when bPowerOffOnStandby = 0. added in 1.6.0 */ + #ifdef __cplusplus void Clear(void) { @@ -1012,7 +1064,11 @@ typedef struct libcec_configuration #endif bPowerOffScreensaver = CEC_DEFAULT_SETTING_POWER_OFF_SCREENSAVER; bPowerOffOnStandby = CEC_DEFAULT_SETTING_POWER_OFF_ON_STANDBY; + bShutdownOnStandby = CEC_DEFAULT_SETTING_SHUTDOWN_ON_STANDBY; bSendInactiveSource = CEC_DEFAULT_SETTING_SEND_INACTIVE_SOURCE; + logicalAddresses.Clear(); + iFirmwareVersion = CEC_FW_VERSION_UNKNOWN; + bPowerOffDevicesOnStandby = CEC_DEFAULT_SETTING_POWER_OFF_DEVICES_STANDBY; callbackParam = NULL; callbacks = NULL; diff --git a/project/cec-config.rc b/project/cec-config.rc index 180f4f0..cc3fb13 100644 Binary files a/project/cec-config.rc and b/project/cec-config.rc differ diff --git a/project/cec-config.vcxproj b/project/cec-config.vcxproj index 2243ff9..6ca84e4 100644 --- a/project/cec-config.vcxproj +++ b/project/cec-config.vcxproj @@ -173,6 +173,7 @@ + diff --git a/project/cec-config.vcxproj.filters b/project/cec-config.vcxproj.filters index 0d3f647..2ee5127 100644 --- a/project/cec-config.vcxproj.filters +++ b/project/cec-config.vcxproj.filters @@ -21,6 +21,9 @@ + + platform + diff --git a/project/libcec.rc b/project/libcec.rc index 4de5b4f..c51d36b 100644 Binary files a/project/libcec.rc and b/project/libcec.rc differ diff --git a/project/libcec.vcxproj b/project/libcec.vcxproj index 3c1e89b..aed370c 100644 --- a/project/libcec.vcxproj +++ b/project/libcec.vcxproj @@ -24,9 +24,11 @@ + + @@ -56,8 +58,11 @@ + + + @@ -73,6 +78,7 @@ + diff --git a/project/libcec.vcxproj.filters b/project/libcec.vcxproj.filters index 56f1a46..fdd9df3 100644 --- a/project/libcec.vcxproj.filters +++ b/project/libcec.vcxproj.filters @@ -131,6 +131,12 @@ implementations + + adapter + + + adapter + @@ -167,6 +173,9 @@ devices + + platform\windows + platform\windows @@ -179,6 +188,15 @@ implementations + + adapter + + + adapter + + + adapter + diff --git a/project/testclient.rc b/project/testclient.rc index 15edfb5..d4bfa3b 100644 Binary files a/project/testclient.rc and b/project/testclient.rc differ diff --git a/project/testclient.vcxproj b/project/testclient.vcxproj index fb19572..0862f27 100644 --- a/project/testclient.vcxproj +++ b/project/testclient.vcxproj @@ -170,6 +170,7 @@ + diff --git a/project/testclient.vcxproj.filters b/project/testclient.vcxproj.filters index 70acf3d..0fd21fb 100644 --- a/project/testclient.vcxproj.filters +++ b/project/testclient.vcxproj.filters @@ -18,6 +18,9 @@ + + platform + diff --git a/src/CecSharpTester/Properties/AssemblyInfo.cs b/src/CecSharpTester/Properties/AssemblyInfo.cs index e12aca9..bc11567 100644 --- a/src/CecSharpTester/Properties/AssemblyInfo.cs +++ b/src/CecSharpTester/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.5.2.0")] -[assembly: AssemblyFileVersion("1.5.2.0")] +[assembly: AssemblyVersion("1.6.0.0")] +[assembly: AssemblyFileVersion("1.6.0.0")] diff --git a/src/LibCecSharp/AssemblyInfo.cpp b/src/LibCecSharp/AssemblyInfo.cpp index 5dfbef2..d70e9d0 100644 --- a/src/LibCecSharp/AssemblyInfo.cpp +++ b/src/LibCecSharp/AssemblyInfo.cpp @@ -13,7 +13,7 @@ using namespace System::Security::Permissions; [assembly:AssemblyTrademarkAttribute("")]; [assembly:AssemblyCultureAttribute("")]; -[assembly:AssemblyVersionAttribute("1.5.2.0")]; +[assembly:AssemblyVersionAttribute("1.6.0.0")]; [assembly:ComVisible(false)]; [assembly:CLSCompliantAttribute(true)]; diff --git a/src/LibCecSharp/CecSharpTypes.h b/src/LibCecSharp/CecSharpTypes.h index 8cdaa89..cc888c7 100644 --- a/src/LibCecSharp/CecSharpTypes.h +++ b/src/LibCecSharp/CecSharpTypes.h @@ -83,6 +83,29 @@ namespace CecSharp Broadcast = 15 }; + public enum class CecAlert + { + ServiceDevice = 1 + }; + + public enum class CecParameterType + { + ParameterTypeString = 1 + }; + + public ref class CecParameter + { + public: + CecParameter(CecParameterType type, System::String ^ strData) + { + Type = type; + Data = strData; + } + + property CecParameterType Type; + property System::String ^ Data; + }; + public enum class CecPowerStatus { On = 0x00, @@ -328,7 +351,9 @@ namespace CecSharp VersionPre1_5 = 0, Version1_5_0 = 0x1500, Version1_5_1 = 0x1501, - Version1_5_2 = 0x1502 + Version1_5_2 = 0x1502, + Version1_5_3 = 0x1503, + Version1_6_0 = 0x1600 }; public enum class CecServerVersion @@ -336,7 +361,9 @@ namespace CecSharp VersionPre1_5 = 0, Version1_5_0 = 0x1500, Version1_5_1 = 0x1501, - Version1_5_2 = 0x1502 + Version1_5_2 = 0x1502, + Version1_5_3 = 0x1503, + Version1_6_0 = 0x1600 }; public ref class CecAdapter @@ -552,6 +579,9 @@ namespace CecSharp PowerOffScreensaver = CEC_DEFAULT_SETTING_POWER_OFF_SCREENSAVER == 1; PowerOffOnStandby = CEC_DEFAULT_SETTING_POWER_OFF_ON_STANDBY == 1; SendInactiveSource = CEC_DEFAULT_SETTING_SEND_INACTIVE_SOURCE == 1; + LogicalAddresses = gcnew CecLogicalAddresses(); + FirmwareVersion = 1; + PowerOffDevicesOnStandby = CEC_DEFAULT_SETTING_POWER_OFF_DEVICES_STANDBY == 1; } void SetCallbacks(CecCallbackMethods ^callbacks) @@ -591,7 +621,23 @@ namespace CecSharp PowerOffScreensaver = config.bPowerOffScreensaver == 1; PowerOffOnStandby = config.bPowerOffOnStandby == 1; - SendInactiveSource = config.bSendInactiveSource == 1; + + if (ServerVersion >= CecServerVersion::Version1_5_1) + SendInactiveSource = config.bSendInactiveSource == 1; + + if (ServerVersion >= CecServerVersion::Version1_5_3) + { + LogicalAddresses->Clear(); + for (uint8_t iPtr = 0; iPtr <= 16; iPtr++) + if (config.logicalAddresses[iPtr]) + LogicalAddresses->Set((CecLogicalAddress)iPtr); + } + + if (ServerVersion >= CecServerVersion::Version1_6_0) + { + FirmwareVersion = config.iFirmwareVersion; + PowerOffDevicesOnStandby = config.bPowerOffDevicesOnStandby == 1; + } } property System::String ^ DeviceName; @@ -613,6 +659,9 @@ namespace CecSharp property bool PowerOffScreensaver; property bool PowerOffOnStandby; property bool SendInactiveSource; + property CecLogicalAddresses ^LogicalAddresses; + property uint16_t FirmwareVersion; + property bool PowerOffDevicesOnStandby; property CecCallbackMethods ^ Callbacks; }; @@ -624,11 +673,13 @@ namespace CecSharp typedef int (__stdcall *KEYCB) (const CEC::cec_keypress &key); typedef int (__stdcall *COMMANDCB)(const CEC::cec_command &command); typedef int (__stdcall *CONFIGCB) (const CEC::libcec_configuration &config); + typedef int (__stdcall *ALERTCB) (const CEC::libcec_alert, const CEC::libcec_parameter &data); static LOGCB g_logCB; static KEYCB g_keyCB; static COMMANDCB g_commandCB; static CONFIGCB g_configCB; + static ALERTCB g_alertCB; static CEC::ICECCallbacks g_cecCallbacks; int CecLogMessageCB(void *cbParam, const CEC::cec_log_message &message) @@ -659,12 +710,20 @@ namespace CecSharp return 0; } + int CecAlertCB(void *cbParam, const CEC::libcec_alert alert, const CEC::libcec_parameter &data) + { + if (g_alertCB) + return g_alertCB(alert, data); + return 0; + } + #pragma managed // delegates for the unmanaged callback methods public delegate int CecLogMessageManagedDelegate(const CEC::cec_log_message &); public delegate int CecKeyPressManagedDelegate(const CEC::cec_keypress &); public delegate int CecCommandManagedDelegate(const CEC::cec_command &); public delegate int CecConfigManagedDelegate(const CEC::libcec_configuration &); + public delegate int CecAlertManagedDelegate(const CEC::libcec_alert, const CEC::libcec_parameter &); // callback method interface public ref class CecCallbackMethods @@ -725,6 +784,12 @@ namespace CecSharp { return 0; } + + virtual int ReceiveAlert(CecAlert alert, CecParameter ^ data) + { + return 0; + } + protected: // managed callback methods int CecLogMessageManaged(const CEC::cec_log_message &message) @@ -768,6 +833,22 @@ namespace CecSharp return iReturn; } + int CecAlertManaged(const CEC::libcec_alert alert, const CEC::libcec_parameter &data) + { + int iReturn(0); + if (m_bHasCallbacks) + { + CecParameterType newType = (CecParameterType)data.paramType; + if (newType == CecParameterType::ParameterTypeString) + { + System::String ^ newData = gcnew System::String((const char *)data.paramData, 0, 128); + CecParameter ^ newParam = gcnew CecParameter(newType, newData); + iReturn = m_callbacks->ReceiveAlert((CecAlert)alert, newParam); + } + } + return iReturn; + } + void DestroyDelegates() { m_bHasCallbacks = false; @@ -812,6 +893,12 @@ namespace CecSharp g_configCB = static_cast(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_configDelegate).ToPointer()); g_cecCallbacks.CBCecConfigurationChanged = CecConfigCB; + // create the delegate method for the alert callback + m_alertDelegate = gcnew CecAlertManagedDelegate(this, &CecCallbackMethods::CecAlertManaged); + m_alertGCHandle = System::Runtime::InteropServices::GCHandle::Alloc(m_alertDelegate); + g_alertCB = static_cast(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_alertDelegate).ToPointer()); + g_cecCallbacks.CBCecAlert = CecAlertCB; + delete context; m_bDelegatesCreated = true; } @@ -833,6 +920,10 @@ namespace CecSharp static System::Runtime::InteropServices::GCHandle m_configGCHandle; CONFIGCB m_configCallback; + CecAlertManagedDelegate ^ m_alertDelegate; + static System::Runtime::InteropServices::GCHandle m_alertGCHandle; + CONFIGCB m_alertCallback; + CecCallbackMethods ^ m_callbacks; bool m_bHasCallbacks; bool m_bDelegatesCreated; diff --git a/src/LibCecSharp/LibCecSharp.cpp b/src/LibCecSharp/LibCecSharp.cpp index 76dd006..2338671 100644 --- a/src/LibCecSharp/LibCecSharp.cpp +++ b/src/LibCecSharp/LibCecSharp.cpp @@ -117,7 +117,13 @@ namespace CecSharp } config.bPowerOffScreensaver = netConfig->PowerOffScreensaver ? 1 : 0; config.bPowerOffOnStandby = netConfig->PowerOffOnStandby ? 1 : 0; - config.bSendInactiveSource = netConfig->SendInactiveSource ? 1 : 0; + + if (netConfig->ServerVersion >= CecServerVersion::Version1_5_1) + config.bSendInactiveSource = netConfig->SendInactiveSource ? 1 : 0; + + if (netConfig->ServerVersion >= CecServerVersion::Version1_6_0) + config.bPowerOffDevicesOnStandby = netConfig->PowerOffDevicesOnStandby ? 1 : 0; + config.callbacks = &g_cecCallbacks; } @@ -481,6 +487,31 @@ namespace CecSharp return bReturn; } + bool IsLibCECActiveSource() + { + return m_libCec->IsLibCECActiveSource(); + } + + bool GetDeviceInformation(String ^ port, LibCECConfiguration ^configuration, uint32_t timeoutMs) + { + bool bReturn(false); + marshal_context ^ context = gcnew marshal_context(); + + libcec_configuration config; + config.Clear(); + + const char* strPortC = port->Length > 0 ? context->marshal_as(port) : NULL; + + if (m_libCec->GetDeviceInformation(strPortC, &config, timeoutMs)) + { + configuration->Update(config); + bReturn = true; + } + + delete context; + return bReturn; + } + String ^ ToString(CecLogicalAddress iAddress) { const char *retVal = m_libCec->ToString((cec_logical_address)iAddress); diff --git a/src/cec-config-gui/CecConfigGUI.cs b/src/cec-config-gui/CecConfigGUI.cs index 94ce2ad..8fb066f 100644 --- a/src/cec-config-gui/CecConfigGUI.cs +++ b/src/cec-config-gui/CecConfigGUI.cs @@ -321,6 +321,12 @@ namespace CecConfigGui } SetControlVisible(pProgress, false); break; + case UpdateEventType.ExitApplication: + ActiveProcess = null; + SetControlsEnabled(false); + SetControlVisible(pProgress, false); + Application.Exit(); + break; } } @@ -545,100 +551,93 @@ namespace CecConfigGui Config.WakeDevices = WakeDevices; Config.PowerOffDevices = PowerOffDevices; - if (!Lib.CanPersistConfiguration()) + /* save settings in the eeprom */ + Lib.PersistConfiguration(Config); + + /* and in xml */ + if (ActiveProcess == null) { - if (ActiveProcess == null) - { - SetControlsEnabled(false); - string xbmcDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\XBMC\userdata\peripheral_data"; - string defaultDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + SetControlsEnabled(false); + string xbmcDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\XBMC\userdata\peripheral_data"; + string defaultDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); - SaveFileDialog dialog = new SaveFileDialog() + SaveFileDialog dialog = new SaveFileDialog() + { + Title = "Where do you want to store the settings?", + InitialDirectory = Directory.Exists(xbmcDir) ? xbmcDir : defaultDir, + FileName = "usb_2548_1001.xml", + Filter = "xml files (*.xml)|*.xml|All files (*.*)|*.*", + FilterIndex = 1 + }; + + if (dialog.ShowDialog() == DialogResult.OK) + { + FileStream fs = null; + string error = string.Empty; + try + { + fs = (FileStream)dialog.OpenFile(); + } + catch (Exception ex) { - Title = "Where do you want to store the settings?", - InitialDirectory = Directory.Exists(xbmcDir) ? xbmcDir : defaultDir, - FileName = "usb_2548_1001.xml", - Filter = "xml files (*.xml)|*.xml|All files (*.*)|*.*", - FilterIndex = 1 - }; - - if (dialog.ShowDialog() == DialogResult.OK) + error = ex.Message; + } + if (fs == null) { - FileStream fs = null; - string error = string.Empty; - try - { - fs = (FileStream)dialog.OpenFile(); - } - catch (Exception ex) - { - error = ex.Message; - } - if (fs == null) - { - MessageBox.Show("Cannot open '" + dialog.FileName + "' for writing" + (error.Length > 0 ? ": " + error : string.Empty ), "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - else - { - StreamWriter writer = new StreamWriter(fs); - StringBuilder output = new StringBuilder(); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - - // only supported by 1.5.0+ clients - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - output.AppendLine(""); - - output.Append(""); - - output.Append(""); - - // only supported by 1.5.1+ clients - output.AppendLine(""); - output.AppendLine(""); - - output.AppendLine(""); - writer.Write(output.ToString()); - writer.Close(); - fs.Close(); - fs.Dispose(); - MessageBox.Show("Settings are stored.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Information); - } + MessageBox.Show("Cannot open '" + dialog.FileName + "' for writing" + (error.Length > 0 ? ": " + error : string.Empty ), "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + else + { + StreamWriter writer = new StreamWriter(fs); + StringBuilder output = new StringBuilder(); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + + // only supported by 1.5.0+ clients + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + output.AppendLine(""); + + output.Append(""); + + output.Append(""); + + // only supported by 1.5.1+ clients + output.AppendLine(""); + output.AppendLine(""); + + output.AppendLine(""); + writer.Write(output.ToString()); + writer.Close(); + fs.Close(); + fs.Dispose(); + MessageBox.Show("Settings are stored.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Information); } - SetControlsEnabled(true); } + SetControlsEnabled(true); } - else - { - if (!Lib.PersistConfiguration(Config)) - MessageBox.Show("Could not persist the new settings.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Error); - else - MessageBox.Show("Settings are stored.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - SetControlsEnabled(true); } private void bReloadConfig_Click(object sender, EventArgs e) diff --git a/src/cec-config-gui/Properties/AssemblyInfo.cs b/src/cec-config-gui/Properties/AssemblyInfo.cs index a1206f9..829c847 100644 --- a/src/cec-config-gui/Properties/AssemblyInfo.cs +++ b/src/cec-config-gui/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.5.2.0")] -[assembly: AssemblyFileVersion("1.5.2.0")] +[assembly: AssemblyVersion("1.6.0.0")] +[assembly: AssemblyFileVersion("1.6.0.0")] diff --git a/src/cec-config-gui/actions/ConnectToDevice.cs b/src/cec-config-gui/actions/ConnectToDevice.cs index fedc313..8c198f4 100644 --- a/src/cec-config-gui/actions/ConnectToDevice.cs +++ b/src/cec-config-gui/actions/ConnectToDevice.cs @@ -22,7 +22,10 @@ namespace CecConfigGui.actions { DialogResult result = MessageBox.Show("Could not detect to any CEC adapter. Please check your configuration. Do you want to try again?", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.YesNo); if (result == DialogResult.No) - Application.Exit(); + { + SendEvent(UpdateEventType.ExitApplication); + return; + } else adapters = Lib.FindAdapters(string.Empty); } @@ -31,7 +34,10 @@ namespace CecConfigGui.actions { DialogResult result = MessageBox.Show("Could not connect to any CEC adapter. Please check your configuration. Do you want to try again?", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.YesNo); if (result == DialogResult.No) - Application.Exit(); + { + SendEvent(UpdateEventType.ExitApplication); + return; + } } SendEvent(UpdateEventType.ProgressBar, 20); diff --git a/src/cec-config-gui/actions/UpdateEvent.cs b/src/cec-config-gui/actions/UpdateEvent.cs index a7e2df6..4a3e701 100644 --- a/src/cec-config-gui/actions/UpdateEvent.cs +++ b/src/cec-config-gui/actions/UpdateEvent.cs @@ -17,7 +17,8 @@ namespace CecConfigGui AVRVendorId, Configuration, MenuLanguage, - PollDevices + PollDevices, + ExitApplication } public class UpdateEvent : EventArgs diff --git a/src/cec-config/cec-config.cpp b/src/cec-config/cec-config.cpp index 4563691..1e6c96f 100644 --- a/src/cec-config/cec-config.cpp +++ b/src/cec-config/cec-config.cpp @@ -145,6 +145,7 @@ void EnableCallbacks(ICECAdapter *adapter) g_callbacks.CBCecKeyPress = &CecKeyPress; g_callbacks.CBCecCommand = &CecCommand; g_callbacks.CBCecConfigurationChanged = NULL; + g_callbacks.CBCecAlert = NULL; adapter->EnableCallbacks(NULL, &g_callbacks); } @@ -228,18 +229,20 @@ bool OpenConnection(cec_device_type type = CEC_DEVICE_TYPE_RECORDING_DEVICE) int8_t FindPhysicalAddressPortNumber(void) { - PrintToStdOut("Enter the HDMI port number to which you connected your CEC adapter, followed by . Only port 1, 2, 3 or 4 are supported. Anything else will cancel this wizard."); + PrintToStdOut("Enter the HDMI port number to which you connected your CEC adapter, followed by . Valid ports are in the range 1-15. Anything else will cancel this wizard."); string input; getline(cin, input); cin.clear(); - if (input.empty() || (input != "1" && input != "2" && input != "3" && input != "4")) + if (input.empty()) return -1; - return (int8_t)atoi(input.c_str()); + + int hdmiport = atoi(input.c_str()); + return (hdmiport < 1 || hdmiport > 15) ? -1 : (int8_t)hdmiport; } cec_logical_address FindPhysicalAddressBaseDevice(void) { - PrintToStdOut("Press 1 of your CEC adapter is connected to your TV or\npress 2 if it's connected to an AVR, followed by . Anything else will cancel this wizard."); + PrintToStdOut("Press 1 if your CEC adapter is connected to your TV or\npress 2 if it's connected to an AVR, followed by . Anything else will cancel this wizard."); string input; getline(cin, input); diff --git a/src/lib/CECProcessor.cpp b/src/lib/CECProcessor.cpp index b455ac8..c7d586e 100644 --- a/src/lib/CECProcessor.cpp +++ b/src/lib/CECProcessor.cpp @@ -57,10 +57,9 @@ CCECProcessor::CCECProcessor(CLibCEC *controller, libcec_configuration *configur m_iRetryLineTimeout(3), m_iLastTransmission(0) { - m_logicalAddresses.Clear(); CreateBusDevices(); m_configuration.Clear(); - m_configuration.serverVersion = configuration->serverVersion; + m_configuration.serverVersion = CEC_SERVER_VERSION_1_6_0; SetConfiguration(configuration); if (m_configuration.tvVendor != CEC_VENDOR_UNKNOWN) @@ -78,7 +77,7 @@ CCECProcessor::CCECProcessor(CLibCEC *controller, const char *strDeviceName, con m_iLastTransmission(0) { m_configuration.Clear(); - m_configuration.serverVersion = CEC_SERVER_VERSION_1_5_2; + m_configuration.serverVersion = CEC_SERVER_VERSION_1_6_0; // client version < 1.5.0 m_configuration.clientVersion = (uint32_t)CEC_CLIENT_VERSION_PRE_1_5; @@ -90,7 +89,6 @@ CCECProcessor::CCECProcessor(CLibCEC *controller, const char *strDeviceName, con if (m_configuration.deviceTypes.IsEmpty()) m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE); - m_logicalAddresses.Clear(); CreateBusDevices(); } @@ -134,7 +132,10 @@ CCECProcessor::~CCECProcessor(void) Close(); for (unsigned int iPtr = 0; iPtr < 16; iPtr++) + { delete m_busDevices[iPtr]; + m_busDevices[iPtr] = NULL; + } } void CCECProcessor::Close(void) @@ -152,13 +153,12 @@ void CCECProcessor::Close(void) if (bClose && m_communication) { - m_communication->Close(); delete m_communication; m_communication = NULL; } } -bool CCECProcessor::OpenConnection(const char *strPort, uint16_t iBaudRate, uint32_t iTimeoutMs) +bool CCECProcessor::OpenConnection(const char *strPort, uint16_t iBaudRate, uint32_t iTimeoutMs, bool bStartListening /* = true */) { bool bReturn(false); Close(); @@ -174,18 +174,11 @@ bool CCECProcessor::OpenConnection(const char *strPort, uint16_t iBaudRate, uint m_bConnectionOpened = (m_communication != NULL); } - /* check for an already opened connection */ - if (m_communication->IsOpen()) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "connection already opened"); - return bReturn; - } - CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT); /* open a new connection */ unsigned iConnectTry(0); - while (timeout.TimeLeft() > 0 && (bReturn = m_communication->Open(this, (timeout.TimeLeft() / CEC_CONNECT_TRIES))) == 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(); @@ -193,7 +186,24 @@ bool CCECProcessor::OpenConnection(const char *strPort, uint16_t iBaudRate, uint } if (bReturn) - CLibCEC::AddLog(CEC_LOG_NOTICE, "connected to the CEC adapter. firmware version = %d, client version = %s", m_communication->GetFirmwareVersion(), ToString((cec_client_version)m_configuration.clientVersion)); + { + m_configuration.iFirmwareVersion = m_communication->GetFirmwareVersion(); + CLibCEC::AddLog(CEC_LOG_NOTICE, "connected to the CEC adapter. firmware version = %d, client version = %s", m_configuration.iFirmwareVersion, ToString((cec_client_version)m_configuration.clientVersion)); + } + + if (m_configuration.bGetSettingsFromROM == 1) + { + libcec_configuration config; + config.Clear(); + m_communication->GetConfiguration(&config); + + CLockObject lock(m_mutex); + if (!config.deviceTypes.IsEmpty()) + m_configuration.deviceTypes = config.deviceTypes; + if (config.iPhysicalAddress > 0) + m_configuration.iPhysicalAddress = config.iPhysicalAddress; + snprintf(m_configuration.strDeviceName, 13, "%s", config.strDeviceName); + } return bReturn; } @@ -215,8 +225,8 @@ bool CCECProcessor::Initialise(void) bool bReturn(false); { CLockObject lock(m_mutex); - if (!m_logicalAddresses.IsEmpty()) - m_logicalAddresses.Clear(); + if (!m_configuration.logicalAddresses.IsEmpty()) + m_configuration.logicalAddresses.Clear(); if (!FindLogicalAddresses()) { @@ -225,11 +235,11 @@ bool CCECProcessor::Initialise(void) } /* only set our OSD name for the primary device */ - m_busDevices[m_logicalAddresses.primary]->m_strDeviceName = m_configuration.strDeviceName; + m_busDevices[m_configuration.logicalAddresses.primary]->m_strDeviceName = m_configuration.strDeviceName; /* make the primary device the active source if the option is set */ if (m_configuration.bActivateSource == 1) - m_busDevices[m_logicalAddresses.primary]->m_bActiveSource = true; + m_busDevices[m_configuration.logicalAddresses.primary]->m_bActiveSource = true; } /* get the vendor id from the TV, so we are using the correct handler */ @@ -238,15 +248,15 @@ bool CCECProcessor::Initialise(void) if (m_configuration.iPhysicalAddress != 0) { CLibCEC::AddLog(CEC_LOG_NOTICE, "setting the physical address to %4x", m_configuration.iPhysicalAddress); - m_busDevices[m_logicalAddresses.primary]->m_iPhysicalAddress = m_configuration.iPhysicalAddress; - if ((bReturn = m_busDevices[m_logicalAddresses.primary]->TransmitPhysicalAddress()) == false) + m_busDevices[m_configuration.logicalAddresses.primary]->m_iPhysicalAddress = m_configuration.iPhysicalAddress; + if ((bReturn = m_busDevices[m_configuration.logicalAddresses.primary]->TransmitPhysicalAddress()) == false) CLibCEC::AddLog(CEC_LOG_ERROR, "unable to set the physical address to %4x", m_configuration.iPhysicalAddress); } else if (m_configuration.iPhysicalAddress == 0 && (bReturn = SetHDMIPort(m_configuration.baseDevice, m_configuration.iHDMIPort, true)) == false) CLibCEC::AddLog(CEC_LOG_ERROR, "unable to set HDMI port %d on %s (%x)", m_configuration.iHDMIPort, ToString(m_configuration.baseDevice), (uint8_t)m_configuration.baseDevice); - if (m_configuration.bActivateSource == 1) - m_busDevices[m_logicalAddresses.primary]->ActivateSource(); + if (bReturn && m_configuration.bActivateSource == 1) + m_busDevices[m_configuration.logicalAddresses.primary]->ActivateSource(); SetInitialised(bReturn); if (bReturn) @@ -289,7 +299,7 @@ bool CCECProcessor::TryLogicalAddress(cec_logical_address address) { if (m_busDevices[address]->TryLogicalAddress()) { - m_logicalAddresses.Set(address); + m_configuration.logicalAddresses.Set(address); return true; } @@ -335,7 +345,7 @@ bool CCECProcessor::ChangeDeviceType(cec_device_type from, cec_device_type to) CLockObject lock(m_mutex); CCECBusDevice *previousDevice = GetDeviceByType(from); - m_logicalAddresses.primary = CECDEVICE_UNKNOWN; + m_configuration.logicalAddresses.primary = CECDEVICE_UNKNOWN; for (unsigned int iPtr = 0; iPtr < 5; iPtr++) { @@ -407,7 +417,7 @@ bool CCECProcessor::ChangeDeviceType(cec_device_type from, cec_device_type to) bool CCECProcessor::FindLogicalAddresses(void) { bool bReturn(true); - m_logicalAddresses.Clear(); + m_configuration.logicalAddresses.Clear(); if (m_configuration.deviceTypes.IsEmpty()) { @@ -433,7 +443,7 @@ bool CCECProcessor::FindLogicalAddresses(void) } if (bReturn) - SetAckMask(m_logicalAddresses.AckMask()); + SetAckMask(m_configuration.logicalAddresses.AckMask()); return bReturn; } @@ -448,23 +458,27 @@ void CCECProcessor::ReplaceHandlers(void) bool CCECProcessor::OnCommandReceived(const cec_command &command) { - ParseCommand(command); - return true; + return m_inBuffer.Push(command); } void *CCECProcessor::Process(void) { CLibCEC::AddLog(CEC_LOG_DEBUG, "processor thread started"); + cec_command command; + command.Clear(); + while (!IsStopped() && m_communication->IsOpen()) { + if (m_inBuffer.Pop(command, 500)) + ParseCommand(command); + if (IsInitialised()) { ReplaceHandlers(); m_controller->CheckKeypressTimeout(); } - Sleep(5); } return NULL; @@ -477,13 +491,13 @@ bool CCECProcessor::SetActiveSource(cec_device_type type /* = CEC_DEVICE_TYPE_RE if (!IsRunning()) return bReturn; - cec_logical_address addr = m_logicalAddresses.primary; + cec_logical_address addr = m_configuration.logicalAddresses.primary; if (type != CEC_DEVICE_TYPE_RESERVED) { for (uint8_t iPtr = 0; iPtr <= 11; iPtr++) { - if (m_logicalAddresses[iPtr] && m_busDevices[iPtr]->m_type == type) + if (m_configuration.logicalAddresses[iPtr] && m_busDevices[iPtr]->m_type == type) { addr = (cec_logical_address) iPtr; break; @@ -502,7 +516,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(); @@ -566,6 +581,12 @@ bool CCECProcessor::SetHDMIPort(cec_logical_address iBaseDevice, uint8_t iPort, { bool bReturn(false); + // limit the HDMI port range to 1-15 + if (iPort < 1) + iPort = 1; + if (iPort > 15) + iPort = 15; + { CLockObject lock(m_mutex); m_configuration.baseDevice = iBaseDevice; @@ -620,8 +641,8 @@ bool CCECProcessor::TransmitInactiveSource(void) if (!IsRunning()) return false; - if (!m_logicalAddresses.IsEmpty() && m_busDevices[m_logicalAddresses.primary]) - return m_busDevices[m_logicalAddresses.primary]->TransmitInactiveSource(); + if (!m_configuration.logicalAddresses.IsEmpty() && m_busDevices[m_configuration.logicalAddresses.primary]) + return m_busDevices[m_configuration.logicalAddresses.primary]->TransmitInactiveSource(); return false; } @@ -640,12 +661,12 @@ void CCECProcessor::LogOutput(const cec_command &data) bool CCECProcessor::SetLogicalAddress(cec_logical_address iLogicalAddress) { CLockObject lock(m_mutex); - if (m_logicalAddresses.primary != iLogicalAddress) + if (m_configuration.logicalAddresses.primary != iLogicalAddress) { CLibCEC::AddLog(CEC_LOG_NOTICE, "<< setting primary logical address to %1x", iLogicalAddress); - m_logicalAddresses.primary = iLogicalAddress; - m_logicalAddresses.Set(iLogicalAddress); - return SetAckMask(m_logicalAddresses.AckMask()); + m_configuration.logicalAddresses.primary = iLogicalAddress; + m_configuration.logicalAddresses.Set(iLogicalAddress); + return SetAckMask(m_configuration.logicalAddresses.AckMask()); } return true; @@ -655,12 +676,12 @@ bool CCECProcessor::SetMenuState(cec_menu_state state, bool bSendUpdate /* = tru { for (uint8_t iPtr = 0; iPtr < 16; iPtr++) { - if (m_logicalAddresses[iPtr]) + if (m_configuration.logicalAddresses[iPtr]) m_busDevices[iPtr]->SetMenuState(state); } if (bSendUpdate) - m_busDevices[m_logicalAddresses.primary]->TransmitMenuState(CECDEVICE_TV); + m_busDevices[m_configuration.logicalAddresses.primary]->TransmitMenuState(CECDEVICE_TV); return true; } @@ -677,11 +698,11 @@ bool CCECProcessor::SetPhysicalAddress(uint16_t iPhysicalAddress, bool bSendUpda m_configuration.iPhysicalAddress = iPhysicalAddress; CLibCEC::AddLog(CEC_LOG_DEBUG, "setting physical address to '%4x'", iPhysicalAddress); - if (!m_logicalAddresses.IsEmpty()) + if (!m_configuration.logicalAddresses.IsEmpty()) { bool bWasActiveSource(false); for (uint8_t iPtr = 0; iPtr < 15; iPtr++) - if (m_logicalAddresses[iPtr]) + if (m_configuration.logicalAddresses[iPtr]) { bWasActiveSource |= m_busDevices[iPtr]->IsActiveSource(); m_busDevices[iPtr]->SetInactiveSource(); @@ -703,7 +724,16 @@ bool CCECProcessor::SetPhysicalAddress(uint16_t iPhysicalAddress, bool bSendUpda SetActiveView(); if (bReturn) - CLibCEC::ConfigurationChanged(m_configuration); + { + libcec_configuration config; + { + CLockObject lock(m_mutex); + config = m_configuration; + } + + PersistConfiguration(&config); + CLibCEC::ConfigurationChanged(config); + } return bReturn; } @@ -720,16 +750,16 @@ bool CCECProcessor::SwitchMonitoring(bool bEnable) if (bEnable) return SetAckMask(0); else - return SetAckMask(m_logicalAddresses.AckMask()); + return SetAckMask(m_configuration.logicalAddresses.AckMask()); } bool CCECProcessor::PollDevice(cec_logical_address iAddress) { if (iAddress != CECDEVICE_UNKNOWN && m_busDevices[iAddress]) { - return m_logicalAddresses.primary == CECDEVICE_UNKNOWN ? + return m_configuration.logicalAddresses.primary == CECDEVICE_UNKNOWN ? m_busDevices[iAddress]->TransmitPoll(iAddress) : - m_busDevices[m_logicalAddresses.primary]->TransmitPoll(iAddress); + m_busDevices[m_configuration.logicalAddresses.primary]->TransmitPoll(iAddress); } return false; } @@ -761,15 +791,15 @@ 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_logicalAddresses.primary]->GetPhysicalAddress(false) == iPhysicalAddress) - return m_busDevices[m_logicalAddresses.primary]; + if (m_busDevices[m_configuration.logicalAddresses.primary]->GetPhysicalAddress(false) == iPhysicalAddress) + return m_busDevices[m_configuration.logicalAddresses.primary]; 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; @@ -785,7 +815,7 @@ CCECBusDevice *CCECProcessor::GetDeviceByType(cec_device_type type) const for (uint8_t iPtr = 0; iPtr < 16; iPtr++) { - if (m_busDevices[iPtr]->m_type == type && m_logicalAddresses[iPtr]) + if (m_busDevices[iPtr]->m_type == type && m_configuration.logicalAddresses[iPtr]) { device = m_busDevices[iPtr]; break; @@ -798,7 +828,7 @@ CCECBusDevice *CCECProcessor::GetDeviceByType(cec_device_type type) const CCECBusDevice *CCECProcessor::GetPrimaryDevice(void) const { CCECBusDevice *device(NULL); - cec_logical_address primary = m_logicalAddresses.primary; + cec_logical_address primary = m_configuration.logicalAddresses.primary; if (primary != CECDEVICE_UNKNOWN) device = m_busDevices[primary]; return device; @@ -864,12 +894,14 @@ cec_logical_address CCECProcessor::GetActiveSource(void) bool CCECProcessor::IsActiveSource(cec_logical_address iAddress) { - return m_busDevices[iAddress]->IsActiveSource(); + return iAddress > CECDEVICE_TV && iAddress < CECDEVICE_BROADCAST ? + m_busDevices[iAddress]->IsActiveSource() : + false; } bool CCECProcessor::Transmit(const cec_command &data) { - if (m_logicalAddresses[(uint8_t)data.destination]) + if (m_configuration.logicalAddresses[(uint8_t)data.destination]) { CLibCEC::AddLog(CEC_LOG_WARNING, "not sending data to myself!"); return false; @@ -890,7 +922,7 @@ bool CCECProcessor::Transmit(const cec_command &data) iMaxTries = m_busDevices[data.initiator]->GetHandler()->GetTransmitRetries() + 1; } - return m_communication->Write(data, iMaxTries, m_iLineTimeout, m_iRetryLineTimeout) + return m_communication->Write(data, iMaxTries, m_iStandardLineTimeout, m_iRetryLineTimeout) == ADAPTER_MESSAGE_STATE_SENT_ACKED; } @@ -900,7 +932,7 @@ void CCECProcessor::TransmitAbort(cec_logical_address address, cec_opcode opcode cec_command command; // TODO - cec_command::Format(command, m_logicalAddresses.primary, address, CEC_OPCODE_FEATURE_ABORT); + cec_command::Format(command, m_configuration.logicalAddresses.primary, address, CEC_OPCODE_FEATURE_ABORT); command.parameters.PushBack((uint8_t)opcode); command.parameters.PushBack((uint8_t)reason); @@ -910,7 +942,9 @@ void CCECProcessor::TransmitAbort(cec_logical_address address, cec_opcode opcode void CCECProcessor::ParseCommand(const cec_command &command) { CStdString dataStr; - dataStr.Format(">> %1x%1x:%02x", command.initiator, command.destination, command.opcode); + dataStr.Format(">> %1x%1x", command.initiator, command.destination); + if (command.opcode_set == 1) + dataStr.AppendFormat(":%02x", command.opcode); for (uint8_t iPtr = 0; iPtr < command.parameters.size; iPtr++) dataStr.AppendFormat(":%02x", (unsigned int)command.parameters[iPtr]); CLibCEC::AddLog(CEC_LOG_TRAFFIC, dataStr.c_str()); @@ -949,8 +983,8 @@ bool CCECProcessor::IsPresentDeviceType(cec_device_type type) uint16_t CCECProcessor::GetPhysicalAddress(void) const { - if (!m_logicalAddresses.IsEmpty() && m_busDevices[m_logicalAddresses.primary]) - return m_busDevices[m_logicalAddresses.primary]->GetPhysicalAddress(false); + if (!m_configuration.logicalAddresses.IsEmpty() && m_busDevices[m_configuration.logicalAddresses.primary]) + return m_busDevices[m_configuration.logicalAddresses.primary]->GetPhysicalAddress(false); return false; } @@ -1376,6 +1410,10 @@ const char *CCECProcessor::ToString(const cec_client_version version) return "1.5.1"; case CEC_CLIENT_VERSION_1_5_2: return "1.5.2"; + case CEC_CLIENT_VERSION_1_5_3: + return "1.5.3"; + case CEC_CLIENT_VERSION_1_6_0: + return "1.6.0"; default: return "Unknown"; } @@ -1393,6 +1431,10 @@ const char *CCECProcessor::ToString(const cec_server_version version) return "1.5.1"; case CEC_SERVER_VERSION_1_5_2: return "1.5.2"; + case CEC_SERVER_VERSION_1_5_3: + return "1.5.3"; + case CEC_SERVER_VERSION_1_6_0: + return "1.6.0"; default: return "Unknown"; } @@ -1451,7 +1493,7 @@ bool CCECProcessor::StartBootloader(const char *strPort /* = NULL */) IAdapterCommunication *comm = new CUSBCECAdapterCommunication(this, strPort); CTimeout timeout(10000); int iConnectTry(0); - while (timeout.TimeLeft() > 0 && (bReturn = comm->Open(NULL, (timeout.TimeLeft() / CEC_CONNECT_TRIES)), true) == false) + while (timeout.TimeLeft() > 0 && (bReturn = comm->Open(timeout.TimeLeft() / CEC_CONNECT_TRIES, true)) == false) { CLibCEC::AddLog(CEC_LOG_ERROR, "could not open a connection (try %d)", ++iConnectTry); comm->Close(); @@ -1477,7 +1519,8 @@ bool CCECProcessor::PingAdapter(void) void CCECProcessor::HandlePoll(cec_logical_address initiator, cec_logical_address destination) { - m_busDevices[initiator]->HandlePoll(destination); + if (destination < CECDEVICE_BROADCAST) + m_busDevices[destination]->HandlePollFrom(initiator); } bool CCECProcessor::HandleReceiveFailed(cec_logical_address initiator) @@ -1497,12 +1540,15 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) CCECBusDevice *primary = IsRunning() ? GetPrimaryDevice() : NULL; cec_device_type oldPrimaryType = primary ? primary->GetType() : CEC_DEVICE_TYPE_RECORDING_DEVICE; m_configuration.clientVersion = configuration->clientVersion; + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using client version '%s'", __FUNCTION__, ToString((cec_client_version)configuration->clientVersion)); // client version 1.5.0 // device types bool bDeviceTypeChanged = IsRunning () && m_configuration.deviceTypes != configuration->deviceTypes; m_configuration.deviceTypes = configuration->deviceTypes; + if (bDeviceTypeChanged) + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using primary device type '%s'", __FUNCTION__, ToString(configuration->deviceTypes[0])); bool bPhysicalAddressChanged(false); @@ -1515,6 +1561,8 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) { if (IsRunning()) CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - autodetected physical address '%4x'", __FUNCTION__, iPhysicalAddress); + else + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using physical address '%x'", __FUNCTION__, iPhysicalAddress); bPhysicalAddressChanged = (m_configuration.iPhysicalAddress != iPhysicalAddress); m_configuration.iPhysicalAddress = iPhysicalAddress; m_configuration.iHDMIPort = 0; @@ -1528,9 +1576,11 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) { if (configuration->iPhysicalAddress != 0) bPhysicalAddressChanged = IsRunning() && m_configuration.iPhysicalAddress != configuration->iPhysicalAddress; - if (IsRunning()) - CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using physical address '%4x'", __FUNCTION__, configuration->iPhysicalAddress); - m_configuration.iPhysicalAddress = configuration->iPhysicalAddress; + if (bPhysicalAddressChanged) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - physical address '%x'", __FUNCTION__, configuration->iPhysicalAddress); + m_configuration.iPhysicalAddress = configuration->iPhysicalAddress; + } } bool bHdmiPortChanged(false); @@ -1538,20 +1588,17 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) { // base device bHdmiPortChanged = IsRunning() && m_configuration.baseDevice != configuration->baseDevice; - if (IsRunning()) - CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using base device '%x'", __FUNCTION__, (int)configuration->baseDevice); + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using base device '%x'", __FUNCTION__, (int)configuration->baseDevice); m_configuration.baseDevice = configuration->baseDevice; // hdmi port bHdmiPortChanged |= IsRunning() && m_configuration.iHDMIPort != configuration->iHDMIPort; - if (IsRunning()) - CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using HDMI port '%d'", __FUNCTION__, configuration->iHDMIPort); + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using HDMI port '%d'", __FUNCTION__, configuration->iHDMIPort); m_configuration.iHDMIPort = configuration->iHDMIPort; } else { - if (IsRunning()) - CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - resetting HDMI port and base device to defaults", __FUNCTION__); + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - resetting HDMI port and base device to defaults", __FUNCTION__); m_configuration.baseDevice = CECDEVICE_UNKNOWN; m_configuration.iHDMIPort = 0; } @@ -1559,6 +1606,7 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) bReinit = bPhysicalAddressChanged || bHdmiPortChanged || bDeviceTypeChanged; // device name + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - using OSD name '%s'", __FUNCTION__, configuration->strDeviceName); snprintf(m_configuration.strDeviceName, 13, "%s", configuration->strDeviceName); if (primary && !primary->GetOSDName().Equals(m_configuration.strDeviceName)) { @@ -1568,6 +1616,7 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) } // tv vendor id override + CLibCEC::AddLog(CEC_LOG_DEBUG, "%s - vendor id '%s'", __FUNCTION__, ToString((cec_vendor_id)configuration->tvVendor)); if (m_configuration.tvVendor != configuration->tvVendor) { m_configuration.tvVendor= configuration->tvVendor; @@ -1583,7 +1632,7 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) } // just copy these - m_configuration.clientVersion = configuration->clientVersion; + m_configuration.bUseTVMenuLanguage = configuration->bUseTVMenuLanguage; m_configuration.bActivateSource = configuration->bActivateSource; m_configuration.bGetSettingsFromROM = configuration->bGetSettingsFromROM; m_configuration.powerOffDevices = configuration->powerOffDevices; @@ -1594,11 +1643,22 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) if (configuration->clientVersion >= CEC_CLIENT_VERSION_1_5_1) m_configuration.bSendInactiveSource = configuration->bSendInactiveSource; + // client version 1.6.0 + if (configuration->clientVersion >= CEC_CLIENT_VERSION_1_6_0) + { + m_configuration.bPowerOffDevicesOnStandby = configuration->bPowerOffDevicesOnStandby; + m_configuration.bShutdownOnStandby = configuration->bShutdownOnStandby; + } + // ensure that there is at least 1 device type set if (m_configuration.deviceTypes.IsEmpty()) m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE); - if (bReinit) + // persist the configuration + if (IsRunning()) + m_communication->PersistConfiguration(&m_configuration); + + if (bReinit || m_configuration.logicalAddresses.IsEmpty()) { if (bDeviceTypeChanged) return ChangeDeviceType(oldPrimaryType, m_configuration.deviceTypes[0]); @@ -1607,7 +1667,7 @@ bool CCECProcessor::SetConfiguration(const libcec_configuration *configuration) else return SetHDMIPort(m_configuration.baseDevice, m_configuration.iHDMIPort); } - else if (m_configuration.bActivateSource == 1 && IsRunning() && !IsActiveSource(m_logicalAddresses.primary)) + else if (m_configuration.bActivateSource == 1 && IsRunning() && !IsActiveSource(m_configuration.logicalAddresses.primary)) { // activate the source if we're not already the active source SetActiveSource(m_configuration.deviceTypes.types[0]); @@ -1641,6 +1701,18 @@ bool CCECProcessor::GetCurrentConfiguration(libcec_configuration *configuration) if (configuration->clientVersion >= CEC_CLIENT_VERSION_1_5_1) configuration->bSendInactiveSource = m_configuration.bSendInactiveSource; + // client version 1.5.3 + if (configuration->clientVersion >= CEC_CLIENT_VERSION_1_5_3) + configuration->logicalAddresses = m_configuration.logicalAddresses; + + // client version 1.6.0 + if (configuration->clientVersion >= CEC_CLIENT_VERSION_1_6_0) + { + configuration->iFirmwareVersion = m_configuration.iFirmwareVersion; + configuration->bPowerOffDevicesOnStandby = m_configuration.bPowerOffDevicesOnStandby; + configuration->bShutdownOnStandby = m_configuration.bShutdownOnStandby; + } + return true; } @@ -1659,3 +1731,16 @@ void CCECProcessor::RescanActiveDevices(void) for (unsigned int iPtr = 0; iPtr < 16; iPtr++) m_busDevices[iPtr]->GetStatus(true); } + +bool CCECProcessor::GetDeviceInformation(const char *strPort, libcec_configuration *config, uint32_t iTimeoutMs /* = 10000 */) +{ + if (!OpenConnection(strPort, 38400, iTimeoutMs, false)) + return false; + + config->iFirmwareVersion = m_communication->GetFirmwareVersion(); + config->iPhysicalAddress = m_communication->GetPhysicalAddress(); + + delete m_communication; + m_communication = NULL; + return true; +} diff --git a/src/lib/CECProcessor.h b/src/lib/CECProcessor.h index 08bf8d0..25b0c2a 100644 --- a/src/lib/CECProcessor.h +++ b/src/lib/CECProcessor.h @@ -43,6 +43,58 @@ namespace CEC class IAdapterCommunication; class CCECBusDevice; + // a buffer that priotises the input from the TV. + // if we need more than this, we'll have to change it into a priority_queue + class CCECInputBuffer + { + public: + CCECInputBuffer(void) : m_bHasData(false) {} + virtual ~CCECInputBuffer(void) + { + m_condition.Broadcast(); + } + + bool Push(const cec_command &command) + { + bool bReturn(false); + PLATFORM::CLockObject lock(m_mutex); + if (command.initiator == CECDEVICE_TV) + bReturn = m_tvInBuffer.Push(command); + else + bReturn = m_inBuffer.Push(command); + + m_bHasData |= bReturn; + if (m_bHasData) + m_condition.Signal(); + + return bReturn; + } + + bool Pop(cec_command &command, uint16_t iTimeout = 10000) + { + bool bReturn(false); + PLATFORM::CLockObject lock(m_mutex); + if (m_tvInBuffer.IsEmpty() && m_inBuffer.IsEmpty() && + !m_condition.Wait(m_mutex, m_bHasData, iTimeout)) + return bReturn; + + if (m_tvInBuffer.Pop(command)) + bReturn = true; + else if (m_inBuffer.Pop(command)) + bReturn = true; + + m_bHasData = !m_tvInBuffer.IsEmpty() || !m_inBuffer.IsEmpty(); + return bReturn; + } + + private: + PLATFORM::CMutex m_mutex; + PLATFORM::CCondition m_condition; + volatile bool m_bHasData; + PLATFORM::SyncedBuffer m_tvInBuffer; + PLATFORM::SyncedBuffer m_inBuffer; + }; + class CCECProcessor : public PLATFORM::CThread, public IAdapterCommunicationCallback { public: @@ -57,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); @@ -71,11 +123,11 @@ namespace CEC virtual cec_osd_name GetDeviceOSDName(cec_logical_address iAddress); virtual uint64_t GetDeviceVendorId(cec_logical_address iAddress); virtual cec_power_status GetDevicePowerStatus(cec_logical_address iAddress); - virtual cec_logical_address GetLogicalAddress(void) const { return m_logicalAddresses.primary; } - virtual cec_logical_addresses GetLogicalAddresses(void) const { return m_logicalAddresses; } + virtual cec_logical_address GetLogicalAddress(void) const { return m_configuration.logicalAddresses.primary; } + virtual cec_logical_addresses GetLogicalAddresses(void) const { return m_configuration.logicalAddresses; } virtual cec_logical_addresses GetActiveDevices(void); virtual uint16_t GetDevicePhysicalAddress(cec_logical_address iAddress); - virtual bool HasLogicalAddress(cec_logical_address address) const { return m_logicalAddresses.IsSet(address); } + virtual bool HasLogicalAddress(cec_logical_address address) const { return m_configuration.logicalAddresses.IsSet(address); } virtual bool IsPresentDevice(cec_logical_address address); virtual bool IsPresentDeviceType(cec_device_type type); virtual uint16_t GetPhysicalAddress(void) const; @@ -142,11 +194,12 @@ namespace CEC virtual void HandlePoll(cec_logical_address initiator, cec_logical_address destination); virtual bool HandleReceiveFailed(cec_logical_address initiator); + virtual bool GetDeviceInformation(const char *strPort, libcec_configuration *config, uint32_t iTimeoutMs = 10000); + CCECBusDevice * m_busDevices[16]; - PLATFORM::CMutex m_transmitMutex; private: - bool OpenConnection(const char *strPort, uint16_t iBaudRate, uint32_t iTimeoutMs); + bool OpenConnection(const char *strPort, uint16_t iBaudRate, uint32_t iTimeoutMs, bool bStartListening = true); bool Initialise(void); void SetInitialised(bool bSetTo = true); void CreateBusDevices(void); @@ -164,17 +217,16 @@ namespace CEC bool m_bConnectionOpened; bool m_bInitialised; - cec_logical_addresses m_logicalAddresses; PLATFORM::CMutex m_mutex; IAdapterCommunication * m_communication; CLibCEC* m_controller; bool m_bMonitor; cec_keypress m_previousKey; PLATFORM::CThread * m_busScan; - uint8_t m_iLineTimeout; uint8_t m_iStandardLineTimeout; uint8_t m_iRetryLineTimeout; uint64_t m_iLastTransmission; + CCECInputBuffer m_inBuffer; libcec_configuration m_configuration; }; diff --git a/src/lib/LibCEC.cpp b/src/lib/LibCEC.cpp index ab05822..3230e68 100644 --- a/src/lib/LibCEC.cpp +++ b/src/lib/LibCEC.cpp @@ -60,7 +60,7 @@ CLibCEC::CLibCEC(libcec_configuration *configuration) : m_callbacks(configuration->callbacks), m_cbParam(configuration->callbackParam) { - configuration->serverVersion = CEC_SERVER_VERSION_1_5_2; + configuration->serverVersion = CEC_SERVER_VERSION_1_6_0; m_cec = new CCECProcessor(this, configuration); } @@ -510,7 +510,7 @@ bool CECStartBootloader(void) { CUSBCECAdapterCommunication comm(NULL, deviceList[0].comm); CTimeout timeout(10000); - while (timeout.TimeLeft() > 0 && (bReturn = comm.Open(NULL, (timeout.TimeLeft() / CEC_CONNECT_TRIES)), true) == false) + while (timeout.TimeLeft() > 0 && (bReturn = comm.Open(timeout.TimeLeft() / CEC_CONNECT_TRIES, true)) == false) { comm.Close(); CEvent::Sleep(500); @@ -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); @@ -614,7 +619,102 @@ void CLibCEC::RescanActiveDevices(void) bool CLibCEC::IsLibCECActiveSource(void) { - return m_cec ? - m_cec->m_busDevices[m_cec->GetActiveSource()]->GetStatus(false) == CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC : - false; + bool bReturn(false); + if (m_cec) + { + cec_logical_address activeSource = m_cec->GetActiveSource(); + if (activeSource != CECDEVICE_UNKNOWN) + bReturn = m_cec->m_busDevices[activeSource]->GetStatus(false) == CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC; + } + return bReturn; +} + +cec_device_type CLibCEC::GetType(cec_logical_address address) +{ + switch (address) + { + case CECDEVICE_AUDIOSYSTEM: + return CEC_DEVICE_TYPE_AUDIO_SYSTEM; + case CECDEVICE_PLAYBACKDEVICE1: + case CECDEVICE_PLAYBACKDEVICE2: + case CECDEVICE_PLAYBACKDEVICE3: + return CEC_DEVICE_TYPE_PLAYBACK_DEVICE; + case CECDEVICE_RECORDINGDEVICE1: + case CECDEVICE_RECORDINGDEVICE2: + case CECDEVICE_RECORDINGDEVICE3: + return CEC_DEVICE_TYPE_RECORDING_DEVICE; + case CECDEVICE_TUNER1: + case CECDEVICE_TUNER2: + case CECDEVICE_TUNER3: + case CECDEVICE_TUNER4: + return CEC_DEVICE_TYPE_TUNER; + case CECDEVICE_TV: + return CEC_DEVICE_TYPE_TV; + default: + return CEC_DEVICE_TYPE_RESERVED; + } +} + +uint16_t CLibCEC::GetMaskForType(cec_logical_address address) +{ + return GetMaskForType(GetType(address)); +} + +uint16_t CLibCEC::GetMaskForType(cec_device_type type) +{ + switch (type) + { + case CEC_DEVICE_TYPE_AUDIO_SYSTEM: + { + cec_logical_addresses addr; + addr.Clear(); + addr.Set(CECDEVICE_AUDIOSYSTEM); + return addr.AckMask(); + } + case CEC_DEVICE_TYPE_PLAYBACK_DEVICE: + { + cec_logical_addresses addr; + addr.Clear(); + addr.Set(CECDEVICE_PLAYBACKDEVICE1); + addr.Set(CECDEVICE_PLAYBACKDEVICE2); + addr.Set(CECDEVICE_PLAYBACKDEVICE3); + return addr.AckMask(); + } + case CEC_DEVICE_TYPE_RECORDING_DEVICE: + { + cec_logical_addresses addr; + addr.Clear(); + addr.Set(CECDEVICE_RECORDINGDEVICE1); + addr.Set(CECDEVICE_RECORDINGDEVICE2); + addr.Set(CECDEVICE_RECORDINGDEVICE3); + return addr.AckMask(); + } + case CEC_DEVICE_TYPE_TUNER: + { + cec_logical_addresses addr; + addr.Clear(); + addr.Set(CECDEVICE_TUNER1); + addr.Set(CECDEVICE_TUNER2); + addr.Set(CECDEVICE_TUNER3); + addr.Set(CECDEVICE_TUNER4); + return addr.AckMask(); + } + case CEC_DEVICE_TYPE_TV: + { + cec_logical_addresses addr; + addr.Clear(); + addr.Set(CECDEVICE_TV); + return addr.AckMask(); + } + default: + return 0; + } +} + +bool CLibCEC::GetDeviceInformation(const char *strPort, libcec_configuration *config, uint32_t iTimeoutMs /* = 10000 */) +{ + if (m_cec->IsRunning()) + return false; + + return m_cec->GetDeviceInformation(strPort, config, iTimeoutMs); } diff --git a/src/lib/LibCEC.h b/src/lib/LibCEC.h index 9241a2c..4697371 100644 --- a/src/lib/LibCEC.h +++ b/src/lib/LibCEC.h @@ -121,6 +121,13 @@ 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); + static uint16_t GetMaskForType(cec_device_type type); + + virtual bool GetDeviceInformation(const char *strPort, libcec_configuration *config, uint32_t iTimeoutMs = 10000); //@} static void AddLog(const cec_log_level level, const char *strFormat, ...); diff --git a/src/lib/LibCECC.cpp b/src/lib/LibCECC.cpp index 29b08fd..4a7e0be 100644 --- a/src/lib/LibCECC.cpp +++ b/src/lib/LibCECC.cpp @@ -421,4 +421,9 @@ int cec_is_libcec_active_source(void) return cec_parser ? (cec_parser->IsLibCECActiveSource() ? 1 : 0) : -1; } +int cec_get_device_information(const char *strPort, CEC::libcec_configuration *config, uint32_t iTimeoutMs) +{ + return cec_parser ? (cec_parser->GetDeviceInformation(strPort, config, iTimeoutMs) ? 1 : 0) : -1; +} + //@} 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 d78b11d..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,22 +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) = 0; + virtual bool Open(uint32_t iTimeoutMs = 10000, bool bSkipChecks = false, bool bStartListening = true) = 0; /*! * @brief Close an open connection @@ -90,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 @@ -135,7 +135,7 @@ namespace CEC virtual bool PingAdapter(void) = 0; /*! - * @return The firmware version of this CEC adapter. + * @return The firmware version of this CEC adapter, or 0 if it's unknown. */ virtual uint16_t GetFirmwareVersion(void) = 0; @@ -151,6 +151,13 @@ namespace CEC */ virtual bool PersistConfiguration(libcec_configuration *configuration) = 0; + /*! + * @brief Get the persisted configuration from the adapter (if supported) + * @param configuration The updated configuration. + * @return True when the configuration was updated, false otherwise. + */ + virtual bool GetConfiguration(libcec_configuration *configuration) = 0; + /*! * @return The name of the port */ @@ -160,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..69a2632 --- /dev/null +++ b/src/lib/adapter/USBCECAdapterCommands.cpp @@ -0,0 +1,529 @@ +/* + * 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; + +CUSBCECAdapterCommands::CUSBCECAdapterCommands(CUSBCECAdapterCommunication *comm) : + m_comm(comm), + m_bSettingsRetrieved(false), + m_bSettingAutoEnabled(false), + m_settingCecVersion(CEC_VERSION_UNKNOWN), + m_iSettingLAMask(0), + m_bNeedsWrite(false) +{ + m_persistedConfiguration.Clear(); +} + +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_persistedConfiguration.iFirmwareVersion = CEC_FW_VERSION_UNKNOWN; + unsigned int iFwVersionTry(0); + + while (m_persistedConfiguration.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_persistedConfiguration.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_persistedConfiguration.iFirmwareVersion == CEC_FW_VERSION_UNKNOWN) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "defaulting to firmware version 1"); + m_persistedConfiguration.iFirmwareVersion = 1; + } + + return m_persistedConfiguration.iFirmwareVersion; +} + +bool CUSBCECAdapterCommands::RequestSettingAutoEnabled(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting autonomous mode setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_AUTO_ENABLED); + if (response.size == 1) + { + m_bSettingAutoEnabled = response[0] == 1; + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingCECVersion(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting CEC version setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_HDMI_VERSION); + if (response.size == 1) + { + m_settingCecVersion = (cec_version)response[0]; + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingDefaultLogicalAddress(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting default logical address setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS); + if (response.size == 1) + { + m_persistedConfiguration.logicalAddresses.primary = (cec_logical_address)response[0]; + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingDeviceType(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting device type setting"); + m_persistedConfiguration.deviceTypes.Clear(); + + cec_datapacket response = RequestSetting(MSGCODE_GET_DEVICE_TYPE); + if (response.size == 1) + { + m_persistedConfiguration.deviceTypes.Add((cec_device_type)response[0]); + CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted device type setting %s", CLibCEC::GetInstance()->ToString((cec_device_type)response[0])); + return true; + } + CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted device type setting"); + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingLogicalAddressMask(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting logical address mask setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_LOGICAL_ADDRESS_MASK); + if (response.size == 2) + { + m_iSettingLAMask = ((uint16_t)response[0] << 8) | ((uint16_t)response[1]); + return true; + } + return false; +} + +bool CUSBCECAdapterCommands::RequestSettingOSDName(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting OSD name setting"); + + memset(m_persistedConfiguration.strDeviceName, 0, 13); + cec_datapacket response = RequestSetting(MSGCODE_GET_OSD_NAME); + if (response.size == 0) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted device name setting"); + 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; + + snprintf(m_persistedConfiguration.strDeviceName, 13, "%s", buf); + CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted device name setting %s", buf); + return true; +} + +bool CUSBCECAdapterCommands::RequestSettingPhysicalAddress(void) +{ + CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting physical address setting"); + + cec_datapacket response = RequestSetting(MSGCODE_GET_PHYSICAL_ADDRESS); + if (response.size == 2) + { + m_persistedConfiguration.iPhysicalAddress = ((uint16_t)response[0] << 8) | ((uint16_t)response[1]); + CLibCEC::AddLog(CEC_LOG_DEBUG, "using persisted physical address setting %4x", m_persistedConfiguration.iPhysicalAddress); + return true; + } + CLibCEC::AddLog(CEC_LOG_DEBUG, "no persisted physical address setting"); + return false; +} + +bool CUSBCECAdapterCommands::SetSettingAutoEnabled(bool enabled) +{ + bool bReturn(true); + + /* check whether this value was changed */ + if (m_bSettingAutoEnabled == enabled) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "autonomous mode setting unchanged (%s)", enabled ? "on" : "off"); + return bReturn; + } + + m_bNeedsWrite = true; + 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); + bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + + if (bReturn) + m_bSettingAutoEnabled = enabled; + + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingDeviceType(cec_device_type type) +{ + bool bReturn(true); + + /* check whether this value was changed */ + if (m_persistedConfiguration.deviceTypes.types[0] == type) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "device type setting unchanged (%X)", (uint8_t)type); + return bReturn; + } + + m_bNeedsWrite = true; + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the device type to %X", (uint8_t)type); + + CCECAdapterMessage params; + params.PushEscaped((uint8_t)type); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_DEVICE_TYPE, params); + bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingDefaultLogicalAddress(cec_logical_address address) +{ + bool bReturn(true); + + /* check whether this value was changed */ + if (m_persistedConfiguration.logicalAddresses.primary == address) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "logical address setting unchanged (%X)", (uint8_t)address); + return bReturn; + } + + m_bNeedsWrite = true; + CLibCEC::AddLog(CEC_LOG_DEBUG, "setting the default logical address to %X", (uint8_t)address); + + CCECAdapterMessage params; + params.PushEscaped((uint8_t)address); + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS, params); + bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + + if (bReturn) + m_persistedConfiguration.logicalAddresses.primary = address; + + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingLogicalAddressMask(uint16_t iMask) +{ + bool bReturn(true); + + /* check whether this value was changed */ + if (m_iSettingLAMask == iMask) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "logical address mask setting unchanged (%2X)", iMask); + return bReturn; + } + + m_bNeedsWrite = true; + 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); + bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + + if (bReturn) + m_iSettingLAMask = iMask; + + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingPhysicalAddress(uint16_t iPhysicalAddress) +{ + bool bReturn(true); + + /* check whether this value was changed */ + if (m_persistedConfiguration.iPhysicalAddress == iPhysicalAddress) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "logical address mask setting unchanged (%04X)", iPhysicalAddress); + return bReturn; + } + + m_bNeedsWrite = true; + 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); + bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + + if (bReturn) + m_persistedConfiguration.iPhysicalAddress = iPhysicalAddress; + + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingCECVersion(cec_version version) +{ + bool bReturn(true); + + /* check whether this value was changed */ + if (m_settingCecVersion == version) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "CEC version setting unchanged (%s)", CLibCEC::GetInstance()->ToString(version)); + return bReturn; + } + + m_bNeedsWrite = true; + 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); + bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + + if (bReturn) + m_settingCecVersion = version; + + return bReturn; +} + +bool CUSBCECAdapterCommands::SetSettingOSDName(const char *strOSDName) +{ + bool bReturn(true); + + /* check whether this value was changed */ + if (!strcmp(m_persistedConfiguration.strDeviceName, strOSDName)) + { + CLibCEC::AddLog(CEC_LOG_DEBUG, "OSD name setting unchanged (%s)", strOSDName); + return bReturn; + } + + 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); + bReturn = message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; + delete message; + + if (bReturn) + snprintf(m_persistedConfiguration.strDeviceName, 13, "%s", strOSDName); + + return bReturn; +} + +bool CUSBCECAdapterCommands::WriteEEPROM(void) +{ + if (!m_bNeedsWrite) + return true; + + CLibCEC::AddLog(CEC_LOG_DEBUG, "writing settings in the EEPROM"); + + CCECAdapterMessage params; + CCECAdapterMessage *message = m_comm->SendCommand(MSGCODE_WRITE_EEPROM, params); + m_bNeedsWrite = !(message->state == ADAPTER_MESSAGE_STATE_SENT_ACKED); + delete message; + return m_bNeedsWrite; +} + +bool CUSBCECAdapterCommands::PersistConfiguration(libcec_configuration *configuration) +{ + if (m_persistedConfiguration.iFirmwareVersion < 2) + return false; + + if (!RequestSettings()) + 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); + bReturn &= WriteEEPROM(); + return bReturn; +} + +bool CUSBCECAdapterCommands::RequestSettings(void) +{ + if (m_persistedConfiguration.iFirmwareVersion < 2) + { + // settings can only be persisted with firmware v2+ + return false; + } + + if (!m_bSettingsRetrieved) + return true; + + bool bReturn(true); + bReturn &= RequestSettingAutoEnabled(); + bReturn &= RequestSettingCECVersion(); + bReturn &= RequestSettingDefaultLogicalAddress(); + bReturn &= RequestSettingDeviceType(); + bReturn &= RequestSettingLogicalAddressMask(); + bReturn &= RequestSettingOSDName(); + bReturn &= RequestSettingPhysicalAddress(); + + // 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) + + m_bSettingsRetrieved = true; + + return bReturn; +} + +bool CUSBCECAdapterCommands::GetConfiguration(libcec_configuration *configuration) +{ + // get the settings from the eeprom if needed + if (!RequestSettings()) + return false; + + // copy the settings + configuration->iFirmwareVersion = m_persistedConfiguration.iFirmwareVersion; + configuration->deviceTypes = m_persistedConfiguration.deviceTypes; + configuration->iPhysicalAddress = m_persistedConfiguration.iPhysicalAddress; + snprintf(configuration->strDeviceName, 13, "%s", m_persistedConfiguration.strDeviceName); + + return true; +} + +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..b88651e --- /dev/null +++ b/src/lib/adapter/USBCECAdapterCommands.h @@ -0,0 +1,222 @@ +#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); + + /*! + * @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); + + /*! + * @return The firmware version of the adapter, retrieved when the connection is opened. + */ + uint16_t GetFirmwareVersion(void) const { return m_persistedConfiguration.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: + /*! + * @brief Reads all settings from the eeprom. + * @return True when read, false otherwise. + */ + bool RequestSettings(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. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingAutoEnabled(void); + + /*! + * @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. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingDeviceType(void); + + /*! + * @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. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingDefaultLogicalAddress(void); + + /*! + * @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. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingLogicalAddressMask(void); + + /*! + * @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. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingPhysicalAddress(void); + + /*! + * @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. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingCECVersion(void); + + /*! + * @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. + * @return True when retrieved, false otherwise. + */ + bool RequestSettingOSDName(void); + + /*! + * @brief Persist the current settings in the EEPROM + * @return True when persisted, false otherwise. + */ + bool WriteEEPROM(void); + + CUSBCECAdapterCommunication *m_comm; /**< the communication handler */ + bool m_bSettingsRetrieved; /**< true when the settings were read from the eeprom, false otherwise */ + bool m_bSettingAutoEnabled; /**< the value of the auto-enabled setting */ + cec_version m_settingCecVersion; /**< the value of the cec version setting */ + uint16_t m_iSettingLAMask; /**< the value of the LA mask setting */ + bool m_bNeedsWrite; /**< true when we sent changed settings to the adapter that have not been persisted */ + libcec_configuration m_persistedConfiguration; /**< the configuration that is persisted in the eeprom */ + }; +} diff --git a/src/lib/adapter/USBCECAdapterCommunication.cpp b/src/lib/adapter/USBCECAdapterCommunication.cpp index e51a861..ef80ed0 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" @@ -40,225 +42,136 @@ using namespace std; using namespace CEC; using namespace PLATFORM; -void *CUSBCECAdapterProcessor::Process(void) -{ - cec_command command; - while (!IsStopped()) - { - if (m_inBuffer.Pop(command)) - m_callback->OnCommandReceived(command); - Sleep(5); - } - - return NULL; -} - -void CUSBCECAdapterProcessor::AddCommand(cec_command command) -{ - m_inBuffer.Push(command); -} +#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_lastInitiator(CECDEVICE_UNKNOWN), - m_bNextIsEscaped(false), - m_bGotStart(false), - m_messageProcessor(NULL), - m_bInitialised(false) + m_lastPollDestination(CECDEVICE_UNKNOWN), + m_bInitialised(false), + m_pingThread(NULL), + m_commands(NULL), + m_adapterMessageQueue(NULL) { - m_port = new PLATFORM::CSerialPort(strPort, iBaudRate); + for (unsigned int iPtr = 0; iPtr < 15; iPtr++) + m_bWaitingForAck[iPtr] = false; + m_port = new CSerialPort(strPort, iBaudRate); } CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void) { Close(); + delete m_commands; + delete m_adapterMessageQueue; + delete m_port; } -bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */) +bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = 10000 */, bool bSkipChecks /* = false */, bool bStartListening /* = true */) { - 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; -} - -bool CUSBCECAdapterCommunication::Open(IAdapterCommunicationCallback *cb, uint32_t iTimeoutMs /* = 10000 */, bool bSkipChecks /* = false */) -{ - 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]; - while (m_port->Read(buff, 1024, 100) > 0) - { - CLibCEC::AddLog(CEC_LOG_DEBUG, "data received, clearing it"); - 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"); - return false; } - else + else if (bStartListening) { - if (CreateThread()) + /* 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 (m_pingThread->CreateThread()) { - CLibCEC::AddLog(CEC_LOG_DEBUG, "communication thread started"); - return true; + bConnectionOpened = true; } else { - CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a communication thread"); + bConnectionOpened = false; + CLibCEC::AddLog(CEC_LOG_ERROR, "could not create a ping thread"); } } - return false; -} + if (!bConnectionOpened || !bStartListening) + StopThread(0); -void CUSBCECAdapterCommunication::Close(void) -{ - StopThread(); + return bConnectionOpened; } -void *CUSBCECAdapterCommunication::Process(void) +void CUSBCECAdapterCommunication::Close(void) { - m_messageProcessor = new CUSBCECAdapterProcessor(m_callback); - m_messageProcessor->CreateThread(); - - cec_command command; - command.Clear(); - bool bCommandReceived(false); - while (!IsStopped()) + /* set the ackmask to 0 before closing the connection */ + if (IsRunning()) { - { - CLockObject lock(m_mutex); - ReadFromDevice(50); - bCommandReceived = m_callback && Read(command, 0) && m_bInitialised; - } - - /* push the next command to the callback method if there is one */ - if (!IsStopped() && bCommandReceived) - m_messageProcessor->AddCommand(command); - - if (!IsStopped()) - { - Sleep(5); - WriteNextCommand(); - } + SetAckMask(0); + if (m_commands->GetFirmwareVersion() >= 2) + SetControlledMode(false); } - /* stop the message processor */ - m_messageProcessor->StopThread(); - delete m_messageProcessor; - - /* notify all threads that are waiting on messages to be sent */ - CCECAdapterMessage *msg(NULL); - while (m_outBuffer.Pop(msg)) - msg->event.Broadcast(); + /* stop and delete the ping thread */ + if (m_pingThread) + m_pingThread->StopThread(0); + delete m_pingThread; + m_pingThread = NULL; - /* set the ackmask to 0 before closing the connection */ - SetAckMaskInternal(0, true); + /* stop the reader thread */ + StopThread(0); + /* close and delete the com port connection */ if (m_port) - { - delete m_port; - m_port = NULL; - } - - return NULL; + m_port->Close(); } cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, uint8_t iMaxTries, uint8_t iLineTimeout /* = 3 */, uint8_t iRetryLineTimeout /* = 3 */) @@ -267,22 +180,16 @@ cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command & if (!IsRunning()) return retVal; - CCECAdapterMessage *output = new CCECAdapterMessage(data); + CCECAdapterMessage *output = new CCECAdapterMessage(data, iMaxTries, iLineTimeout, iRetryLineTimeout); - /* set the number of retries */ - if (data.opcode == CEC_OPCODE_NONE) //TODO - output->maxTries = 1; - else if (data.initiator != CECDEVICE_BROADCAST) - output->maxTries = iMaxTries; - - output->lineTimeout = iLineTimeout; - output->retryTimeout = iRetryLineTimeout; - output->tries = 0; + /* 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,488 +199,325 @@ 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; - m_outBuffer.Push(data); - data->event.Wait(5000); + 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) -{ - CLockObject lock(m_mutex); - - msg.Clear(); - CCECAdapterMessage *buf(NULL); - - if (!m_inBuffer.Pop(buf)) + else if (messageCode == MSGCODE_RECEIVE_FAILED) { - if (iTimeout == 0 || !m_rcvCondition.Wait(m_mutex, m_bHasData, iTimeout)) - return false; - m_inBuffer.Pop(buf); - m_bHasData = !m_inBuffer.IsEmpty(); + /* hack to suppress warnings when an LG is polling */ + if (m_lastPollDestination != CECDEVICE_UNKNOWN) + bIsError = m_callback->HandleReceiveFailed(m_lastPollDestination); } - if (buf) + return bIsError; +} + +void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest) +{ + /* mark as waiting for an ack from the destination */ + if (dest < CECDEVICE_BROADCAST) { - msg.packet = buf->packet; - msg.state = ADAPTER_MESSAGE_STATE_INCOMING; - delete buf; - return true; + CLockObject lock(m_mutex); + m_bWaitingForAck[dest] = true; } - return false; } -CStdString CUSBCECAdapterCommunication::GetError(void) const +void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = 1000 */) { - CStdString strError; - strError = m_port->GetError(); - return strError; + CTimeout timeout(iTimeout); + uint8_t buff[1024]; + ssize_t iBytesRead(0); + bool bGotMsgEnd(true); + + while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd)) + { + bGotMsgEnd = false; + /* if something was received, wait for MSGEND */ + for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++) + bGotMsgEnd = buff[iPtr] == MSGEND; + } } -bool CUSBCECAdapterCommunication::StartBootloader(void) +bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout) { - bool bReturn(false); - if (!IsRunning()) - return bReturn; + bool bReturn(true); + bool bChanged(false); - CLibCEC::AddLog(CEC_LOG_DEBUG, "starting the bootloader"); - CCECAdapterMessage *output = new CCECAdapterMessage; - - output->PushBack(MSGSTART); - output->PushEscaped(MSGCODE_START_BOOTLOADER); - output->PushBack(MSGEND); - output->isTransmission = false; - output->expectControllerAck = false; + /* only send the command if the timeout changed */ + { + CLockObject lock(m_mutex); + bChanged = (m_iLineTimeout != iTimeout); + m_iLineTimeout = iTimeout; + } - if ((bReturn = Write(output)) == false) - CLibCEC::AddLog(CEC_LOG_ERROR, "could not start the bootloader"); - delete output; + if (bChanged) + bReturn = m_commands->SetLineTimeout(iTimeout); return bReturn; } -bool CUSBCECAdapterCommunication::PingAdapter(void) +bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message) { - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "sending ping"); + 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; + } - CCECAdapterMessage *output = new CCECAdapterMessage; + /* 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; + } - output->PushBack(MSGSTART); - output->PushEscaped(MSGCODE_PING); - output->PushBack(MSGEND); - output->isTransmission = false; + CLibCEC::AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message())); + message->state = ADAPTER_MESSAGE_STATE_SENT; + return true; +} - SendMessageToAdapter(output); - bool bWriteOk = output->state == ADAPTER_MESSAGE_STATE_SENT_ACKED; - delete output; - if (!bWriteOk) +bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */) +{ + ssize_t iBytesRead(0); + uint8_t buff[256]; + if (iSize > 256) + iSize = 256; + + /* read from the serial port */ + { + CLockObject lock(m_mutex); + if (!m_port) + return false; + iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout); + } + + if (iBytesRead < 0 || iBytesRead > 256) { - CLibCEC::AddLog(CEC_LOG_ERROR, "could not ping the adapter"); + CLibCEC::AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str()); + StopThread(false); return false; } + else if (iBytesRead > 0) + { + /* add the data to the current frame */ + m_adapterMessageQueue->AddData(buff, iBytesRead); + } return true; } -bool CUSBCECAdapterCommunication::ParseMessage(const CCECAdapterMessage &msg) +CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage ¶ms, bool bIsRetry /* = false */) { - bool bEom(false); - bool bIsError(msg.IsError()); + if (!m_port || !m_port->IsOpen() || + !m_adapterMessageQueue) + return NULL; - if (msg.IsEmpty()) - return bEom; + /* 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); - CLockObject adapterLock(m_mutex); - switch(msg.Message()) + /* write the command */ + if (!m_adapterMessageQueue->Write(output)) { - 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_lastInitiator = m_currentframe.initiator; - m_processor->HandlePoll(m_currentframe.initiator, m_currentframe.destination); - } - } - break; - case MSGCODE_RECEIVE_FAILED: + // timed out + return output; + } + else + { + if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED) { - m_currentframe.Clear(); - if (m_lastInitiator != CECDEVICE_UNKNOWN) - bIsError = m_processor->HandleReceiveFailed(m_lastInitiator); + /* 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); } - break; - case MSGCODE_FRAME_DATA: - { - if (msg.Size() >= 2) - { - m_currentframe.PushBack(msg[1]); - m_currentframe.eom = msg.IsEOM(); - } - } - break; - default: - break; } - CLibCEC::AddLog(bIsError ? CEC_LOG_WARNING : CEC_LOG_DEBUG, msg.ToString()); - return msg.IsEOM(); + return output; } -uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void) +bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = 10000 */) { - uint16_t iReturn(m_iFirmwareVersion); + bool bReturn(false); + CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT); - if (iReturn == CEC_FW_VERSION_UNKNOWN) + /* try to ping the adapter */ + bool bPinged(false); + unsigned iPingTry(0); + while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false) { - CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "requesting the firmware version"); - CCECAdapterMessage *output = new CCECAdapterMessage; - - output->PushBack(MSGSTART); - output->PushEscaped(MSGCODE_FIRMWARE_VERSION); - output->PushBack(MSGEND); - output->isTransmission = false; - output->expectControllerAck = false; - - SendMessageToAdapter(output); - bool bWriteOk = output->state == ADAPTER_MESSAGE_STATE_SENT; - delete output; - if (!bWriteOk) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "could not request the firmware version"); - return iReturn; - } + CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry); + CEvent::Sleep(500); + } - Sleep(250); // TODO ReadFromDevice() isn't waiting for the timeout to pass on win32 - ReadFromDevice(CEC_DEFAULT_TRANSMIT_WAIT, 5 /* start + msgcode + 2 bytes for fw version + end */); - CCECAdapterMessage input; - if (Read(input, 0)) - { - if (input.Message() != MSGCODE_FIRMWARE_VERSION || input.Size() != 3) - CLibCEC::AddLog(CEC_LOG_ERROR, "invalid firmware version (size = %d, message = %d)", input.Size(), input.Message()); - else - { - m_iFirmwareVersion = (input[1] << 8 | input[2]); - iReturn = m_iFirmwareVersion; - } - } - else + /* try to read the firmware version */ + if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2) + { + /* 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, "no firmware version received"); + CLibCEC::AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry); + CEvent::Sleep(500); } + bReturn = bControlled; } + else + bReturn = true; - return iReturn; + SetInitialised(bReturn); + return bReturn; } -bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout) +bool CUSBCECAdapterCommunication::IsOpen(void) { - m_iLineTimeout = iTimeout; - return true; - //TODO -// bool bReturn(m_iLineTimeout != iTimeout); -// -// if (!bReturn) -// { -// CCECAdapterMessage *output = new CCECAdapterMessage; -// -// output->PushBack(MSGSTART); -// output->PushEscaped(MSGCODE_TRANSMIT_IDLETIME); -// output->PushEscaped(iTimeout); -// output->PushBack(MSGEND); -// output->isTransmission = false; -// -// if ((bReturn = Write(output)) == false) -// CLibCEC::AddLog(CEC_LOG_ERROR, "could not set the idletime"); -// delete output; -// } -// -// return bReturn; + /* thread is not being stopped, the port is open and the thread is running */ + return !IsStopped() && m_port->IsOpen() && IsRunning(); } -bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask) +CStdString CUSBCECAdapterCommunication::GetError(void) const { - return SetAckMaskInternal(iMask, false); + return m_port->GetError(); } -bool CUSBCECAdapterCommunication::SetAckMaskInternal(uint16_t iMask, bool bWriteDirectly /* = false */) +void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */) { - bool bReturn(false); - CLibCEC::AddLog(CEC_LOG_DEBUG, "setting ackmask to %2x", iMask); - - CCECAdapterMessage *output = new CCECAdapterMessage; - - output->PushBack(MSGSTART); - output->PushEscaped(MSGCODE_SET_ACK_MASK); - output->PushEscaped(iMask >> 8); - output->PushEscaped((uint8_t)iMask); - output->PushBack(MSGEND); - output->isTransmission = false; - - if (bWriteDirectly) - SendMessageToAdapter(output); - else if ((bReturn = Write(output)) == false) - CLibCEC::AddLog(CEC_LOG_ERROR, "could not set the ackmask"); - delete output; - - return bReturn; + CLockObject lock(m_mutex); + m_bInitialised = bSetTo; } - -bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled) +bool CUSBCECAdapterCommunication::IsInitialised(void) { CLockObject lock(m_mutex); - CLibCEC::AddLog(CEC_LOG_DEBUG, "turning controlled mode %s", controlled ? "on" : "off"); - - CCECAdapterMessage *output = new CCECAdapterMessage; - - output->PushBack(MSGSTART); - output->PushEscaped(MSGCODE_SET_CONTROLLED); - output->PushEscaped(controlled); - output->PushBack(MSGEND); - output->isTransmission = false; + return m_bInitialised; +} - SendMessageToAdapter(output); - bool bWriteOk = output->state == ADAPTER_MESSAGE_STATE_SENT; - delete output; - if (!bWriteOk) - { - CLibCEC::AddLog(CEC_LOG_ERROR, "could not set controlled mode"); +bool CUSBCECAdapterCommunication::StartBootloader(void) +{ + if (!IsRunning()) return false; - } - return true; + return m_commands->StartBootloader(); } -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.Size() / 4); - - int64_t iNow = GetTimeMs(); - int64_t iTargetTime = iNow + (message.transmit_timeout <= 5 ? CEC_DEFAULT_TRANSMIT_WAIT : message.transmit_timeout); - - while (!bTransmitSucceeded && !bError && iNow < iTargetTime) - { - ReadFromDevice(50); - CCECAdapterMessage msg; - if (!Read(msg, 0)) - { - iNow = GetTimeMs(); - continue; - } - - if (msg.Message() == MSGCODE_FRAME_START && msg.IsACK()) - { - m_processor->HandlePoll(msg.Initiator(), msg.Destination()); - m_lastInitiator = msg.Initiator(); - iNow = GetTimeMs(); - continue; - } - - if (msg.Message() == MSGCODE_RECEIVE_FAILED && - m_lastInitiator != CECDEVICE_UNKNOWN && - m_processor->HandleReceiveFailed(m_lastInitiator)) - { - 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 m_commands->PingAdapter(); +} - return bTransmitSucceeded && !bError; +uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void) +{ + return m_commands->GetFirmwareVersion(); } -void CUSBCECAdapterCommunication::AddData(uint8_t *data, size_t iLen) +bool CUSBCECAdapterCommunication::PersistConfiguration(libcec_configuration *configuration) { - 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->PersistConfiguration(configuration); } -bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */) +bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration *configuration) { - ssize_t iBytesRead; - uint8_t buff[256]; - if (!m_port) - return false; - if (iSize > 256) - iSize = 256; + return m_commands->GetConfiguration(configuration); +} - 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); - } +CStdString CUSBCECAdapterCommunication::GetPortName(void) +{ + return m_port->GetName(); +} - return iBytesRead > 0; +bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled) +{ + return m_commands->SetControlledMode(controlled); } -void CUSBCECAdapterCommunication::SendMessageToAdapter(CCECAdapterMessage *msg) +void *CAdapterPingThread::Process(void) { - CLockObject adapterLock(m_mutex); - if (!m_port->IsOpen()) + while (!IsStopped()) { - CLibCEC::AddLog(CEC_LOG_ERROR, "error writing to serial port: the connection is closed"); - msg->state = ADAPTER_MESSAGE_STATE_ERROR; - return; - } - - if (msg->tries == 1) - SetLineTimeout(msg->lineTimeout); - else - SetLineTimeout(msg->retryTimeout); + if (m_timeout.TimeLeft() == 0) + { + /* reinit the timeout */ + m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT); - 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; + /* 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 (msg->expectControllerAck) - { - if (!WaitForAck(*msg)) - CLibCEC::AddLog(CEC_LOG_DEBUG, "did not receive ack"); + 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; + } } - } - msg->event.Signal(); -} - -void CUSBCECAdapterCommunication::WriteNextCommand(void) -{ - CCECAdapterMessage *msg(NULL); - if (m_outBuffer.Pop(msg)) - SendMessageToAdapter(msg); -} -CStdString CUSBCECAdapterCommunication::GetPortName(void) -{ - CStdString strName; - strName = m_port->GetName(); - return strName; + Sleep(500); + } + return NULL; } diff --git a/src/lib/adapter/USBCECAdapterCommunication.h b/src/lib/adapter/USBCECAdapterCommunication.h index c71453a..9d125a7 100644 --- a/src/lib/adapter/USBCECAdapterCommunication.h +++ b/src/lib/adapter/USBCECAdapterCommunication.h @@ -45,78 +45,137 @@ namespace PLATFORM namespace CEC { class CCECProcessor; + class CAdapterPingThread; + class CUSBCECAdapterCommands; + class CCECAdapterMessageQueue; - class CUSBCECAdapterProcessor: public PLATFORM::CThread + class CUSBCECAdapterCommunication : public IAdapterCommunication, public PLATFORM::CThread { - public: - CUSBCECAdapterProcessor(IAdapterCommunicationCallback *cb) : - m_callback(cb) {}; - virtual ~CUSBCECAdapterProcessor(void) - { - StopThread(); - } - - void *Process(void); - void AddCommand(cec_command command); - private: - IAdapterCommunicationCallback * m_callback; - PLATFORM::SyncedBuffer m_inBuffer; - }; + friend class CUSBCECAdapterCommands; + friend class CCECAdapterMessageQueue; - class CUSBCECAdapterCommunication : public IAdapterCommunication, private PLATFORM::CThread - { public: - CUSBCECAdapterCommunication(CCECProcessor *processor, const char *strPort, uint16_t iBaudRate = 38400); - virtual ~CUSBCECAdapterCommunication(); + /*! + * @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); - virtual bool Open(IAdapterCommunicationCallback *cb, uint32_t iTimeoutMs = 10000, bool bSkipChecks = false); - virtual void Close(void); - virtual bool IsOpen(void); - virtual CStdString GetError(void) const; - - bool Read(cec_command &command, uint32_t iTimeout); + /** @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 * UNUSED(configuration)) { return false; } // TODO - 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 SetAckMaskInternal(uint16_t iMask, bool bWriteDirectly = false); + /*! + * @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); - bool ParseMessage(const CCECAdapterMessage &msg); - void SendMessageToAdapter(CCECAdapterMessage *msg); - void WriteNextCommand(void); - 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::SyncedBuffer m_outBuffer; - 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_lastInitiator; - CCECAdapterMessage m_currentAdapterMessage; - bool m_bNextIsEscaped; - bool m_bGotStart; - IAdapterCommunicationCallback * m_callback; - CUSBCECAdapterProcessor * m_messageProcessor; - bool m_bInitialised; + + /*! + * @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 + { + public: + CAdapterPingThread(CUSBCECAdapterCommunication *com, uint32_t iTimeout) : + m_com(com), + m_timeout(iTimeout){} + virtual ~CAdapterPingThread(void) {} + + virtual void* Process(void); + private: + CUSBCECAdapterCommunication *m_com; + PLATFORM::CTimeout m_timeout; }; }; diff --git a/src/lib/adapter/USBCECAdapterDetection.cpp b/src/lib/adapter/USBCECAdapterDetection.cpp index 0d9f8b3..8775b4e 100644 --- a/src/lib/adapter/USBCECAdapterDetection.cpp +++ b/src/lib/adapter/USBCECAdapterDetection.cpp @@ -55,6 +55,9 @@ static GUID USB_RAW_GUID = { 0xA5DCBF10, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0x extern "C" { #include } +#elif defined(__FreeBSD__) +#include +#include #endif #define CEC_VID 0x2548 @@ -323,6 +326,19 @@ uint8_t CUSBCECAdapterDetection::FindAdapters(cec_adapter *deviceList, uint8_t i RegCloseKey(hDeviceKey); } +#elif defined(__FreeBSD__) + char devicePath[PATH_MAX + 1]; + int i; + + for (i = 0; i < 8; ++i) + { + (void)snprintf(devicePath, sizeof(devicePath), "/dev/ttyU%d", i); + if (!access(devicePath, 0)) + { + snprintf(deviceList[iFound ].path, sizeof(deviceList[iFound].path), "%s", devicePath); + snprintf(deviceList[iFound++].comm, sizeof(deviceList[iFound].path), "%s", devicePath); + } + } #endif iBufSize = 0; /* silence "unused" warning on linux/osx */ 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 06e4088..2b12260 100644 --- a/src/lib/adapter/USBCECAdapterMessage.h +++ b/src/lib/adapter/USBCECAdapterMessage.h @@ -32,331 +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 = MessageCodeAsString(); - - 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; - } - - CStdString MessageCodeAsString(void) const - { - CStdString strMsg; - switch (Message()) - { - case MSGCODE_NOTHING: - strMsg = "NOTHING"; - break; - case MSGCODE_PING: - strMsg = "PING"; - break; - case MSGCODE_TIMEOUT_ERROR: - strMsg = "TIMEOUT"; - break; - case MSGCODE_HIGH_ERROR: - strMsg = "HIGH_ERROR"; - break; - case MSGCODE_LOW_ERROR: - strMsg = "LOW_ERROR"; - break; - case MSGCODE_FRAME_START: - strMsg = "FRAME_START"; - break; - case MSGCODE_FRAME_DATA: - strMsg = "FRAME_DATA"; - break; - case MSGCODE_RECEIVE_FAILED: - strMsg = "RECEIVE_FAILED"; - break; - case MSGCODE_COMMAND_ACCEPTED: - strMsg = "COMMAND_ACCEPTED"; - break; - case MSGCODE_COMMAND_REJECTED: - strMsg = "COMMAND_REJECTED"; - break; - case MSGCODE_SET_ACK_MASK: - strMsg = "SET_ACK_MASK"; - break; - case MSGCODE_TRANSMIT: - strMsg = "TRANSMIT"; - break; - case MSGCODE_TRANSMIT_EOM: - strMsg = "TRANSMIT_EOM"; - break; - case MSGCODE_TRANSMIT_IDLETIME: - strMsg = "TRANSMIT_IDLETIME"; - break; - case MSGCODE_TRANSMIT_ACK_POLARITY: - strMsg = "TRANSMIT_ACK_POLARITY"; - break; - case MSGCODE_TRANSMIT_LINE_TIMEOUT: - strMsg = "TRANSMIT_LINE_TIMEOUT"; - break; - case MSGCODE_TRANSMIT_SUCCEEDED: - strMsg = "TRANSMIT_SUCCEEDED"; - break; - case MSGCODE_TRANSMIT_FAILED_LINE: - strMsg = "TRANSMIT_FAILED_LINE"; - break; - case MSGCODE_TRANSMIT_FAILED_ACK: - strMsg = "TRANSMIT_FAILED_ACK"; - break; - case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA: - strMsg = "TRANSMIT_FAILED_TIMEOUT_DATA"; - break; - case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE: - strMsg = "TRANSMIT_FAILED_TIMEOUT_LINE"; - break; - case MSGCODE_FIRMWARE_VERSION: - strMsg = "FIRMWARE_VERSION"; - break; - case MSGCODE_START_BOOTLOADER: - strMsg = "START_BOOTLOADER"; - break; - case MSGCODE_FRAME_EOM: - strMsg = "FRAME_EOM"; - break; - case MSGCODE_FRAME_ACK: - strMsg = "FRAME_ACK"; - break; - case MSGCODE_SET_POWERSTATE: - strMsg = "SET_POWERSTATE"; - break; - case MSGCODE_SET_CONTROLLED: - strMsg = "SET_CONTROLLED"; - break; - } - - return strMsg; - } - - 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 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..9c66051 --- /dev/null +++ b/src/lib/adapter/USBCECAdapterMessageQueue.cpp @@ -0,0 +1,322 @@ +/* + * 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; +using namespace std; + +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 bHandled(false); + + if (IsResponse(message)) + { + switch (message.Message()) + { + case MSGCODE_COMMAND_ACCEPTED: + bHandled = MessageReceivedCommandAccepted(message); + break; + case MSGCODE_TRANSMIT_SUCCEEDED: + bHandled = MessageReceivedTransmitSucceeded(message); + break; + default: + bHandled = MessageReceivedResponse(message); + break; + } + } + + return bHandled; +} + +void CCECAdapterMessageQueueEntry::Signal(void) +{ + CLockObject lock(m_mutex); + m_bSucceeded = true; + m_condition.Signal(); +} + +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); + bool bHandled(false); + { + CLockObject lock(m_mutex); + if (m_iPacketsLeft > 0) + { + /* decrease by 1 */ + 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; + } + bHandled = true; + } + } + + if (bSendSignal) + Signal(); + + return bHandled; +} + +bool CCECAdapterMessageQueueEntry::MessageReceivedTransmitSucceeded(const CCECAdapterMessage &message) +{ + { + CLockObject lock(m_mutex); + 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 + since the messages are processed in order, this should not happen, so this is an error situation */ + 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; + } + } + + Signal(); + + return true; +} + +bool CCECAdapterMessageQueueEntry::MessageReceivedResponse(const CCECAdapterMessage &message) +{ + { + CLockObject lock(m_mutex); + 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; + } + + Signal(); + + return true; +} + + +CCECAdapterMessageQueue::~CCECAdapterMessageQueue(void) +{ + Clear(); +} + +void CCECAdapterMessageQueue::Clear(void) +{ + CLockObject lock(m_mutex); + m_messages.clear(); +} + +void CCECAdapterMessageQueue::MessageReceived(const CCECAdapterMessage &msg) +{ + bool bHandled(false); + CLockObject lock(m_mutex); + /* send the received message to each entry in the queue until it is handled */ + for (map::iterator it = m_messages.begin(); !bHandled && it != m_messages.end(); it++) + bHandled = it->second->MessageReceived(msg); + + if (!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); + uint64_t iEntryId(0); + /* add to the wait for ack queue */ + if (msg->Message() != MSGCODE_START_BOOTLOADER) + { + CLockObject lock(m_mutex); + entry = new CCECAdapterMessageQueueEntry(msg); + iEntryId = m_iNextMessage++; + m_messages.insert(make_pair(iEntryId, entry)); + } + + /* TODO write the message async */ + if (!m_com->WriteToDevice(msg)) + { + /* error! */ + Clear(); + return false; + } + + bool bReturn(true); + if (entry) + { + if (!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; + bReturn = false; + } + + CLockObject lock(m_mutex); + m_messages.erase(iEntryId); + } + + return bReturn; +} diff --git a/src/lib/adapter/USBCECAdapterMessageQueue.h b/src/lib/adapter/USBCECAdapterMessageQueue.h new file mode 100644 index 0000000..9c5be49 --- /dev/null +++ b/src/lib/adapter/USBCECAdapterMessageQueue.h @@ -0,0 +1,171 @@ +#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" +#include + +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 message was handled, 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 message was handled, 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 message was handled, false otherwise. + */ + bool MessageReceivedResponse(const CCECAdapterMessage &message); + + /*! + * @brief Signals the waiting thread. + */ + void Signal(void); + + 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) : + m_com(com), + m_iNextMessage(0) {} + 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: + CUSBCECAdapterCommunication * m_com; /**< the communication handler */ + PLATFORM::CMutex m_mutex; /**< mutex for changes to this class */ + std::map m_messages; /**< the outgoing message queue */ + uint64_t m_iNextMessage; /**< the index of the next message */ + 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 845cb07..cba201b 100644 --- a/src/lib/devices/CECBusDevice.cpp +++ b/src/lib/devices/CECBusDevice.cpp @@ -88,7 +88,7 @@ bool CCECBusDevice::HandleCommand(const cec_command &command) m_iLastActive = GetTimeMs(); /* don't call GetStatus() here, just read the value with the mutex locked */ - if (m_deviceStatus != CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC) + if (m_deviceStatus != CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC && command.opcode_set == 1) m_deviceStatus = CEC_DEVICE_STATUS_PRESENT; MarkBusy(); @@ -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,18 +368,19 @@ cec_vendor_id CCECBusDevice::GetVendorId(bool bUpdate /* = false */) return m_vendor; } -bool CCECBusDevice::RequestVendorId(void) +bool CCECBusDevice::RequestVendorId(bool bWaitForResponse /* = true */) { bool bReturn(false); - if (!MyLogicalAddressContains(m_iLogicalAddress)) + if (!MyLogicalAddressContains(m_iLogicalAddress) && GetMyLogicalAddress() != CECDEVICE_UNKNOWN) { 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) @@ -567,44 +568,49 @@ bool CCECBusDevice::TryLogicalAddress(void) void CCECBusDevice::SetDeviceStatus(const cec_bus_device_status newStatus) { - CLockObject lock(m_mutex); - switch (newStatus) - { - case CEC_DEVICE_STATUS_UNKNOWN: - if (m_deviceStatus != newStatus) - CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'unknown'", ToString(m_iLogicalAddress)); - m_iStreamPath = 0; - m_powerStatus = CEC_POWER_STATUS_UNKNOWN; - m_vendor = CEC_VENDOR_UNKNOWN; - m_menuState = CEC_MENU_STATE_ACTIVATED; - m_bActiveSource = false; - m_iLastActive = 0; - m_cecVersion = CEC_VERSION_UNKNOWN; - m_deviceStatus = newStatus; - break; - case CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC: - if (m_deviceStatus != newStatus) - CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'handled by libCEC'", ToString(m_iLogicalAddress)); - m_iStreamPath = 0; - m_powerStatus = CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON; - m_vendor = CEC_VENDOR_UNKNOWN; - m_menuState = CEC_MENU_STATE_ACTIVATED; - m_bActiveSource = false; - m_iLastActive = 0; - m_cecVersion = CEC_VERSION_1_3A; - m_deviceStatus = newStatus; - break; - case CEC_DEVICE_STATUS_PRESENT: - if (m_deviceStatus != newStatus) - CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'present'", ToString(m_iLogicalAddress)); - m_deviceStatus = newStatus; - break; - case CEC_DEVICE_STATUS_NOT_PRESENT: - if (m_deviceStatus != newStatus) - CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'not present'", ToString(m_iLogicalAddress)); - m_deviceStatus = newStatus; - break; + { + CLockObject lock(m_mutex); + switch (newStatus) + { + case CEC_DEVICE_STATUS_UNKNOWN: + if (m_deviceStatus != newStatus) + CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'unknown'", ToString(m_iLogicalAddress)); + m_iStreamPath = 0; + m_powerStatus = CEC_POWER_STATUS_UNKNOWN; + m_vendor = CEC_VENDOR_UNKNOWN; + m_menuState = CEC_MENU_STATE_ACTIVATED; + m_bActiveSource = false; + m_iLastActive = 0; + m_cecVersion = CEC_VERSION_UNKNOWN; + m_deviceStatus = newStatus; + break; + case CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC: + if (m_deviceStatus != newStatus) + CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'handled by libCEC'", ToString(m_iLogicalAddress)); + m_iStreamPath = 0; + m_powerStatus = CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON; + m_vendor = CEC_VENDOR_UNKNOWN; + m_menuState = CEC_MENU_STATE_ACTIVATED; + m_bActiveSource = false; + m_iLastActive = 0; + m_cecVersion = CEC_VERSION_1_3A; + m_deviceStatus = newStatus; + break; + case CEC_DEVICE_STATUS_PRESENT: + if (m_deviceStatus != newStatus) + CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'present'", ToString(m_iLogicalAddress)); + m_deviceStatus = newStatus; + break; + case CEC_DEVICE_STATUS_NOT_PRESENT: + if (m_deviceStatus != newStatus) + CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'not present'", ToString(m_iLogicalAddress)); + m_deviceStatus = newStatus; + break; + } } + + if (newStatus == CEC_DEVICE_STATUS_PRESENT) + RequestVendorId(false); } void CCECBusDevice::SetPhysicalAddress(uint16_t iNewAddress) @@ -625,6 +631,21 @@ 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; + // 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 + device->SetActiveSource(); + } + else + { + // try to find the device with the old address, and mark it as inactive when found + device = m_processor->GetDeviceByPhysicalAddress(iOldAddress, false, true); + if (device) + device->SetInactiveSource(); + } + if (iNewAddress > 0) { lock.Unlock(); @@ -869,6 +890,35 @@ bool CCECBusDevice::TransmitPhysicalAddress(void) return bReturn; } +bool CCECBusDevice::TransmitSetMenuLanguage(cec_logical_address dest) +{ + bool bReturn(false); + cec_menu_language language = GetMenuLanguage(); + + char lang[3]; + { + CLockObject lock(m_mutex); + lang[0] = language.language[0]; + lang[1] = language.language[1]; + lang[2] = language.language[2]; + } + + MarkBusy(); + if (lang[0] == '?' && lang[1] == '?' && lang[2] == '?') + { + CLibCEC::AddLog(CEC_LOG_NOTICE, "<< %s (%X) -> %s (%X): Menu language feature abort", GetLogicalAddressName(), m_iLogicalAddress, ToString(dest), dest); + m_processor->TransmitAbort(dest, CEC_OPCODE_GIVE_DEVICE_VENDOR_ID); + bReturn = true; + } + else + { + CLibCEC::AddLog(CEC_LOG_NOTICE, "<< %s (%X) -> broadcast (F): Menu language '%s'", GetLogicalAddressName(), m_iLogicalAddress, lang); + bReturn = m_handler->TransmitSetMenuLanguage(m_iLogicalAddress, lang); + } + MarkReady(); + return bReturn; +} + bool CCECBusDevice::TransmitPoll(cec_logical_address dest) { bool bReturn(false); @@ -984,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 30ac2dc..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); @@ -100,6 +101,7 @@ namespace CEC virtual bool TransmitOSDName(cec_logical_address dest); virtual bool TransmitOSDString(cec_logical_address dest, cec_display_control duration, const char *strMessage); virtual bool TransmitPhysicalAddress(void); + virtual bool TransmitSetMenuLanguage(cec_logical_address dest); virtual bool TransmitPowerState(cec_logical_address dest); virtual bool TransmitPoll(cec_logical_address dest); virtual bool TransmitVendorID(cec_logical_address dest, bool bSendAbort = true); @@ -113,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/ANCommandHandler.cpp b/src/lib/implementations/ANCommandHandler.cpp index f8a8ade..182f0ac 100644 --- a/src/lib/implementations/ANCommandHandler.cpp +++ b/src/lib/implementations/ANCommandHandler.cpp @@ -59,6 +59,9 @@ bool CANCommandHandler::HandleVendorRemoteButtonDown(const cec_command &command) CEC_USER_CONTROL_CODE_AN_RETURN : CEC_USER_CONTROL_CODE_EXIT; break; + case CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST: + key.keycode = CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST; + break; default: break; } @@ -95,3 +98,15 @@ bool CANCommandHandler::HandleCommand(const cec_command &command) return bHandled; } + +bool CANCommandHandler::PowerOn(const cec_logical_address iInitiator, const cec_logical_address iDestination) +{ + if (iDestination == CECDEVICE_AUDIOSYSTEM) + { + /* Samsung AVR devices need to be woken up with key CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION */ + return TransmitKeypress(iInitiator, iDestination, CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION) && + TransmitKeyRelease(iInitiator, iDestination); + } + + return CCECCommandHandler::PowerOn(iInitiator, iDestination); +} diff --git a/src/lib/implementations/ANCommandHandler.h b/src/lib/implementations/ANCommandHandler.h index 23b9de9..d9437d1 100644 --- a/src/lib/implementations/ANCommandHandler.h +++ b/src/lib/implementations/ANCommandHandler.h @@ -44,5 +44,6 @@ namespace CEC virtual bool HandleCommand(const cec_command &command); protected: virtual bool HandleVendorRemoteButtonDown(const cec_command &command); + virtual bool PowerOn(const cec_logical_address iInitiator, const cec_logical_address iDestination); }; }; diff --git a/src/lib/implementations/CECCommandHandler.cpp b/src/lib/implementations/CECCommandHandler.cpp index a9b4c40..eed6087 100644 --- a/src/lib/implementations/CECCommandHandler.cpp +++ b/src/lib/implementations/CECCommandHandler.cpp @@ -61,6 +61,9 @@ CCECCommandHandler::~CCECCommandHandler(void) bool CCECCommandHandler::HandleCommand(const cec_command &command) { + if (command.opcode_set == 0) + return HandlePoll(command); + bool bHandled(true); CLibCEC::AddCommand(command); @@ -80,6 +83,10 @@ bool CCECCommandHandler::HandleCommand(const cec_command &command) if (m_processor->IsInitialised()) HandleGivePhysicalAddress(command); break; + case CEC_OPCODE_GET_MENU_LANGUAGE: + if (m_processor->IsInitialised()) + HandleGiveMenuLanguage(command); + break; case CEC_OPCODE_GIVE_OSD_NAME: if (m_processor->IsInitialised()) HandleGiveOSDName(command); @@ -338,6 +345,18 @@ bool CCECCommandHandler::HandleGivePhysicalAddress(const cec_command &command) return false; } +bool CCECCommandHandler::HandleGiveMenuLanguage(const cec_command &command) +{ + if (m_processor->IsRunning() && m_busDevice->MyLogicalAddressContains(command.destination)) + { + CCECBusDevice *device = GetDevice(command.destination); + if (device) + return device->TransmitSetMenuLanguage(command.initiator); + } + + return false; +} + bool CCECCommandHandler::HandleGiveSystemAudioModeStatus(const cec_command &command) { if (m_processor->IsRunning() && m_busDevice->MyLogicalAddressContains(command.destination)) @@ -371,6 +390,12 @@ bool CCECCommandHandler::HandleMenuRequest(const cec_command &command) return false; } +bool CCECCommandHandler::HandlePoll(const cec_command &command) +{ + m_busDevice->HandlePoll(command.destination); + return true; +} + bool CCECCommandHandler::HandleReportAudioStatus(const cec_command &command) { if (command.parameters.size == 1) @@ -582,37 +607,49 @@ bool CCECCommandHandler::HandleTextViewOn(const cec_command &command) bool CCECCommandHandler::HandleUserControlPressed(const cec_command &command) { - if (m_processor->IsRunning() && m_busDevice->MyLogicalAddressContains(command.destination) && command.parameters.size > 0) + if (m_processor->IsRunning() && + m_busDevice->MyLogicalAddressContains(command.destination) && + command.parameters.size > 0) { CLibCEC::AddKey(); - if (command.parameters[0] <= CEC_USER_CONTROL_CODE_MAX) + CLibCEC::SetCurrentButton((cec_user_control_code) command.parameters[0]); + + if (command.parameters[0] == CEC_USER_CONTROL_CODE_POWER || + command.parameters[0] == CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION) { - if (command.parameters[0] == CEC_USER_CONTROL_CODE_POWER || - command.parameters[0] == CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION) + bool bPowerOn(true); + CCECBusDevice *device = GetDevice(command.destination); + if (!device) + return true; + + // CEC_USER_CONTROL_CODE_POWER operates as a toggle + // assume CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION does not + if (command.parameters[0] == CEC_USER_CONTROL_CODE_POWER) { - CCECBusDevice *device = GetDevice(command.destination); - if (device) - { - device->SetPowerStatus(CEC_POWER_STATUS_ON); - if (device->MyLogicalAddressContains(device->GetLogicalAddress())) - { - device->SetActiveSource(); - device->TransmitImageViewOn(); - device->TransmitActiveSource(); - - if (device->GetType() == CEC_DEVICE_TYPE_PLAYBACK_DEVICE || - device->GetType() == CEC_DEVICE_TYPE_RECORDING_DEVICE) - ((CCECPlaybackDevice *)device)->TransmitDeckStatus(command.initiator); - } - } + cec_power_status status = device->GetPowerStatus(); + bPowerOn = !(status == CEC_POWER_STATUS_ON || status == CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON); + } + + if (bPowerOn) + { + device->SetActiveSource(); + device->TransmitImageViewOn(); + device->TransmitActiveSource(); + + if (device->GetType() == CEC_DEVICE_TYPE_PLAYBACK_DEVICE || + device->GetType() == CEC_DEVICE_TYPE_RECORDING_DEVICE) + ((CCECPlaybackDevice *)device)->TransmitDeckStatus(command.initiator); } else { - CLibCEC::SetCurrentButton((cec_user_control_code) command.parameters[0]); + device->SetInactiveSource(); + device->TransmitInactiveSource(); + device->SetMenuState(CEC_MENU_STATE_DEACTIVATED); } - return true; } + + return true; } return false; } @@ -732,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) @@ -854,6 +891,17 @@ bool CCECCommandHandler::TransmitPhysicalAddress(const cec_logical_address iInit return Transmit(command, false); } +bool CCECCommandHandler::TransmitSetMenuLanguage(const cec_logical_address iInitiator, const char lang[3]) +{ + cec_command command; + command.Format(command, iInitiator, CECDEVICE_BROADCAST, CEC_OPCODE_SET_MENU_LANGUAGE); + command.parameters.PushBack((uint8_t) lang[0]); + command.parameters.PushBack((uint8_t) lang[1]); + command.parameters.PushBack((uint8_t) lang[2]); + + return Transmit(command, false); +} + bool CCECCommandHandler::TransmitPoll(const cec_logical_address iInitiator, const cec_logical_address iDestination) { cec_command command; @@ -951,9 +999,14 @@ bool CCECCommandHandler::Transmit(cec_command &command, bool bExpectResponse /* bool bReturn(false); command.transmit_timeout = m_iTransmitTimeout; + if (command.initiator == CECDEVICE_UNKNOWN) + { + CLibCEC::AddLog(CEC_LOG_ERROR, "not transmitting a command without a valid initiator"); + return bReturn; + } + { uint8_t iTries(0), iMaxTries(command.opcode == CEC_OPCODE_NONE ? 1 : m_iTransmitRetries + 1); - CLockObject writeLock(m_processor->m_transmitMutex); while (!bReturn && ++iTries <= iMaxTries) { if ((bReturn = m_processor->Transmit(command)) == true) diff --git a/src/lib/implementations/CECCommandHandler.h b/src/lib/implementations/CECCommandHandler.h index 4cfb81f..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); @@ -145,6 +145,7 @@ namespace CEC virtual bool TransmitOSDName(const cec_logical_address iInitiator, const cec_logical_address iDestination, CStdString strDeviceName); virtual bool TransmitOSDString(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_display_control duration, const char *strMessage); virtual bool TransmitPhysicalAddress(const cec_logical_address iInitiator, uint16_t iPhysicalAddress, cec_device_type type); + virtual bool TransmitSetMenuLanguage(const cec_logical_address iInitiator, const char lang[3]); virtual bool TransmitPoll(const cec_logical_address iInitiator, const cec_logical_address iDestination); virtual bool TransmitPowerState(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_power_status state); virtual bool TransmitVendorID(const cec_logical_address iInitiator, uint64_t iVendorId); @@ -171,9 +172,11 @@ namespace CEC virtual bool HandleGiveDeviceVendorId(const cec_command &command); virtual bool HandleGiveOSDName(const cec_command &command); virtual bool HandleGivePhysicalAddress(const cec_command &command); + virtual bool HandleGiveMenuLanguage(const cec_command &command); virtual bool HandleGiveSystemAudioModeStatus(const cec_command &command); virtual bool HandleImageViewOn(const cec_command &command); virtual bool HandleMenuRequest(const cec_command &command); + virtual bool HandlePoll(const cec_command &command); virtual bool HandleReportAudioStatus(const cec_command &command); virtual bool HandleReportPhysicalAddress(const cec_command &command); virtual bool HandleReportPowerStatus(const cec_command &command); diff --git a/src/lib/implementations/SLCommandHandler.cpp b/src/lib/implementations/SLCommandHandler.cpp index 692a5fd..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; } @@ -411,10 +411,14 @@ bool CSLCommandHandler::PowerOn(const cec_logical_address iInitiator, const cec_ { /* LG devices only allow themselves to be woken up by the TV with a vendor command */ cec_command command; + + if (!m_bSLEnabled) + TransmitVendorID(CECDEVICE_TV, CEC_VENDOR_LG); + 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/posix/os-threads.h b/src/lib/platform/posix/os-threads.h index fc28fc9..8b56731 100644 --- a/src/lib/platform/posix/os-threads.h +++ b/src/lib/platform/posix/os-threads.h @@ -52,8 +52,8 @@ namespace PLATFORM #ifdef __APPLE__ struct timeval tv; gettimeofday(&tv, NULL); - now.tv_sec = tv.tv_sec + 0; - now.tv_nsec = 0; + now.tv_sec = tv.tv_sec; + now.tv_nsec = tv.tv_usec * 1000; #else clock_gettime(CLOCK_REALTIME, &now); #endif diff --git a/src/lib/platform/posix/os-types.h b/src/lib/platform/posix/os-types.h index 631e965..b89b9cc 100644 --- a/src/lib/platform/posix/os-types.h +++ b/src/lib/platform/posix/os-types.h @@ -32,10 +32,11 @@ */ #define _FILE_OFFSET_BITS 64 +#include #include #include #include -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(__FreeBSD__) #include #endif #include diff --git a/src/lib/platform/posix/serialport.cpp b/src/lib/platform/posix/serialport.cpp index 5557064..cefc212 100644 --- a/src/lib/platform/posix/serialport.cpp +++ b/src/lib/platform/posix/serialport.cpp @@ -37,7 +37,7 @@ #include "../util/baudrate.h" #include "../posix/os-socket.h" -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__FreeBSD__) #ifndef XCASE #define XCASE 0 #endif @@ -53,29 +53,32 @@ using namespace PLATFORM; void CSerialSocket::Close(void) { - SocketClose(m_socket); + if (IsOpen()) + SocketClose(m_socket); } void CSerialSocket::Shutdown(void) { - SocketClose(m_socket); + if (IsOpen()) + SocketClose(m_socket); } ssize_t CSerialSocket::Write(void* data, size_t len) { - return SocketWrite(m_socket, &m_iError, data, len); + return IsOpen() ? SocketWrite(m_socket, &m_iError, data, len) : -1; } ssize_t CSerialSocket::Read(void* data, size_t len, uint64_t iTimeoutMs /* = 0 */) { - return SocketRead(m_socket, &m_iError, data, len, iTimeoutMs); + return IsOpen() ? SocketRead(m_socket, &m_iError, data, len, iTimeoutMs) : -1; } //setting all this stuff up is a pain in the ass bool CSerialSocket::Open(uint64_t iTimeoutMs /* = 0 */) { iTimeoutMs = 0; - CLockObject lock(m_mutex); + if (IsOpen()) + return false; if (m_iDatabits != SERIAL_DATA_BITS_FIVE && m_iDatabits != SERIAL_DATA_BITS_SIX && m_iDatabits != SERIAL_DATA_BITS_SEVEN && m_iDatabits != SERIAL_DATA_BITS_EIGHT) diff --git a/src/lib/platform/sockets/serialport.h b/src/lib/platform/sockets/serialport.h index c74cc05..eed05a0 100644 --- a/src/lib/platform/sockets/serialport.h +++ b/src/lib/platform/sockets/serialport.h @@ -71,13 +71,16 @@ namespace PLATFORM public: CSerialSocket(const CStdString &strName, uint32_t iBaudrate, SerialDataBits iDatabits = SERIAL_DATA_BITS_EIGHT, SerialStopBits iStopbits = SERIAL_STOP_BITS_ONE, SerialParity iParity = SERIAL_PARITY_NONE) : CCommonSocket(INVALID_SERIAL_SOCKET_VALUE, strName), + #ifdef __WINDOWS__ + m_iCurrentReadTimeout(MAXDWORD), + #endif m_bIsOpen(false), m_iBaudrate(iBaudrate), m_iDatabits(iDatabits), m_iStopbits(iStopbits), m_iParity(iParity) {} - virtual ~CSerialSocket(void) {} + virtual ~CSerialSocket(void) { Close(); } virtual bool Open(uint64_t iTimeoutMs = 0); virtual void Close(void); @@ -96,6 +99,9 @@ namespace PLATFORM protected: #ifndef __WINDOWS__ struct termios m_options; + #else + bool SetTimeouts(serial_socket_t socket, int* iError, DWORD iTimeoutMs); + DWORD m_iCurrentReadTimeout; #endif bool m_bIsOpen; diff --git a/src/lib/platform/threads/threads.h b/src/lib/platform/threads/threads.h index c730da8..97774b3 100644 --- a/src/lib/platform/threads/threads.h +++ b/src/lib/platform/threads/threads.h @@ -45,7 +45,7 @@ namespace PLATFORM virtual ~CThread(void) { - StopThread(); + StopThread(0); } static void *ThreadHandler(CThread *thread) 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; diff --git a/src/lib/platform/util/timeutils.h b/src/lib/platform/util/timeutils.h index b886f88..5f2d27a 100644 --- a/src/lib/platform/util/timeutils.h +++ b/src/lib/platform/util/timeutils.h @@ -78,17 +78,14 @@ namespace PLATFORM inline int64_t GetTimeMs() { #if defined(__APPLE__) - return (int64_t) (CVGetCurrentHostTime() * 1000 / CVGetHostClockFrequency()); + return (int64_t) (CVGetCurrentHostTime() / (int64_t)(CVGetHostClockFrequency() * 0.001)); #elif defined(__WINDOWS__) - time_t rawtime; - time(&rawtime); - LARGE_INTEGER tickPerSecond; LARGE_INTEGER tick; if (QueryPerformanceFrequency(&tickPerSecond)) { QueryPerformanceCounter(&tick); - return (int64_t) (tick.QuadPart / 1000.); + return (int64_t) (tick.QuadPart / (tickPerSecond.QuadPart / 1000.)); } return -1; #else diff --git a/src/lib/platform/windows/os-threads.cpp b/src/lib/platform/windows/os-threads.cpp new file mode 100644 index 0000000..7c06d41 --- /dev/null +++ b/src/lib/platform/windows/os-threads.cpp @@ -0,0 +1,136 @@ +/* + * 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 "../os.h" +#include "os-threads.h" +using namespace PLATFORM; + +static ConditionArg g_InitializeConditionVariable; +static ConditionArg g_WakeConditionVariable; +static ConditionArg g_WakeAllConditionVariable; +static ConditionMutexArg g_SleepConditionVariableCS; + +// check whether vista+ conditions are available at runtime +static bool CheckVistaConditionFunctions(void) +{ + static int iHasVistaConditionFunctions(-1); + if (iHasVistaConditionFunctions == -1) + { + HMODULE handle = GetModuleHandle("Kernel32"); + if (handle == NULL) + { + iHasVistaConditionFunctions = 0; + } + else + { + g_InitializeConditionVariable = (ConditionArg) GetProcAddress(handle,"InitializeConditionVariable"); + g_WakeConditionVariable = (ConditionArg) GetProcAddress(handle,"WakeConditionVariable"); + g_WakeAllConditionVariable = (ConditionArg) GetProcAddress(handle,"WakeAllConditionVariable"); + g_SleepConditionVariableCS = (ConditionMutexArg)GetProcAddress(handle,"SleepConditionVariableCS"); + + // 1 when everything is resolved, 0 otherwise + iHasVistaConditionFunctions = g_InitializeConditionVariable && + g_WakeConditionVariable && + g_WakeAllConditionVariable && + g_SleepConditionVariableCS ? 1 : 0; + } + } + return iHasVistaConditionFunctions == 1; +} + +CConditionImpl::CConditionImpl(void) +{ + m_bOnVista = CheckVistaConditionFunctions(); + if (m_bOnVista) + (*g_InitializeConditionVariable)(m_conditionVista = new CONDITION_VARIABLE); + else + m_conditionPreVista = ::CreateEvent(NULL, TRUE, FALSE, NULL); +} + +CConditionImpl::~CConditionImpl(void) +{ + if (m_bOnVista) + delete m_conditionVista; + else + ::CloseHandle(m_conditionPreVista); +} + +void CConditionImpl::Signal(void) +{ + if (m_bOnVista) + (*g_WakeConditionVariable)(m_conditionVista); + else + ::SetEvent(m_conditionPreVista); +} + +void CConditionImpl::Broadcast(void) +{ + if (m_bOnVista) + (*g_WakeAllConditionVariable)(m_conditionVista); + else + ::SetEvent(m_conditionPreVista); +} + +bool CConditionImpl::Wait(mutex_t &mutex) +{ + if (m_bOnVista) + { + return ((*g_SleepConditionVariableCS)(m_conditionVista, mutex, INFINITE) ? true : false); + } + else + { + ::ResetEvent(m_conditionPreVista); + MutexUnlock(mutex); + DWORD iWaitReturn = ::WaitForSingleObject(m_conditionPreVista, 1000); + MutexLock(mutex); + return (iWaitReturn == 0); + } +} + +bool CConditionImpl::Wait(mutex_t &mutex, uint32_t iTimeoutMs) +{ + if (iTimeoutMs == 0) + return Wait(mutex); + + if (m_bOnVista) + { + return ((*g_SleepConditionVariableCS)(m_conditionVista, mutex, iTimeoutMs) ? true : false); + } + else + { + ::ResetEvent(m_conditionPreVista); + MutexUnlock(mutex); + DWORD iWaitReturn = ::WaitForSingleObject(m_conditionPreVista, iTimeoutMs); + MutexLock(mutex); + return (iWaitReturn == 0); + } +} diff --git a/src/lib/platform/windows/os-threads.h b/src/lib/platform/windows/os-threads.h index e082691..3714c16 100644 --- a/src/lib/platform/windows/os-threads.h +++ b/src/lib/platform/windows/os-threads.h @@ -47,109 +47,16 @@ namespace PLATFORM // windows vista+ conditions typedef VOID (WINAPI *ConditionArg) (CONDITION_VARIABLE*); typedef BOOL (WINAPI *ConditionMutexArg)(CONDITION_VARIABLE*, CRITICAL_SECTION*, DWORD); - static ConditionArg g_InitializeConditionVariable; - static ConditionArg g_WakeConditionVariable; - static ConditionArg g_WakeAllConditionVariable; - static ConditionMutexArg g_SleepConditionVariableCS; - - // check whether vista+ conditions are available at runtime - static bool CheckVistaConditionFunctions(void) - { - static int iHasVistaConditionFunctions(-1); - if (iHasVistaConditionFunctions == -1) - { - HMODULE handle = GetModuleHandle("Kernel32"); - if (handle == NULL) - { - iHasVistaConditionFunctions = 0; - } - else - { - g_InitializeConditionVariable = (ConditionArg) GetProcAddress(handle,"InitializeConditionVariable"); - g_WakeConditionVariable = (ConditionArg) GetProcAddress(handle,"WakeConditionVariable"); - g_WakeAllConditionVariable = (ConditionArg) GetProcAddress(handle,"WakeAllConditionVariable"); - g_SleepConditionVariableCS = (ConditionMutexArg)GetProcAddress(handle,"SleepConditionVariableCS"); - - // 1 when everything is resolved, 0 otherwise - iHasVistaConditionFunctions = g_InitializeConditionVariable && - g_WakeConditionVariable && - g_WakeAllConditionVariable && - g_SleepConditionVariableCS ? 1 : 0; - } - } - return iHasVistaConditionFunctions == 1; - } class CConditionImpl { public: - CConditionImpl(void) - { - m_bOnVista = CheckVistaConditionFunctions(); - if (m_bOnVista) - (*g_InitializeConditionVariable)(m_conditionVista = new CONDITION_VARIABLE); - else - m_conditionPreVista = ::CreateEvent(NULL, TRUE, FALSE, NULL); - } - - virtual ~CConditionImpl(void) - { - if (m_bOnVista) - delete m_conditionVista; - else - ::CloseHandle(m_conditionPreVista); - } - - void Signal(void) - { - if (m_bOnVista) - (*g_WakeConditionVariable)(m_conditionVista); - else - ::SetEvent(m_conditionPreVista); - } - - void Broadcast(void) - { - if (m_bOnVista) - (*g_WakeAllConditionVariable)(m_conditionVista); - else - ::SetEvent(m_conditionPreVista); - } - - bool Wait(mutex_t &mutex) - { - if (m_bOnVista) - { - return ((*g_SleepConditionVariableCS)(m_conditionVista, mutex, INFINITE) ? true : false); - } - else - { - ::ResetEvent(m_conditionPreVista); - MutexUnlock(mutex); - DWORD iWaitReturn = ::WaitForSingleObject(m_conditionPreVista, 1000); - MutexLock(mutex); - return (iWaitReturn == 0); - } - } - - bool Wait(mutex_t &mutex, uint32_t iTimeoutMs) - { - if (iTimeoutMs == 0) - return Wait(mutex); - - if (m_bOnVista) - { - return ((*g_SleepConditionVariableCS)(m_conditionVista, mutex, iTimeoutMs) ? true : false); - } - else - { - ::ResetEvent(m_conditionPreVista); - MutexUnlock(mutex); - DWORD iWaitReturn = ::WaitForSingleObject(m_conditionPreVista, iTimeoutMs); - MutexLock(mutex); - return (iWaitReturn == 0); - } - } + CConditionImpl(void); + virtual ~CConditionImpl(void); + void Signal(void); + void Broadcast(void); + bool Wait(mutex_t &mutex); + bool Wait(mutex_t &mutex, uint32_t iTimeoutMs); bool m_bOnVista; CONDITION_VARIABLE *m_conditionVista; diff --git a/src/lib/platform/windows/serialport.cpp b/src/lib/platform/windows/serialport.cpp index 680b1f0..96e743f 100644 --- a/src/lib/platform/windows/serialport.cpp +++ b/src/lib/platform/windows/serialport.cpp @@ -48,28 +48,25 @@ void FormatWindowsError(int iErrorCode, CStdString &strMessage) } } -bool SetTimeouts(serial_socket_t socket, int* iError, bool bBlocking) +bool CSerialSocket::SetTimeouts(serial_socket_t socket, int* iError, DWORD iTimeoutMs) { if (socket == INVALID_HANDLE_VALUE) return false; - COMMTIMEOUTS cto; - if (!GetCommTimeouts(socket, &cto)) - { - *iError = GetLastError(); - return false; - } + if (iTimeoutMs == m_iCurrentReadTimeout) + return true; - if (bBlocking) + COMMTIMEOUTS cto; + if (iTimeoutMs == 0) { - cto.ReadIntervalTimeout = 0; + cto.ReadIntervalTimeout = MAXDWORD; cto.ReadTotalTimeoutConstant = 0; cto.ReadTotalTimeoutMultiplier = 0; } else { - cto.ReadIntervalTimeout = MAXDWORD; - cto.ReadTotalTimeoutConstant = 0; + cto.ReadIntervalTimeout = 0; + cto.ReadTotalTimeoutConstant = iTimeoutMs; cto.ReadTotalTimeoutMultiplier = 0; } @@ -78,33 +75,50 @@ bool SetTimeouts(serial_socket_t socket, int* iError, bool bBlocking) *iError = GetLastError(); return false; } + else + { + m_iCurrentReadTimeout = iTimeoutMs; + } return true; } void CSerialSocket::Close(void) { - SerialSocketClose(m_socket); + if (IsOpen()) + SerialSocketClose(m_socket); + m_socket = INVALID_SERIAL_SOCKET_VALUE; } void CSerialSocket::Shutdown(void) { - SerialSocketClose(m_socket); + if (IsOpen()) + SerialSocketClose(m_socket); + m_socket = INVALID_SERIAL_SOCKET_VALUE; } ssize_t CSerialSocket::Write(void* data, size_t len) { - return SerialSocketWrite(m_socket, &m_iError, data, len); + return IsOpen() ? SerialSocketWrite(m_socket, &m_iError, data, len) : -1; } ssize_t CSerialSocket::Read(void* data, size_t len, uint64_t iTimeoutMs /* = 0 */) { - return SerialSocketRead(m_socket, &m_iError, data, len, iTimeoutMs); + DWORD dwTimeoutMs((DWORD)iTimeoutMs); + if (iTimeoutMs != (uint64_t)iTimeoutMs) + dwTimeoutMs = MAXDWORD; + + return IsOpen() && SetTimeouts(m_socket, &m_iError, dwTimeoutMs) ? + SerialSocketRead(m_socket, &m_iError, data, len, iTimeoutMs) : + -1; } bool CSerialSocket::Open(uint64_t iTimeoutMs /* = 0 */) { iTimeoutMs = 0; + if (IsOpen()) + return false; + CStdString strComPath = "\\\\.\\" + m_strName; CLockObject lock(m_mutex); m_socket = CreateFile(strComPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); @@ -146,7 +160,7 @@ bool CSerialSocket::Open(uint64_t iTimeoutMs /* = 0 */) return false; } - if (!SetTimeouts(m_socket, &m_iError, false)) + if (!SetTimeouts(m_socket, &m_iError, 0)) { m_strError = "unable to set timeouts"; FormatWindowsError(GetLastError(), m_strError); diff --git a/src/testclient/main.cpp b/src/testclient/main.cpp index 2b2c54d..c47017d 100644 --- a/src/testclient/main.cpp +++ b/src/testclient/main.cpp @@ -181,6 +181,7 @@ void EnableCallbacks(ICECAdapter *adapter) g_callbacks.CBCecKeyPress = &CecKeyPress; g_callbacks.CBCecCommand = &CecCommand; g_callbacks.CBCecConfigurationChanged = NULL; + g_callbacks.CBCecAlert = NULL; adapter->EnableCallbacks(NULL, &g_callbacks); } @@ -194,9 +195,22 @@ void ListDevices(ICECAdapter *parser) } else { - PrintToStdOut("Found devices: %d\n", iDevicesFound); + CStdString strDeviceInfo; + strDeviceInfo.Format("Found devices: %d\n\n", iDevicesFound); + for (int8_t iDevicePtr = 0; iDevicePtr < iDevicesFound; iDevicePtr++) - PrintToStdOut("device: %d\npath: %s\ncom port: %s\n", iDevicePtr + 1, devices[iDevicePtr].path, devices[iDevicePtr].comm); + { + strDeviceInfo.AppendFormat("device: %d\ncom port: %s\n", iDevicePtr + 1, devices[iDevicePtr].comm); + libcec_configuration config; + config.Clear(); + + if (!parser->GetDeviceInformation(devices[iDevicePtr].comm, &config)) + PrintToStdOut("WARNING: unable to open the device on port %s", devices[iDevicePtr].comm); + else + strDeviceInfo.AppendFormat("firmware version: %d\n", config.iFirmwareVersion); + strDeviceInfo.append("\n"); + } + PrintToStdOut(strDeviceInfo.c_str()); } } @@ -214,11 +228,13 @@ void ShowHelpCommandLine(const char* strExec) " -b --base {int} The logical address of the device to with this " << endl << " adapter is connected." << endl << " -f --log-file {file} Writes all libCEC log message to a file" << endl << + " -r --rom Read persisted settings from the EEPROM" << endl << " -sf --short-log-file {file} Writes all libCEC log message without timestamps" << endl << " and log levels to a file." << endl << " -d --log-level {level} Sets the log level. See cectypes.h for values." << endl << " -s --single-command Execute a single command and exit. Does not power" << endl << " on devices on startup and power them off on exit." << endl << + " -o --osd-name {osd name} Use a custom osd name." << endl << " [COM PORT] The com port to connect to. If no COM" << endl << " port is given, the client tries to connect to the" << endl << " first device that is detected." << endl << @@ -1008,12 +1024,35 @@ bool ProcessCommandLineArguments(int argc, char *argv[]) { if (argc >= iArgPtr + 2) { - g_config.iHDMIPort = (int8_t)atoi(argv[iArgPtr + 1]); + uint8_t hdmiport = (int8_t)atoi(argv[iArgPtr + 1]); + if (hdmiport < 1) + hdmiport = 1; + if (hdmiport > 15) + hdmiport = 15; + g_config.iHDMIPort = hdmiport; cout << "using HDMI port '" << (int)g_config.iHDMIPort << "'" << endl; ++iArgPtr; } ++iArgPtr; } + else if (!strcmp(argv[iArgPtr], "-r") || + !strcmp(argv[iArgPtr], "--rom")) + { + cout << "using settings from EEPROM" << endl; + g_config.bGetSettingsFromROM = 1; + ++iArgPtr; + } + else if (!strcmp(argv[iArgPtr], "-o") || + !strcmp(argv[iArgPtr], "--osd-name")) + { + if (argc >= iArgPtr + 2) + { + snprintf(g_config.strDeviceName, 13, "%s", argv[iArgPtr + 1]); + cout << "using osd name " << g_config.strDeviceName << endl; + ++iArgPtr; + } + ++iArgPtr; + } else { g_strPort = argv[iArgPtr++]; @@ -1028,12 +1067,12 @@ int main (int argc, char *argv[]) { g_config.Clear(); snprintf(g_config.strDeviceName, 13, "CECTester"); - g_config.callbackParam = NULL; - g_config.clientVersion = CEC_CLIENT_VERSION_1_5_0; - g_callbacks.CBCecLogMessage = &CecLogMessage; - g_callbacks.CBCecKeyPress = &CecKeyPress; - g_callbacks.CBCecCommand = &CecCommand; - g_config.callbacks = &g_callbacks; + g_config.callbackParam = NULL; + g_config.clientVersion = CEC_CLIENT_VERSION_1_6_0; + g_callbacks.CBCecLogMessage = &CecLogMessage; + g_callbacks.CBCecKeyPress = &CecKeyPress; + g_callbacks.CBCecCommand = &CecCommand; + g_config.callbacks = &g_callbacks; if (!ProcessCommandLineArguments(argc, argv)) return 0; diff --git a/support/create-driver-installer.cmd b/support/create-driver-installer.cmd index 86d17cf..153d953 100644 --- a/support/create-driver-installer.cmd +++ b/support/create-driver-installer.cmd @@ -26,6 +26,7 @@ IF EXIST "..\support\private\create-cat.cmd" ( :CREATEINSTALLER echo. Creating the installer +cd ..\project %NSIS% /V1 /X"SetCompressor /FINAL lzma" "p8-usbcec-driver.nsi" IF NOT EXIST "..\build\p8-usbcec-driver-installer.exe" GOTO :ERRORCREATINGINSTALLER diff --git a/support/create-installer.cmd b/support/create-installer.bat similarity index 92% rename from support/create-installer.cmd rename to support/create-installer.bat index 8c37a8f..73b7a0f 100644 --- a/support/create-installer.cmd +++ b/support/create-installer.bat @@ -1,5 +1,7 @@ @echo off +set EXITCODE=1 + rem Check for NSIS IF EXIST "%ProgramFiles%\NSIS\makensis.exe" ( set NSIS="%ProgramFiles%\NSIS\makensis.exe" @@ -7,10 +9,6 @@ IF EXIST "%ProgramFiles%\NSIS\makensis.exe" ( set NSIS="%ProgramFiles(x86)%\NSIS\makensis.exe" ) ELSE GOTO NONSIS -rem Check for the Windows DDK -IF NOT EXIST "C:\WinDDK\7600.16385.1" GOTO NODDK -set DDK="C:\WinDDK\7600.16385.1" - rem Check for VC10 IF "%VS100COMNTOOLS%"=="" ( set COMPILER10="%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\VCExpress.exe" @@ -26,6 +24,10 @@ mkdir ..\build IF EXIST "..\support\p8-usbcec-driver-installer.exe" ( copy "..\support\p8-usbcec-driver-installer.exe" "..\build\." ) ELSE ( + rem Check for the Windows DDK + IF NOT EXIST "C:\WinDDK\7600.16385.1" GOTO NODDK + set DDK="C:\WinDDK\7600.16385.1" + call create-driver-installer.cmd ) @@ -127,8 +129,14 @@ IF EXIST "..\support\private\sign-binary.cmd" ( CALL ..\support\private\sign-binary.cmd ..\build\libCEC-installer.exe ) -echo. The installer can be found here: ..\build\libCEC-installer.exe +IF "%1%"=="" ( + echo. The installer can be found here: ..\build\libCEC-installer.exe +) ELSE ( + move ..\build\libCEC-installer.exe ..\build\libCEC-%1%-installer.exe + echo. The installer can be found here: ..\build\libCEC-%1%-installer.exe +) +set EXITCODE=0 GOTO EXIT :NOSDK10 @@ -155,4 +163,6 @@ del /q /f ..\build\*.lib del /q /f ..\build\*.exp del /s /f /q ..\build\x64 rmdir ..\build\x64 -cd ..\support \ No newline at end of file +cd ..\support + +exit %EXITCODE% diff --git a/support/p8-usbcec-driver-installer.exe b/support/p8-usbcec-driver-installer.exe new file mode 100644 index 0000000..13596e7 Binary files /dev/null and b/support/p8-usbcec-driver-installer.exe differ