diff --git a/CMakeLists.txt b/CMakeLists.txt index 0710bcca..52659c5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,17 +50,19 @@ SET ( DEFAULT_USE_SYSTEM_MBEDTLS_LIBS OFF ) SET ( DEFAULT_TESTS OFF ) IF ( ${CMAKE_SYSTEM} MATCHES "Linux" ) - SET ( DEFAULT_V4L2 ON ) - SET ( DEFAULT_SPIDEV ON ) - SET ( DEFAULT_TINKERFORGE ON) - SET ( DEFAULT_FB ON ) - SET ( DEFAULT_USB_HID ON ) + SET ( DEFAULT_V4L2 ON ) + SET ( DEFAULT_SPIDEV ON ) + SET ( DEFAULT_TINKERFORGE ON ) + SET ( DEFAULT_FB ON ) + SET ( DEFAULT_USB_HID ON ) + SET ( DEFAULT_CEC ON ) ELSE() - SET ( DEFAULT_V4L2 OFF ) - SET ( DEFAULT_FB OFF ) - SET ( DEFAULT_SPIDEV OFF ) - SET ( DEFAULT_TINKERFORGE OFF) - SET ( DEFAULT_USB_HID OFF ) + SET ( DEFAULT_V4L2 OFF ) + SET ( DEFAULT_FB OFF ) + SET ( DEFAULT_SPIDEV OFF ) + SET ( DEFAULT_TINKERFORGE OFF ) + SET ( DEFAULT_USB_HID OFF ) + SET ( DEFAULT_CEC OFF ) ENDIF() if ( NOT DEFINED PLATFORM ) @@ -173,6 +175,9 @@ message(STATUS "ENABLE_AVAHI = " ${ENABLE_AVAHI}) option(ENABLE_USB_HID "Enable the libusb and hid devices" ${DEFAULT_USB_HID} ) message(STATUS "ENABLE_USB_HID = ${ENABLE_USB_HID}") +option(ENABLE_CEC "Enable the libcec and CEC control" ${DEFAULT_CEC} ) +message(STATUS "ENABLE_CEC = ${ENABLE_CEC}") + option(ENABLE_X11 "Enable the X11 grabber" ${DEFAULT_X11}) message(STATUS "ENABLE_X11 = ${ENABLE_X11}") diff --git a/CompileHowto.md b/CompileHowto.md index 04f3ecd8..4cff5e25 100644 --- a/CompileHowto.md +++ b/CompileHowto.md @@ -40,7 +40,7 @@ wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/ ``` sudo apt-get update -sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite libssl-dev zlib1g-dev +sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite libssl-dev zlib1g-dev ``` **on RPI you need the videocore IV headers** diff --git a/CrossCompileHowto.md b/CrossCompileHowto.md index e17f13d6..8f983eb6 100644 --- a/CrossCompileHowto.md +++ b/CrossCompileHowto.md @@ -4,14 +4,14 @@ Use a clean Raspbian Stretch Lite (on target) and Ubuntu 18/19 (on host) to exec ## On the Target system (here Raspberry Pi) Install required additional packages. ``` -sudo apt-get install qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite aptitude qt5-default rsync libssl-dev zlib1g-dev +sudo apt-get install qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite aptitude qt5-default rsync libssl-dev zlib1g-dev ``` ## On the Host system (here Ubuntu) Update the Ubuntu environment to the latest stage and install required additional packages. ``` sudo apt-get update sudo apt-get upgrade -sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite libssl-dev zlib1g-dev +sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite libssl-dev zlib1g-dev ``` Refine the target IP or hostname, plus userID as required and set-up cross-compilation environment: diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 1bed97d3..d9e96053 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -33,6 +33,9 @@ // Define to enable avahi #cmakedefine ENABLE_AVAHI +// Define to enable cec +#cmakedefine ENABLE_CEC + // Define to enable the usb / hid devices #cmakedefine ENABLE_USB_HID diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index e34c61da..bc3bd5e2 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -636,6 +636,8 @@ "edt_conf_v4l2_cropTop_expl" : "Count of pixels on the top side that are removed from the picture.", "edt_conf_v4l2_cropBottom_title" : "Crop bottom", "edt_conf_v4l2_cropBottom_expl" : "Count of pixels on the bottom side that are removed from the picture.", + "edt_conf_v4l2_cecDetection_title" : "CEC detection", + "edt_conf_v4l2_cecDetection_expl" : "If enabled, usb capture will be temporarily disabled when CEC standby event received from HDMI bus.", "edt_conf_v4l2_signalDetection_title" : "Signal detection", "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found. This will happen when the picture fall below the threshold value for a period of 4 seconds.", "edt_conf_v4l2_redSignalThreshold_title" : "Red signal threshold", diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index 94cab009..bfbaf7a7 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -465,7 +465,8 @@ async function requestLedDeviceProperties(type, params) function requestLedDeviceIdentification(type, params) { sendToHyperion("leddevice", "identify", '"ledDeviceType": "'+type+'","params": '+JSON.stringify(params)+''); - + //let data = {ledDeviceType: type, params: params}; //sendToHyperion("leddevice", "identify", data ); } + diff --git a/bin/compile.sh b/bin/compile.sh index 75adfc4f..ea4767e3 100755 --- a/bin/compile.sh +++ b/bin/compile.sh @@ -5,7 +5,7 @@ CFG="${2:-Release}" INST="$( [ "${3:-}" = "install" ] && echo true || echo false )" sudo apt-get update -sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev libturbojpeg0-dev python3-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libssl-dev libjpeg-dev libqt5sql5-sqlite zlib1g-dev || exit 1 +sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev libturbojpeg0-dev python3-dev libcec-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libssl-dev libjpeg-dev libqt5sql5-sqlite zlib1g-dev || exit 1 if [ -e /dev/vc-cma -a -e /dev/vc-mem ] then diff --git a/bin/create_oe_depedencies.sh b/bin/create_oe_depedencies.sh deleted file mode 100644 index 0971468f..00000000 --- a/bin/create_oe_depedencies.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -RPI_ROOTFS=/home/tvdzwan/hummingboard/rootfs -IMX6_ROOTFS=/home/tvdzwan/hummingboard/rootfs -outfile=hyperion.deps.openelec-imx6.tar.gz - -tar --create --verbose --gzip --absolute-names --show-transformed-names --dereference \ - --file "$outfile" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libaudio.so.2" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libffi.so.6" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libICE.so.6" \ - "$IMX6_ROOTFS/lib/arm-linux-gnueabihf/libpcre.so.3" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libpng12.so.0" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libQtCore.so.4" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libQtGui.so.4" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libQtNetwork.so.4" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libSM.so.6" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libX11.so.6" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libXau.so.6" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libxcb.so.1" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libxcb-util.so" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libxcb-randr.so" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libXdmcp.so.6" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libXext.so.6" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libXrandr.so.2" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libXrender.so.1" \ - "$IMX6_ROOTFS/usr/lib/arm-linux-gnueabihf/libXt.so.6" \ - "./openelec/hyperiond.sh" \ - "./openelec/hyperion-v4l2.sh" \ - "./openelec/hyperion-remote.sh" - diff --git a/bin/create_release.sh b/bin/create_release.sh index 6436572e..bbe302fa 100755 --- a/bin/create_release.sh +++ b/bin/create_release.sh @@ -14,7 +14,7 @@ if ! [ -d "$builddir" ]; then echo "Could not find build director" exit 1 fi - + outfile="$repodir/deploy/hyperion_$buildid.tar.gz" echo create $outfile diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 6c622655..ede9b364 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -24,7 +24,6 @@ macro(DeployUnix TARGET) "libusb-1" "libutil" "libX11" - "libz" ) if (APPLE) diff --git a/cmake/FindCEC.cmake b/cmake/FindCEC.cmake new file mode 100644 index 00000000..0f94c8f3 --- /dev/null +++ b/cmake/FindCEC.cmake @@ -0,0 +1,19 @@ +# - Try to find CEC +# Once done this will define +# +# CEC_FOUND - system has libcec +# CEC_INCLUDE_DIRS - the libcec include directory +# CEC_LIBRARIES - The libcec libraries + +if(PKG_CONFIG_FOUND) + pkg_check_modules (CEC libcec>=3.0.0) +else() + find_path(CEC_INCLUDE_DIRS libcec/cec.h) + find_library(CEC_LIBRARIES cec) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CEC DEFAULT_MSG CEC_INCLUDE_DIRS CEC_LIBRARIES) + +list(APPEND CEC_DEFINITIONS -DHAVE_LIBCEC=1) +mark_as_advanced(CEC_INCLUDE_DIRS CEC_LIBRARIES CEC_DEFINITIONS) diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index d7abd933..43a38a7f 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -110,6 +110,7 @@ /// * cropTop : Cropping from the top [default=0] /// * cropBottom : Cropping from the bottom [default=0] /// * signalDetection : enable/disable signal detection [default=true] + /// * cecDetection : enable/disable cec detection [default=true] /// * redSignalThreshold : Signal threshold for the red channel between 0 and 100 [default=5] /// * greenSignalThreshold : Signal threshold for the green channel between 0 and 100 [default=5] /// * blueSignalThreshold : Signal threshold for the blue channel between 0 and 100 [default=5] @@ -133,6 +134,7 @@ "greenSignalThreshold" : 5, "blueSignalThreshold" : 5, "signalDetection" : false, + "cecDetection" : false, "sDVOffsetMin" : 0.25, "sDHOffsetMin" : 0.25, "sDVOffsetMax" : 0.75, diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 6ed736fa..8fe6675c 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -73,6 +73,7 @@ "greenSignalThreshold" : 5, "blueSignalThreshold" : 5, "signalDetection" : false, + "cecDetection" : false, "sDVOffsetMin" : 0.25, "sDHOffsetMin" : 0.25, "sDVOffsetMax" : 0.75, diff --git a/include/api/API.h b/include/api/API.h index c42a97d9..492cb11c 100644 --- a/include/api/API.h +++ b/include/api/API.h @@ -406,4 +406,4 @@ private: // current instance index quint8 _currInstanceIndex; -}; \ No newline at end of file +}; diff --git a/include/cec/CECEvent.h b/include/cec/CECEvent.h new file mode 100644 index 00000000..1649ad22 --- /dev/null +++ b/include/cec/CECEvent.h @@ -0,0 +1,8 @@ +#pragma once + +enum class CECEvent +{ + On, + Off +}; + diff --git a/include/cec/CECHandler.h b/include/cec/CECHandler.h new file mode 100644 index 00000000..e35bdd20 --- /dev/null +++ b/include/cec/CECHandler.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include + +#include + +#include + +using CECCallbacks = CEC::ICECCallbacks; +using CECAdapter = CEC::ICECAdapter; +using CECAdapterDescriptor = CEC::cec_adapter_descriptor; +using CECLogMessage = CEC::cec_log_message; +using CECKeyPress = CEC::cec_keypress; +using CECCommand = CEC::cec_command; +using CECLogicalAddress = CEC::cec_logical_address; +using CECLogicalAddresses = CEC::cec_logical_addresses; +using CECMenuState = CEC::cec_menu_state; +using CECPowerStatus = CEC::cec_power_status; +using CECVendorId = CEC::cec_vendor_id; +using CECParameter = CEC::libcec_parameter; +using CECConfig = CEC::libcec_configuration; +using CECAlert = CEC::libcec_alert; + +class Logger; + +class CECHandler : public QObject +{ + Q_OBJECT +public: + CECHandler(); + ~CECHandler() override; + + QString scan() const; + +public slots: + bool start(); + void stop(); + +signals: + void cecEvent(CECEvent event); + +private: + /* CEC Callbacks */ + static void onCecLogMessage (void * context, const CECLogMessage * message); + static void onCecKeyPress (void * context, const CECKeyPress * key); + static void onCecAlert (void * context, const CECAlert alert, const CECParameter data); + static void onCecConfigurationChanged (void * context, const CECConfig * configuration); + static void onCecCommandReceived (void * context, const CECCommand * command); + static void onCecSourceActivated (void * context, const CECLogicalAddress address, const uint8_t activated); + static int onCecMenuStateChanged (void * context, const CECMenuState state); + + /* CEC Adapter helpers */ + QVector getAdapters() const; + bool openAdapter(const CECAdapterDescriptor & descriptor); + void printAdapter(const CECAdapterDescriptor & descriptor) const; + + /* CEC Helpers */ + CECCallbacks getCallbacks() const; + CECConfig getConfig() const; + + CECAdapter * _cecAdapter {}; + CECCallbacks _cecCallbacks {}; + CECConfig _cecConfig {}; + + Logger * _logger {}; +}; diff --git a/include/grabber/V4L2Grabber.h b/include/grabber/V4L2Grabber.h index 9ff7aafa..6e7b684a 100644 --- a/include/grabber/V4L2Grabber.h +++ b/include/grabber/V4L2Grabber.h @@ -16,6 +16,7 @@ #include #include #include +#include // general JPEG decoder includes #ifdef HAVE_JPEG_DECODER @@ -67,6 +68,7 @@ public: } bool getSignalDetectionEnabled() { return _signalDetectionEnabled; } + bool getCecDetectionEnabled() { return _cecDetectionEnabled; } int grabFrame(Image &); @@ -98,6 +100,11 @@ public: /// void setSignalDetectionEnable(bool enable) override; + /// + /// @brief overwrite Grabber.h implementation + /// + void setCecDetectionEnable(bool enable) override; + /// /// @brief overwrite Grabber.h implementation /// @@ -149,6 +156,8 @@ public slots: void stop(); + void handleCecEvent(CECEvent event); + signals: void newFrame(const Image & image); void readError(const char* err); @@ -257,6 +266,8 @@ private: int _noSignalCounterThreshold; ColorRgb _noSignalThresholdColor; bool _signalDetectionEnabled; + bool _cecDetectionEnabled; + bool _cecStandbyActivated; bool _noSignalDetected; int _noSignalCounter; double _x_frac_min; diff --git a/include/grabber/V4L2Wrapper.h b/include/grabber/V4L2Wrapper.h index f614f642..8089ff3c 100644 --- a/include/grabber/V4L2Wrapper.h +++ b/include/grabber/V4L2Wrapper.h @@ -19,6 +19,7 @@ public: ~V4L2Wrapper() override; bool getSignalDetectionEnable(); + bool getCecDetectionEnable(); public slots: bool start(); @@ -28,9 +29,9 @@ public slots: void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); void setSignalDetectionOffset(double verticalMin, double horizontalMin, double verticalMax, double horizontalMax); void setSignalDetectionEnable(bool enable); + void setCecDetectionEnable(bool enable); void setDeviceVideoStandard(QString device, VideoStandard videoStandard); - -signals: + void handleCecEvent(CECEvent event); private slots: void newFrame(const Image & image); diff --git a/include/hyperion/Grabber.h b/include/hyperion/Grabber.h index 7ec8026b..e07a448b 100644 --- a/include/hyperion/Grabber.h +++ b/include/hyperion/Grabber.h @@ -80,6 +80,11 @@ public: /// virtual void setSignalDetectionEnable(bool enable) {} + /// + /// @brief Apply CecDetectionEnable (used from v4l) + /// + virtual void setCecDetectionEnable(bool enable) {} + /// /// @brief Apply device and videoStanded (used from v4l) /// diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 3218fb03..76c184fc 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -550,6 +550,5 @@ private: /// Boblight instance BoblightServer* _boblightServer; - /// mutex QMutex _changes; }; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 66724b89..3703966c 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -20,6 +20,10 @@ add_subdirectory(db) add_subdirectory(api) add_subdirectory(python) +if(ENABLE_CEC) + add_subdirectory(cec) +endif() + if(ENABLE_AVAHI) - add_subdirectory(bonjour) -endif() \ No newline at end of file + add_subdirectory(bonjour) +endif() diff --git a/libsrc/api/JSONRPC_schema/schema-leddevice.json b/libsrc/api/JSONRPC_schema/schema-leddevice.json index 73da6c9e..847c1c1d 100644 --- a/libsrc/api/JSONRPC_schema/schema-leddevice.json +++ b/libsrc/api/JSONRPC_schema/schema-leddevice.json @@ -22,7 +22,7 @@ "params": { "type" : "object", "required" : false - } + } }, "additionalProperties": false } diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 1265fbf9..108ba5ec 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -1409,7 +1409,6 @@ void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString & */ { if (subc == "discover") { - QJsonObject config; config.insert("type", devType); diff --git a/libsrc/cec/CECHandler.cpp b/libsrc/cec/CECHandler.cpp new file mode 100644 index 00000000..3f717364 --- /dev/null +++ b/libsrc/cec/CECHandler.cpp @@ -0,0 +1,339 @@ +#include +#include + +#include + +#include + +#include +#include +#include + +/* Enable to turn on detailed CEC logs */ +// #define VERBOSE_CEC + +CECHandler::CECHandler() +{ + qRegisterMetaType("CECEvent"); + + _logger = Logger::getInstance("CEC"); + + _cecCallbacks = getCallbacks(); + _cecConfig = getConfig(); + _cecConfig.callbacks = &_cecCallbacks; + _cecConfig.callbackParam = this; +} + +CECHandler::~CECHandler() +{ + stop(); +} + +bool CECHandler::start() +{ + if (_cecAdapter) + return true; + + Info(_logger, "Starting CEC handler"); + + _cecAdapter = LibCecInitialise(&_cecConfig); + if(!_cecAdapter) + { + Error(_logger, "Failed loading libcec.so"); + return false; + } + + auto adapters = getAdapters(); + if (adapters.isEmpty()) + { + Error(_logger, "Failed to find CEC adapter"); + UnloadLibCec(_cecAdapter); + _cecAdapter = nullptr; + + return false; + } + + Info(_logger, "Auto detecting CEC adapter"); + bool opened = false; + for (const auto & adapter : adapters) + { + printAdapter(adapter); + + if (!opened && openAdapter(adapter)) + { + Info(_logger, "CEC Handler initialized with adapter : %s", adapter.strComName); + + opened = true; + } + } + + if (!opened) + { + UnloadLibCec(_cecAdapter); + _cecAdapter = nullptr; + } + + return opened; +} + +void CECHandler::stop() +{ + if (_cecAdapter) + { + Info(_logger, "Stopping CEC handler"); + + _cecAdapter->Close(); + UnloadLibCec(_cecAdapter); + _cecAdapter = nullptr; + } +} + +CECConfig CECHandler::getConfig() const +{ + CECConfig configuration; + + const std::string name("HyperionCEC"); + name.copy(configuration.strDeviceName, std::min(name.size(), sizeof(configuration.strDeviceName))); + + configuration.deviceTypes.Add(CEC::CEC_DEVICE_TYPE_RECORDING_DEVICE); + configuration.clientVersion = CEC::LIBCEC_VERSION_CURRENT; + + return configuration; +} + +CECCallbacks CECHandler::getCallbacks() const +{ + CECCallbacks callbacks; + + callbacks.sourceActivated = onCecSourceActivated; + callbacks.commandReceived = onCecCommandReceived; + callbacks.alert = onCecAlert; + callbacks.logMessage = onCecLogMessage; + callbacks.keyPress = onCecKeyPress; + callbacks.configurationChanged = onCecConfigurationChanged; + callbacks.menuStateChanged = onCecMenuStateChanged; + + return callbacks; +} + +QVector CECHandler::getAdapters() const +{ + if (!_cecAdapter) + return {}; + + QVector descriptors(16); + int8_t size = _cecAdapter->DetectAdapters(descriptors.data(), descriptors.size(), nullptr, true /*quickscan*/); + descriptors.resize(size); + + return descriptors; +} + +bool CECHandler::openAdapter(const CECAdapterDescriptor & descriptor) +{ + if (!_cecAdapter) + return false; + + if(!_cecAdapter->Open(descriptor.strComName)) + { + Error(_logger, QString("Failed to open the CEC adaper on port %1") + .arg(descriptor.strComName) + .toLocal8Bit()); + + return false; + } + return true; +} + +void CECHandler::printAdapter(const CECAdapterDescriptor & descriptor) const +{ + Info(_logger, QString("CEC Adapter:").toLocal8Bit()); + Info(_logger, QString("\tName : %1").arg(descriptor.strComName).toLocal8Bit()); + Info(_logger, QString("\tPath : %1").arg(descriptor.strComPath).toLocal8Bit()); +} + +QString CECHandler::scan() const +{ + if (!_cecAdapter) + return {}; + + Info(_logger, "Starting CEC scan"); + + QJsonArray devices; + CECLogicalAddresses addresses = _cecAdapter->GetActiveDevices(); + for (int address = CEC::CECDEVICE_TV; address <= CEC::CECDEVICE_BROADCAST; ++address) + { + if (addresses[address]) + { + CECLogicalAddress logicalAddress = (CECLogicalAddress)address; + + QJsonObject device; + CECVendorId vendor = (CECVendorId)_cecAdapter->GetDeviceVendorId(logicalAddress); + CECPowerStatus power = _cecAdapter->GetDevicePowerStatus(logicalAddress); + + device["name" ] = _cecAdapter->GetDeviceOSDName(logicalAddress).c_str(); + device["vendor" ] = _cecAdapter->ToString(vendor); + device["address" ] = _cecAdapter->ToString(logicalAddress); + device["power" ] = _cecAdapter->ToString(power); + + devices << device; + + Info(_logger, QString("\tCECDevice: %1 / %2 / %3 / %4") + .arg(device["name"].toString()) + .arg(device["vendor"].toString()) + .arg(device["address"].toString()) + .arg(device["power"].toString()) + .toLocal8Bit()); + } + } + + return QJsonDocument(devices).toJson(QJsonDocument::Compact); +} + +void CECHandler::onCecLogMessage(void * context, const CECLogMessage * message) +{ +#ifdef VERBOSE_CEC + CECHandler * handler = qobject_cast(static_cast(context)); + if (!handler) + return; + + switch (message->level) + { + case CEC::CEC_LOG_ERROR: + Error(handler->_logger, QString("%1") + .arg(message->message) + .toLocal8Bit()); + break; + case CEC::CEC_LOG_WARNING: + Warning(handler->_logger, QString("%1") + .arg(message->message) + .toLocal8Bit()); + break; + case CEC::CEC_LOG_TRAFFIC: + case CEC::CEC_LOG_NOTICE: + Info(handler->_logger, QString("%1") + .arg(message->message) + .toLocal8Bit()); + break; + case CEC::CEC_LOG_DEBUG: + Debug(handler->_logger, QString("%1") + .arg(message->message) + .toLocal8Bit()); + break; + default: + break; + } +#endif +} + +void CECHandler::onCecKeyPress(void * context, const CECKeyPress * key) +{ +#ifdef VERBOSE_CEC + CECHandler * handler = qobject_cast(static_cast(context)); + if (!handler) + return; + + CECAdapter * adapter = handler->_cecAdapter; + + Debug(handler->_logger, QString("CECHandler::onCecKeyPress: %1") + .arg(adapter->ToString(key->keycode)) + .toLocal8Bit()); +#endif +} + +void CECHandler::onCecAlert(void * context, const CECAlert alert, const CECParameter data) +{ +#ifdef VERBOSE_CEC + CECHandler * handler = qobject_cast(static_cast(context)); + if (!handler) + return; + + Error(handler->_logger, QString("CECHandler::onCecAlert: %1") + .arg(alert) + .toLocal8Bit()); +#endif +} + +void CECHandler::onCecConfigurationChanged(void * context, const CECConfig * configuration) +{ +#ifdef VERBOSE_CEC + CECHandler * handler = qobject_cast(static_cast(context)); + if (!handler) + return; + + Debug(handler->_logger, QString("CECHandler::onCecConfigurationChanged: %1") + .arg(configuration->strDeviceName) + .toLocal8Bit()); +#endif +} + +int CECHandler::onCecMenuStateChanged(void * context, const CECMenuState state) +{ +#ifdef VERBOSE_CEC + CECHandler * handler = qobject_cast(static_cast(context)); + if (!handler) + return 0; + + CECAdapter * adapter = handler->_cecAdapter; + + Debug(handler->_logger, QString("CECHandler::onCecMenuStateChanged: %1") + .arg(adapter->ToString(state)) + .toLocal8Bit()); +#endif + return 0; +} + +void CECHandler::onCecCommandReceived(void * context, const CECCommand * command) +{ + CECHandler * handler = qobject_cast(static_cast(context)); + if (!handler) + return; + + CECAdapter * adapter = handler->_cecAdapter; + +#ifdef VERBOSE_CEC + Debug(handler->_logger, QString("CECHandler::onCecCommandReceived: %1 (%2 > %3)") + .arg(adapter->ToString(command->opcode)) + .arg(adapter->ToString(command->initiator)) + .arg(adapter->ToString(command->destination)) + .toLocal8Bit()); +#endif + /* We do NOT check sender */ + // if (address == CEC::CECDEVICE_TV) + { + if (command->opcode == CEC::CEC_OPCODE_SET_STREAM_PATH) + { + Info(handler->_logger, QString("CEC source activated: %1") + .arg(adapter->ToString(command->initiator)) + .toLocal8Bit()); + emit handler->cecEvent(CECEvent::On); + } + if (command->opcode == CEC::CEC_OPCODE_STANDBY) + { + Info(handler->_logger, QString("CEC source deactivated: %1") + .arg(adapter->ToString(command->initiator)) + .toLocal8Bit()); + emit handler->cecEvent(CECEvent::Off); + } + } +} + +void CECHandler::onCecSourceActivated(void * context, const CECLogicalAddress address, const uint8_t activated) +{ + /* We use CECHandler::onCecCommandReceived for + * source activated/deactivated notifications. */ + +#ifdef VERBOSE_CEC + CECHandler * handler = qobject_cast(static_cast(context)); + if (!handler) + return; + + CECAdapter * adapter = handler->_cecAdapter; + + Debug(handler->_logger, QString("CEC source %1 : %2") + .arg(activated ? "activated" : "deactivated") + .arg(adapter->ToString(address)) + .toLocal8Bit()); +#endif +} + + diff --git a/libsrc/cec/CMakeLists.txt b/libsrc/cec/CMakeLists.txt new file mode 100644 index 00000000..71a951f2 --- /dev/null +++ b/libsrc/cec/CMakeLists.txt @@ -0,0 +1,17 @@ +# Define the current source locations +find_package(CEC REQUIRED) + +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/cec) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/cec) + +FILE (GLOB CEC_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp") +add_library(cechandler ${CEC_SOURCES}) + +include_directories(${CEC_INCLUDE_DIRS}) + +target_link_libraries(cechandler + dl + ${CEC_LIBRARIES} + Qt5::Core +) + diff --git a/libsrc/grabber/v4l2/CMakeLists.txt b/libsrc/grabber/v4l2/CMakeLists.txt index b7a2fdbd..9a41bbe5 100644 --- a/libsrc/grabber/v4l2/CMakeLists.txt +++ b/libsrc/grabber/v4l2/CMakeLists.txt @@ -14,5 +14,5 @@ target_link_libraries(v4l2-grabber if(TURBOJPEG_FOUND) target_link_libraries(v4l2-grabber ${TurboJPEG_LIBRARY}) elseif (JPEG_FOUND) - target_link_libraries(v4l2-grabber ${JPEG_LIBRARY}) + target_link_libraries(v4l2-grabber ${JPEG_LIBRARY}) endif(TURBOJPEG_FOUND) diff --git a/libsrc/grabber/v4l2/V4L2Grabber.cpp b/libsrc/grabber/v4l2/V4L2Grabber.cpp index 6168f824..0f71e2e3 100644 --- a/libsrc/grabber/v4l2/V4L2Grabber.cpp +++ b/libsrc/grabber/v4l2/V4L2Grabber.cpp @@ -52,6 +52,8 @@ V4L2Grabber::V4L2Grabber(const QString & device , _noSignalCounterThreshold(40) , _noSignalThresholdColor(ColorRgb{0,0,0}) , _signalDetectionEnabled(true) + , _cecDetectionEnabled(true) + , _cecStandbyActivated(false) , _noSignalDetected(false) , _noSignalCounter(0) , _x_frac_min(0.25) @@ -1031,6 +1033,9 @@ bool V4L2Grabber::process_image(const void *p, int size) void V4L2Grabber::process_image(const uint8_t * data, int size) { + if (_cecDetectionEnabled && _cecStandbyActivated) + return; + Image image(_width, _height); /* ---------------------------------------------------------- @@ -1291,6 +1296,15 @@ void V4L2Grabber::setSignalDetectionEnable(bool enable) } } +void V4L2Grabber::setCecDetectionEnable(bool enable) +{ + if (_cecDetectionEnabled != enable) + { + _cecDetectionEnabled = enable; + Info(_log, QString("CEC detection is now %1").arg(enable ? "enabled" : "disabled").toLocal8Bit()); + } +} + void V4L2Grabber::setPixelDecimation(int pixelDecimation) { if (_pixelDecimation != pixelDecimation) @@ -1383,3 +1397,19 @@ QStringList V4L2Grabber::getFramerates(QString devicePath) { return _deviceProperties.value(devicePath).framerates; } + +void V4L2Grabber::handleCecEvent(CECEvent event) +{ + switch (event) + { + case CECEvent::On : + Debug(_log,"CEC on event received"); + _cecStandbyActivated = false; + return; + case CECEvent::Off : + Debug(_log,"CEC off event received"); + _cecStandbyActivated = true; + return; + default: break; + } +} diff --git a/libsrc/grabber/v4l2/V4L2Wrapper.cpp b/libsrc/grabber/v4l2/V4L2Wrapper.cpp index 5f75740f..db0e24b5 100644 --- a/libsrc/grabber/v4l2/V4L2Wrapper.cpp +++ b/libsrc/grabber/v4l2/V4L2Wrapper.cpp @@ -90,7 +90,22 @@ bool V4L2Wrapper::getSignalDetectionEnable() return _grabber.getSignalDetectionEnabled(); } +void V4L2Wrapper::setCecDetectionEnable(bool enable) +{ + _grabber.setCecDetectionEnable(enable); +} + +bool V4L2Wrapper::getCecDetectionEnable() +{ + return _grabber.getCecDetectionEnabled(); +} + void V4L2Wrapper::setDeviceVideoStandard(QString device, VideoStandard videoStandard) { _grabber.setDeviceVideoStandard(device, videoStandard); } + +void V4L2Wrapper::handleCecEvent(CECEvent event) +{ + _grabber.handleCecEvent(event); +} diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index 394576b7..fb7f2a43 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -172,6 +172,9 @@ void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJso // device framerate _ggrabber->setFramerate(obj["fps"].toInt(15)); + // CEC Standby + _ggrabber->setCecDetectionEnable(obj["cecDetection"].toBool(true)); + _ggrabber->setSignalDetectionEnable(obj["signalDetection"].toBool(true)); _ggrabber->setSignalDetectionOffset( obj["sDHOffsetMin"].toDouble(0.25), diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 39a4b7bf..984df307 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -1,4 +1,3 @@ - // STL includes #include #include diff --git a/libsrc/hyperion/schema/schema-grabberV4L2.json b/libsrc/hyperion/schema/schema-grabberV4L2.json index cdc97846..4816fd8b 100644 --- a/libsrc/hyperion/schema/schema-grabberV4L2.json +++ b/libsrc/hyperion/schema/schema-grabberV4L2.json @@ -131,13 +131,21 @@ "required" : true, "propertyOrder" : 15 }, + "cecDetection" : + { + "type" : "boolean", + "title" : "edt_conf_v4l2_cecDetection_title", + "default" : false, + "required" : true, + "propertyOrder" : 16 + }, "signalDetection" : { "type" : "boolean", "title" : "edt_conf_v4l2_signalDetection_title", "default" : false, "required" : true, - "propertyOrder" : 16 + "propertyOrder" : 17 }, "redSignalThreshold" : { @@ -153,7 +161,7 @@ } }, "required" : true, - "propertyOrder" : 17 + "propertyOrder" : 18 }, "greenSignalThreshold" : { @@ -169,7 +177,7 @@ } }, "required" : true, - "propertyOrder" : 18 + "propertyOrder" : 19 }, "blueSignalThreshold" : { @@ -185,7 +193,7 @@ } }, "required" : true, - "propertyOrder" : 19 + "propertyOrder" : 20 }, "sDVOffsetMin" : { @@ -201,7 +209,7 @@ } }, "required" : true, - "propertyOrder" : 20 + "propertyOrder" : 21 }, "sDVOffsetMax" : { @@ -217,7 +225,7 @@ } }, "required" : true, - "propertyOrder" : 21 + "propertyOrder" : 22 }, "sDHOffsetMin" : { @@ -233,7 +241,7 @@ } }, "required" : true, - "propertyOrder" : 22 + "propertyOrder" : 23 }, "sDHOffsetMax" : { @@ -249,7 +257,7 @@ } }, "required" : true, - "propertyOrder" : 23 + "propertyOrder" : 24 } }, "additionalProperties" : true diff --git a/libsrc/utils/DefaultSignalHandler.cpp b/libsrc/utils/DefaultSignalHandler.cpp index 2b5c6324..0800bf6b 100644 --- a/libsrc/utils/DefaultSignalHandler.cpp +++ b/libsrc/utils/DefaultSignalHandler.cpp @@ -90,7 +90,10 @@ void print_trace() Logger* log = Logger::getInstance("CORE"); char ** symbols = backtrace_symbols(addresses, size); - for (int i = 0; i < size; ++i) + + /* Skip first 2 frames as they are signal + * handler and print_trace functions. */ + for (int i = 2; i < size; ++i) { std::string line = "\t" + decipher_trace(symbols[i]); Error(log, line.c_str()); diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index e68995f7..9a417d8e 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -19,18 +19,18 @@ add_executable(hyperiond ) target_link_libraries(hyperiond - commandline - hyperion - effectengine - jsonserver - flatbufserver - protoserver - webserver - ssdp - database - python - resources - Qt5::Widgets + commandline + hyperion + effectengine + jsonserver + flatbufserver + protoserver + webserver + ssdp + database + python + resources + Qt5::Widgets ) if (NOT CMAKE_VERSION VERSION_LESS "3.12") @@ -88,6 +88,10 @@ if (ENABLE_QT) target_link_libraries(hyperiond qt-grabber) endif () +if (ENABLE_CEC) + target_link_libraries(hyperiond cechandler) +endif () + if(NOT WIN32) install ( TARGETS hyperiond DESTINATION "share/hyperion/bin" COMPONENT "Hyperion" ) install ( DIRECTORY ${CMAKE_SOURCE_DIR}/bin/service DESTINATION "share/hyperion/" COMPONENT "Hyperion" ) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 770e1804..ef0c91ec 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -54,6 +54,10 @@ // EffectFileHandler #include +#ifdef ENABLE_CEC +#include +#endif + HyperionDaemon *HyperionDaemon::daemon = nullptr; HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bool &logLvlOverwrite) @@ -76,6 +80,7 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bo , _osxGrabber(nullptr) , _qtGrabber(nullptr) , _ssdp(nullptr) + , _cecHandler(nullptr) , _currVideoMode(VideoMode::VIDEO_2D) { HyperionDaemon::daemon = this; @@ -95,6 +100,8 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bo if (!logLvlOverwrite) handleSettingsUpdate(settings::LOGGER, getSetting(settings::LOGGER)); + createCecHandler(); + // init EffectFileHandler EffectFileHandler *efh = new EffectFileHandler(rootPath, getSetting(settings::EFFECTS), this); connect(this, &HyperionDaemon::settingsChanged, efh, &EffectFileHandler::handleSettingsUpdate); @@ -194,12 +201,17 @@ void HyperionDaemon::freeObjects() delete _sslWebserver->thread(); delete _sslWebserver; +#ifdef ENABLE_CEC + _cecHandler->thread()->quit(); + _cecHandler->thread()->wait(1000); + delete _cecHandler->thread(); + delete _cecHandler; +#endif + // stop Hyperions (non blocking) _instanceManager->stopAll(); -#ifdef ENABLE_AVAHI delete _bonjourBrowserWrapper; -#endif delete _amlGrabber; delete _dispmanx; delete _fbGrabber; @@ -208,9 +220,8 @@ void HyperionDaemon::freeObjects() delete _v4l2Grabber; _v4l2Grabber = nullptr; -#ifdef ENABLE_AVAHI + _cecHandler = nullptr; _bonjourBrowserWrapper = nullptr; -#endif _amlGrabber = nullptr; _dispmanx = nullptr; _fbGrabber = nullptr; @@ -463,13 +474,23 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type &settingsType, co } else if (settingsType == settings::V4L2) { + const QJsonObject &grabberConfig = config.object(); +#ifdef ENABLE_CEC + QString operation; + if (_cecHandler && grabberConfig["cecDetection"].toBool(false)) + { + QMetaObject::invokeMethod(_cecHandler, "start", Qt::QueuedConnection); + } + else + { + QMetaObject::invokeMethod(_cecHandler, "stop", Qt::QueuedConnection); + } +#endif -#ifdef ENABLE_V4L2 if (_v4l2Grabber != nullptr) return; - const QJsonObject &grabberConfig = config.object(); - +#ifdef ENABLE_V4L2 _v4l2Grabber = new V4L2Wrapper( grabberConfig["device"].toString("auto"), grabberConfig["width"].toInt(0), @@ -489,6 +510,8 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type &settingsType, co grabberConfig["cropRight"].toInt(0), grabberConfig["cropTop"].toInt(0), grabberConfig["cropBottom"].toInt(0)); + + _v4l2Grabber->setCecDetectionEnable(grabberConfig["cecDetection"].toBool(true)); _v4l2Grabber->setSignalDetectionEnable(grabberConfig["signalDetection"].toBool(true)); _v4l2Grabber->setSignalDetectionOffset( grabberConfig["sDHOffsetMin"].toDouble(0.25), @@ -497,9 +520,9 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type &settingsType, co grabberConfig["sDVOffsetMax"].toDouble(0.75)); Debug(_log, "V4L2 grabber created"); - // connect to HyperionDaemon signal - connect(this, &HyperionDaemon::videoMode, _v4l2Grabber, &V4L2Wrapper::setVideoMode); - connect(this, &HyperionDaemon::settingsChanged, _v4l2Grabber, &V4L2Wrapper::handleSettingsUpdate); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _v4l2Grabber, &V4L2Wrapper::setVideoMode); + connect(this, &HyperionDaemon::settingsChanged, _v4l2Grabber, &V4L2Wrapper::handleSettingsUpdate); #else Error(_log, "The v4l2 grabber can not be instantiated, because it has been left out from the build"); #endif @@ -611,3 +634,24 @@ void HyperionDaemon::createGrabberOsx(const QJsonObject &grabberConfig) Error(_log, "The osx grabber can not be instantiated, because it has been left out from the build"); #endif } + +void HyperionDaemon::createCecHandler() +{ +#ifdef ENABLE_CEC + _cecHandler = new CECHandler; + + QThread * thread = new QThread(this); + thread->setObjectName("CECThread"); + _cecHandler->moveToThread(thread); + thread->start(); + + connect(_cecHandler, &CECHandler::cecEvent, [&] (CECEvent event) { + if (_v4l2Grabber) + _v4l2Grabber->handleCecEvent(event); + }); + + Info(_log, "CEC handler created"); +#else + Error(_log, "The CEC handler can not be instantiated, because it has been left out from the build"); +#endif +} diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h index a091933e..c768487a 100644 --- a/src/hyperiond/hyperiond.h +++ b/src/hyperiond/hyperiond.h @@ -65,6 +65,7 @@ class FlatBufferServer; class ProtoServer; class AuthManager; class NetOrigin; +class CECHandler; class HyperionDaemon : public QObject { @@ -144,6 +145,7 @@ private: void createGrabberOsx(const QJsonObject & grabberConfig); void createGrabberX11(const QJsonObject & grabberConfig); void createGrabberQt(const QJsonObject & grabberConfig); + void createCecHandler(); Logger* _log; HyperionIManager* _instanceManager; @@ -162,6 +164,7 @@ private: OsxWrapper* _osxGrabber; QtWrapper* _qtGrabber; SSDPHandler* _ssdp; + CECHandler* _cecHandler; FlatBufferServer* _flatBufferServer; ProtoServer* _protoServer;