mDNS Support (#1452)

* Allow build, if no grabbers are enabled

* Align available functions to right Qt version

* Update to next development version

* Align available functions to right Qt version

* fix workflows (apt/nightly)

* Disable QNetworkConfigurationManager deprecation warnings

* Initial go on Smart Pointers

* Add Deallocation

* Correct QT_WARNING_DISABLE_DEPRECATED (available since 5.9)

* Cluster Build Variables

* Hyperion Light

* Address build warnings

* Hyperion Light - UI

* Update Protobuf to latest master

* Removed compiler warnings

* Added restart ability to systray

* Correct Protobuf

* Ignore 'no-return' warning on protobuf build

* hyperion-remote: Fix auto discovery of hyperion server

* Fix Qt version override

* Update changelog

* Remove Grabber Components, if no Grabber exists

* Standalone Grabber - Fix fps default

* Remote Control - Have Source Selction accrosswhole screen

* Enable Blackborder detection only, if relevant input sources available

* Enable Blackborder detection only, if relevant input sources available

* Remote UI - rearrange containers

* Checkout

* Fix compilation on windows

* Re-added qmdnsengine template cmake

* chrono added for linux

* Removed existing AVAHI/Bonjour, allow to enable/disable mDNS

* hyperiond macos typo fix

* Fix macOS Bundle build

* Fix macOS bundle info details

* Correct CMake files

* Removed existing AVAHI/Bonjour (2)

* Share hyperion's services via mDNS

* Add mDNS Browser and mDNS for LED-Devices

* Support mDNS discovery for standalone grabbers

* Remove ZLib Dependency & Cleanup

* mDNS - hanle 2.local2 an ".local." domains equally

* Hue - Link discovery to bridge class, workaround port 443 for mDNS discovery

* Fix save button state when switching between devices

* Removed sessions (of other hyperions)

* mDNS Publisher - Simplify service naming

* mDNS refactoring & Forwarder discovery

* mDNS Updates to use device service name

* Consistency of standalone grabbers with mDNS Service Registry

* Merge branch 'hyperion-project:master' into mDNS

* Start JSON and WebServers only after Instance 0 is available

* Remove bespoke qDebug Output again

* MDNS updates and refactor Forwarder

* Minor updates

* Upgrade to CMake 3.1

* typo

* macOS fix

* Correct merge

* - Remove dynamic linker flag from standalone dispmanX Grabber
- Added ability to use system qmdns libs

* Cec handler library will load at runtime

* typo fix

* protobuf changes

* mDNS changes for Windows/macOS

* test window build qmdnsengine

* absolute path to protobuf cmake dir

* Rework Hue Wizard supporting mDNS

* LED-Devices - Retry support + Refactoring (excl. Hue)

* LED-Devices - Refactoring/Retry support Hue + additional alignments

* Address LGTM findings

* Fix CI-Build, revert test changes

* Build Windows in Release mode to avoid python problem

* Correct that WebServerObject is available earlier

* Ensure that instance name in logs for one instance are presented

* Update content LEDs

* Rework mDNS Address lookup

* Fix LED UI

* Fix for non mDNS Services (ignore default port)

* Disbale device when now input is available

* Revert back some updates, ensure last color is updated when switched on

* Handle reopening case and changed IP, port for API-calls

* Add UPD-DDP Device

* WLED support for DDP

* Fix printout

* LEDDevice - Allow more retries, udapte defaults

* LED-Net Devices - Select Custom device, if configured

Co-authored-by: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com>
Co-authored-by: Paulchen Panther <Paulchen-Panter@protonmail.com>
This commit is contained in:
LordGrey
2022-05-01 19:42:47 +02:00
committed by GitHub
parent 3ef4ebc1a4
commit e9936e131b
148 changed files with 5885 additions and 4459 deletions

View File

@@ -32,9 +32,9 @@ add_subdirectory(db)
add_subdirectory(api)
add_subdirectory(ssdp)
if(ENABLE_AVAHI)
add_subdirectory(bonjour)
endif()
if(ENABLE_MDNS)
add_subdirectory(mdns)
endif()
if(ENABLE_EFFECTENGINE)
add_subdirectory(effectengine)

View File

@@ -14,6 +14,7 @@
#include <QBuffer>
#include <QByteArray>
#include <QTimer>
#include <QThread>
// hyperion includes
#include <utils/jsonschema/QJsonFactory.h>
@@ -23,9 +24,6 @@
#include <utils/ColorSys.h>
#include <utils/Process.h>
// bonjour wrapper
#include <bonjour/bonjourbrowserwrapper.h>
// ledmapping int <> string transform methods
#include <hyperion/ImageProcessor.h>
@@ -44,17 +42,13 @@ API::API(Logger *log, bool localConnection, QObject *parent)
// Init
_log = log;
_authManager = AuthManager::getInstance();
_instanceManager = HyperionIManager::getInstance();
_instanceManager = HyperionIManager::getInstance();
_localConnection = localConnection;
_authorized = false;
_adminAuthorized = false;
_hyperion = _instanceManager->getHyperionInstance(0);
_currInstanceIndex = 0;
// TODO FIXME
// report back current registers when a Hyperion instance request it
//connect(ApiSync::getInstance(), &ApiSync::requestActiveRegister, this, &API::requestActiveRegister, Qt::QueuedConnection);
// connect to possible token responses that has been requested
connect(_authManager, &AuthManager::tokenResponse, [=] (bool success, QObject *caller, const QString &token, const QString &comment, const QString &id, const int &tan)
@@ -73,7 +67,7 @@ API::API(Logger *log, bool localConnection, QObject *parent)
void API::init()
{
assert(_hyperion);
_hyperion = _instanceManager->getHyperionInstance(0);
bool apiAuthRequired = _authManager->isAuthRequired();
@@ -336,13 +330,6 @@ void API::stopInstance(quint8 index)
QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
}
void API::requestActiveRegister(QObject *callerInstance)
{
// TODO FIXME
//if (_activeRegisters.size())
// QMetaObject::invokeMethod(ApiSync::getInstance(), "answerActiveRegister", Qt::QueuedConnection, Q_ARG(QObject *, callerInstance), Q_ARG(MapRegister, _activeRegisters));
}
bool API::deleteInstance(quint8 index, QString &replyMsg)
{
if (_adminAuthorized)

View File

@@ -13,7 +13,7 @@
"subcommand": {
"type" : "string",
"required" : true,
"enum" : ["discover","getProperties","identify"]
"enum": [ "discover", "getProperties", "identify", "addAuthorization" ]
},
"ledDeviceType": {
"type" : "string",

View File

@@ -0,0 +1,28 @@
{
"type":"object",
"required":true,
"properties":{
"command": {
"type" : "string",
"required" : true,
"enum" : ["service"]
},
"tan" : {
"type" : "integer"
},
"subcommand": {
"type" : "string",
"required" : true,
"enum" : ["discover"]
},
"serviceType": {
"type" : "string",
"required" : true
},
"params": {
"type" : "object",
"required" : false
}
},
"additionalProperties": false
}

View File

@@ -5,7 +5,7 @@
"command": {
"type" : "string",
"required" : true,
"enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "leddevice", "inputsource", "transform", "correction", "temperature" ]
"enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "leddevice", "inputsource", "service", "transform", "correction", "temperature" ]
}
}
}

View File

@@ -22,6 +22,7 @@
<file alias="schema-instance">JSONRPC_schema/schema-instance.json</file>
<file alias="schema-leddevice">JSONRPC_schema/schema-leddevice.json</file>
<file alias="schema-inputsource">JSONRPC_schema/schema-inputsource.json</file>
<file alias="schema-service">JSONRPC_schema/schema-service.json</file>
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>

View File

@@ -63,11 +63,6 @@
#include <utils/Process.h>
#include <utils/JsonUtils.h>
// bonjour wrapper
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourbrowserwrapper.h>
#endif
// ledmapping int <> string transform methods
#include <hyperion/ImageProcessor.h>
@@ -77,6 +72,15 @@
// auth manager
#include <hyperion/AuthManager.h>
#ifdef ENABLE_MDNS
// mDNS discover
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#else
// ssdp discover
#include <ssdp/SSDPDiscover.h>
#endif
using namespace hyperion;
// Constants
@@ -98,8 +102,6 @@ void JsonAPI::initialize()
{
// init API, REQUIRED!
API::init();
// Initialise jsonCB with current instance
_jsonCB->setSubscriptionsTo(_hyperion);
// setup auth interface
connect(this, &API::onPendingTokenRequest, this, &JsonAPI::newPendingTokenRequest);
@@ -112,7 +114,12 @@ void JsonAPI::initialize()
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
// notify hyperion about a jsonMessageForward
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
if (_hyperion != nullptr)
{
// Initialise jsonCB with current instance
_jsonCB->setSubscriptionsTo(_hyperion);
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
}
}
bool JsonAPI::handleInstanceSwitch(quint8 inst, bool forced)
@@ -180,6 +187,12 @@ void JsonAPI::handleMessage(const QString &messageString, const QString &httpAut
return;
}
proceed:
if (_hyperion == nullptr)
{
sendErrorReply("Service Unavailable", command, tan);
return;
}
// switch over all possible commands and handle them
if (command == "color")
handleColorCommand(message, command, tan);
@@ -221,6 +234,8 @@ proceed:
handleLedDeviceCommand(message, command, tan);
else if (command == "inputsource")
handleInputSourceCommand(message, command, tan);
else if (command == "service")
handleServiceCommand(message, command, tan);
// BEGIN | The following commands are deprecated but used to ensure backward compatibility with hyperion Classic remote control
else if (command == "clearall")
@@ -627,6 +642,11 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
services.append("protobuffer");
#endif
#if defined(ENABLE_MDNS)
services.append("mDNS");
#endif
services.append("SSDP");
if (!availableScreenGrabbers.isEmpty() || !availableVideoGrabbers.isEmpty() || services.contains("flatbuffer") || services.contains("protobuffer"))
{
services.append("borderdetection");
@@ -649,24 +669,6 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
info["components"] = component;
info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType());
// add sessions
QJsonArray sessions;
#ifdef ENABLE_AVAHI
for (auto session: BonjourBrowserWrapper::getInstance()->getAllServices())
{
if (session.port < 0)
continue;
QJsonObject item;
item["name"] = session.serviceName;
item["type"] = session.registeredType;
item["domain"] = session.replyDomain;
item["host"] = session.hostName;
item["address"] = session.address;
item["port"] = session.port;
sessions.append(item);
}
info["sessions"] = sessions;
#endif
// add instance info
QJsonArray instanceInfo;
for (const auto &entry : API::getAllInstanceData())
@@ -1571,6 +1573,16 @@ void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString &
sendSuccessReply(full_command, tan);
}
else if (subc == "addAuthorization")
{
ledDevice = LedDeviceFactory::construct(config);
const QJsonObject& params = message["params"].toObject();
const QJsonObject response = ledDevice->addAuthorization(params);
Debug(_log, "response: [%s]", QString(QJsonDocument(response).toJson(QJsonDocument::Compact)).toUtf8().constData());
sendSuccessDataReply(QJsonDocument(response), full_command, tan);
}
else
{
sendErrorReply("Unknown or missing subcommand", full_command, tan);
@@ -1725,6 +1737,55 @@ void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const QString
}
}
void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &command, int tan)
{
DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData());
const QString &subc = message["subcommand"].toString().trimmed();
const QString type = message["serviceType"].toString().trimmed();
QString full_command = command + "-" + subc;
if (subc == "discover")
{
QByteArray serviceType;
QJsonObject servicesDiscovered;
QJsonObject servicesOfType;
QJsonArray serviceList;
#ifdef ENABLE_MDNS
QString discoveryMethod("mDNS");
serviceType = MdnsServiceRegister::getServiceType(type);
#else
QString discoveryMethod("ssdp");
#endif
if (!serviceType.isEmpty())
{
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, serviceType));
serviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(serviceType, MdnsServiceRegister::getServiceNameFilter(type), DEFAULT_DISCOVER_TIMEOUT);
#endif
servicesOfType.insert(type, serviceList);
servicesDiscovered.insert("discoveryMethod", discoveryMethod);
servicesDiscovered.insert("services", servicesOfType);
sendSuccessDataReply(QJsonDocument(servicesDiscovered), full_command, tan);
}
else
{
sendErrorReply(QString("Discovery of service type [%1] via %2 not supported").arg(type, discoveryMethod), full_command, tan);
}
}
else
{
sendErrorReply("Unknown or missing subcommand", full_command, tan);
}
}
void JsonAPI::handleNotImplemented(const QString &command, int tan)
{
sendErrorReply("Command not implemented", command, tan);

View File

@@ -9,10 +9,6 @@
// components
#include <hyperion/ComponentRegister.h>
// bonjour wrapper
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourbrowserwrapper.h>
#endif
// priorityMuxer
#include <hyperion/PriorityMuxer.h>
@@ -33,12 +29,9 @@ JsonCB::JsonCB(QObject* parent)
: QObject(parent)
, _hyperion(nullptr)
, _componentRegister(nullptr)
#ifdef ENABLE_AVAHI
, _bonjour(BonjourBrowserWrapper::getInstance())
#endif
, _prioMuxer(nullptr)
{
_availableCommands << "components-update" << "sessions-update" << "priorities-update" << "imageToLedMapping-update"
_availableCommands << "components-update" << "priorities-update" << "imageToLedMapping-update"
<< "adjustment-update" << "videomode-update" << "settings-update" << "leds-update" << "instance-update" << "token-update";
#if defined(ENABLE_EFFECTENGINE)
@@ -66,16 +59,6 @@ bool JsonCB::subscribeFor(const QString& type, bool unsubscribe)
connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState, Qt::UniqueConnection);
}
if(type == "sessions-update")
{
#ifdef ENABLE_AVAHI
if(unsubscribe)
disconnect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange);
else
connect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange, Qt::UniqueConnection);
#endif
}
if(type == "priorities-update")
{
if (unsubscribe)
@@ -208,26 +191,6 @@ void JsonCB::handleComponentState(hyperion::Components comp, bool state)
doCallback("components-update", QVariant(data));
}
#ifdef ENABLE_AVAHI
void JsonCB::handleBonjourChange(const QMap<QString,BonjourRecord>& bRegisters)
{
QJsonArray data;
for (const auto & session: bRegisters)
{
if (session.port<0) continue;
QJsonObject item;
item["name"] = session.serviceName;
item["type"] = session.registeredType;
item["domain"] = session.replyDomain;
item["host"] = session.hostName;
item["address"]= session.address;
item["port"] = session.port;
data.append(item);
}
doCallback("sessions-update", QVariant(data));
}
#endif
void JsonCB::handlePriorityUpdate(int currentPriority, const PriorityMuxer::InputsMap& activeInputs)
{

View File

@@ -1,33 +0,0 @@
# Define the current source locations
set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/bonjour)
set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/bonjour)
FILE ( GLOB Bonjour_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
add_library(bonjour ${Bonjour_SOURCES} )
target_link_libraries(bonjour
hyperion
hyperion-utils
Qt${QT_VERSION_MAJOR}::Network
)
IF (NOT APPLE)
set(USE_SHARED_AVAHI_LIBS ${DEFAULT_USE_SHARED_AVAHI_LIBS} CACHE BOOL "use avahi libraries from system")
if (USE_SHARED_AVAHI_LIBS)
target_link_libraries(bonjour
dns_sd
avahi-client
avahi-common
avahi-core)
else()
target_link_libraries(bonjour
libdns_sd.a
libavahi-client.a
libavahi-common.a
libavahi-core.a)
endif()
target_link_libraries(bonjour dbus-1)
ENDIF()

View File

@@ -1,84 +0,0 @@
#include <bonjour/bonjourbrowserwrapper.h>
//qt incl
#include <QTimer>
// bonjour
#include <bonjour/bonjourservicebrowser.h>
#include <bonjour/bonjourserviceresolver.h>
BonjourBrowserWrapper* BonjourBrowserWrapper::instance = nullptr;
BonjourBrowserWrapper::BonjourBrowserWrapper(QObject * parent)
: QObject(parent)
, _bonjourResolver(new BonjourServiceResolver(this))
, _timerBonjourResolver(new QTimer(this))
{
// register meta
qRegisterMetaType<QMap<QString,BonjourRecord>>("QMap<QString,BonjourRecord>");
BonjourBrowserWrapper::instance = this;
connect(_bonjourResolver, &BonjourServiceResolver::bonjourRecordResolved, this, &BonjourBrowserWrapper::bonjourRecordResolved);
connect(_timerBonjourResolver, &QTimer::timeout, this, &BonjourBrowserWrapper::bonjourResolve);
_timerBonjourResolver->setInterval(1000);
_timerBonjourResolver->start();
// browse for _hyperiond-http._tcp
browseForServiceType(QLatin1String("_hyperiond-http._tcp"));
}
bool BonjourBrowserWrapper::browseForServiceType(const QString &serviceType)
{
if(!_browsedServices.contains(serviceType))
{
BonjourServiceBrowser* newBrowser = new BonjourServiceBrowser(this);
connect(newBrowser, &BonjourServiceBrowser::currentBonjourRecordsChanged, this, &BonjourBrowserWrapper::currentBonjourRecordsChanged);
newBrowser->browseForServiceType(serviceType);
_browsedServices.insert(serviceType, newBrowser);
return true;
}
return false;
}
void BonjourBrowserWrapper::currentBonjourRecordsChanged(const QList<BonjourRecord> &list)
{
_hyperionSessions.clear();
for ( auto rec : list )
{
_hyperionSessions.insert(rec.serviceName, rec);
}
}
void BonjourBrowserWrapper::bonjourRecordResolved(const QHostInfo &hostInfo, int port)
{
if ( _hyperionSessions.contains(_bonjourCurrentServiceToResolve))
{
QString host = hostInfo.hostName();
QString domain = _hyperionSessions[_bonjourCurrentServiceToResolve].replyDomain;
if (host.endsWith("."+domain))
{
host.remove(host.length()-domain.length()-1,domain.length()+1);
}
_hyperionSessions[_bonjourCurrentServiceToResolve].hostName = host;
_hyperionSessions[_bonjourCurrentServiceToResolve].port = port;
_hyperionSessions[_bonjourCurrentServiceToResolve].address = hostInfo.addresses().isEmpty() ? "" : hostInfo.addresses().first().toString();
//Debug(_log, "found hyperion session: %s:%d",QSTRING_CSTR(hostInfo.hostName()), port);
//emit change
emit browserChange(_hyperionSessions);
}
}
void BonjourBrowserWrapper::bonjourResolve()
{
for(auto key : _hyperionSessions.keys())
{
if (_hyperionSessions[key].port < 0)
{
_bonjourCurrentServiceToResolve = key;
_bonjourResolver->resolveBonjourRecord(_hyperionSessions[key]);
break;
}
}
}

View File

@@ -1,109 +0,0 @@
/*
Copyright (c) 2007, Trenton Schulz
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "bonjour/bonjourservicebrowser.h"
#include <QtCore/QSocketNotifier>
BonjourServiceBrowser::BonjourServiceBrowser(QObject *parent)
: QObject(parent)
, dnssref(0)
, bonjourSocket(0)
{
}
BonjourServiceBrowser::~BonjourServiceBrowser()
{
if (dnssref)
{
DNSServiceRefDeallocate(dnssref);
dnssref = 0;
}
}
void BonjourServiceBrowser::browseForServiceType(const QString &serviceType)
{
DNSServiceErrorType err = DNSServiceBrowse(&dnssref, 0, 0, serviceType.toUtf8().constData(), 0, bonjourBrowseReply, this);
if (err != kDNSServiceErr_NoError)
{
emit error(err);
}
else
{
int sockfd = DNSServiceRefSockFD(dnssref);
if (sockfd == -1)
{
emit error(kDNSServiceErr_Invalid);
}
else
{
bonjourSocket = new QSocketNotifier(sockfd, QSocketNotifier::Read, this);
connect(bonjourSocket, &QSocketNotifier::activated, this, &BonjourServiceBrowser::bonjourSocketReadyRead);
}
}
}
void BonjourServiceBrowser::bonjourSocketReadyRead()
{
DNSServiceErrorType err = DNSServiceProcessResult(dnssref);
if (err != kDNSServiceErr_NoError)
{
emit error(err);
}
}
void BonjourServiceBrowser::bonjourBrowseReply(DNSServiceRef , DNSServiceFlags flags,
quint32 , DNSServiceErrorType errorCode,
const char *serviceName, const char *regType,
const char *replyDomain, void *context)
{
BonjourServiceBrowser *serviceBrowser = static_cast<BonjourServiceBrowser *>(context);
if (errorCode != kDNSServiceErr_NoError)
{
emit serviceBrowser->error(errorCode);
}
else
{
BonjourRecord bonjourRecord(serviceName, regType, replyDomain);
if ((flags & kDNSServiceFlagsAdd) != 0)
{
if (!serviceBrowser->bonjourRecords.contains(bonjourRecord))
{
serviceBrowser->bonjourRecords.append(bonjourRecord);
}
}
else
{
serviceBrowser->bonjourRecords.removeAll(bonjourRecord);
}
if (!(flags & kDNSServiceFlagsMoreComing))
{
emit serviceBrowser->currentBonjourRecordsChanged(serviceBrowser->bonjourRecords);
}
}
}

View File

@@ -1,151 +0,0 @@
/*
Copyright (c) 2007, Trenton Schulz
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <bonjour/bonjourserviceregister.h>
#include <stdlib.h>
#include <QtCore/QSocketNotifier>
#include <QHostInfo>
#include <utils/Logger.h>
#include <HyperionConfig.h>
#include <hyperion/AuthManager.h>
BonjourServiceRegister::BonjourServiceRegister(QObject *parent)
: QObject(parent), dnssref(0), bonjourSocket(0)
{
setenv("AVAHI_COMPAT_NOWARN", "1", 1);
}
BonjourServiceRegister::~BonjourServiceRegister()
{
if (dnssref)
{
DNSServiceRefDeallocate(dnssref);
dnssref = 0;
}
}
void BonjourServiceRegister::registerService(const QString& service, int port)
{
_port = port;
// zeroconf $configname@$hostname:port
// TODO add name of the main instance
registerService(
BonjourRecord(QHostInfo::localHostName()+ ":" + QString::number(port),
service,
QString()
),
port
);
}
void BonjourServiceRegister::registerService(const BonjourRecord &record, quint16 servicePort, const std::vector<std::pair<std::string, std::string>>& txt)
{
if (dnssref)
{
Warning(Logger::getInstance("BonJour"), "Already registered a service for this object, aborting new register");
return;
}
quint16 bigEndianPort = servicePort;
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
{
bigEndianPort = 0 | ((servicePort & 0x00ff) << 8) | ((servicePort & 0xff00) >> 8);
}
#endif
// base txtRec
std::vector<std::pair<std::string, std::string> > txtBase = {{"id",AuthManager::getInstance()->getID().toStdString()},{"version",HYPERION_VERSION}};
// create txt record
TXTRecordRef txtRec;
TXTRecordCreate(&txtRec,0,NULL);
if(!txt.empty())
{
txtBase.insert(txtBase.end(), txt.begin(), txt.end());
}
// add txt records
for(std::vector<std::pair<std::string, std::string> >::const_iterator it = txtBase.begin(); it != txtBase.end(); ++it)
{
//Debug(Logger::getInstance("BonJour"), "TXTRecord: key:%s, value:%s",it->first.c_str(),it->second.c_str());
uint8_t txtLen = (uint8_t)strlen(it->second.c_str());
TXTRecordSetValue(&txtRec, it->first.c_str(), txtLen, it->second.c_str());
}
DNSServiceErrorType err = DNSServiceRegister(&dnssref, 0, 0, record.serviceName.toUtf8().constData(),
record.registeredType.toUtf8().constData(),
(record.replyDomain.isEmpty() ? 0 : record.replyDomain.toUtf8().constData()),
0, bigEndianPort, TXTRecordGetLength(&txtRec), TXTRecordGetBytesPtr(&txtRec), bonjourRegisterService, this);
if (err != kDNSServiceErr_NoError)
{
emit error(err);
}
else
{
int sockfd = DNSServiceRefSockFD(dnssref);
if (sockfd == -1)
{
emit error(kDNSServiceErr_Invalid);
}
else
{
bonjourSocket = new QSocketNotifier(sockfd, QSocketNotifier::Read, this);
connect(bonjourSocket, &QSocketNotifier::activated, this, &BonjourServiceRegister::bonjourSocketReadyRead);
}
}
TXTRecordDeallocate(&txtRec);
}
void BonjourServiceRegister::bonjourSocketReadyRead()
{
DNSServiceErrorType err = DNSServiceProcessResult(dnssref);
if (err != kDNSServiceErr_NoError)
emit error(err);
}
void BonjourServiceRegister::bonjourRegisterService(DNSServiceRef, DNSServiceFlags,
DNSServiceErrorType errorCode, const char *name,
const char *regtype, const char *domain,
void *data)
{
BonjourServiceRegister *serviceRegister = static_cast<BonjourServiceRegister *>(data);
if (errorCode != kDNSServiceErr_NoError)
{
emit serviceRegister->error(errorCode);
}
else
{
serviceRegister->finalRecord = BonjourRecord(QString::fromUtf8(name),
QString::fromUtf8(regtype),
QString::fromUtf8(domain));
emit serviceRegister->serviceRegistered(serviceRegister->finalRecord);
}
}

View File

@@ -1,122 +0,0 @@
/*
Copyright (c) 2007, Trenton Schulz
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <QtCore/QSocketNotifier>
#include <QtNetwork/QHostInfo>
#include "bonjour/bonjourrecord.h"
#include "bonjour/bonjourserviceresolver.h"
BonjourServiceResolver::BonjourServiceResolver(QObject *parent)
: QObject(parent)
, dnssref(0)
, bonjourSocket(0)
, bonjourPort(-1)
{
}
BonjourServiceResolver::~BonjourServiceResolver()
{
cleanupResolve();
}
void BonjourServiceResolver::cleanupResolve()
{
if (dnssref)
{
DNSServiceRefDeallocate(dnssref);
dnssref = 0;
delete bonjourSocket;
bonjourPort = -1;
}
}
bool BonjourServiceResolver::resolveBonjourRecord(const BonjourRecord &record)
{
if (dnssref)
{
//qWarning("resolve in process, aborting");
return false;
}
DNSServiceErrorType err = DNSServiceResolve(&dnssref, 0, 0,
record.serviceName.toUtf8().constData(),
record.registeredType.toUtf8().constData(),
record.replyDomain.toUtf8().constData(),
(DNSServiceResolveReply)bonjourResolveReply, this);
if (err != kDNSServiceErr_NoError)
{
emit error(err);
}
else
{
int sockfd = DNSServiceRefSockFD(dnssref);
if (sockfd == -1)
{
emit error(kDNSServiceErr_Invalid);
}
else
{
bonjourSocket = new QSocketNotifier(sockfd, QSocketNotifier::Read, this);
connect(bonjourSocket, &QSocketNotifier::activated, this, &BonjourServiceResolver::bonjourSocketReadyRead);
}
}
return true;
}
void BonjourServiceResolver::bonjourSocketReadyRead()
{
DNSServiceErrorType err = DNSServiceProcessResult(dnssref);
if (err != kDNSServiceErr_NoError)
emit error(err);
}
void BonjourServiceResolver::bonjourResolveReply(DNSServiceRef sdRef, DNSServiceFlags ,
quint32 , DNSServiceErrorType errorCode,
const char *, const char *hosttarget, quint16 port,
quint16 , const char *, void *context)
{
BonjourServiceResolver *serviceResolver = static_cast<BonjourServiceResolver *>(context);
if (errorCode != kDNSServiceErr_NoError) {
emit serviceResolver->error(errorCode);
return;
}
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
{
port = 0 | ((port & 0x00ff) << 8) | ((port & 0xff00) >> 8);
}
#endif
serviceResolver->bonjourPort = port;
QHostInfo::lookupHost(QString::fromUtf8(hosttarget), serviceResolver, SLOT(finishConnect(const QHostInfo &)));
}
void BonjourServiceResolver::finishConnect(const QHostInfo &hostInfo)
{
emit bonjourRecordResolved(hostInfo, bonjourPort);
QMetaObject::invokeMethod(this, "cleanupResolve", Qt::QueuedConnection);
}

View File

@@ -8,6 +8,8 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <QFile>
/* Enable to turn on detailed CEC logs */
// #define VERBOSE_CEC
@@ -34,15 +36,16 @@ bool CECHandler::start()
if (_cecAdapter)
return true;
Info(_logger, "Starting CEC handler");
_cecAdapter = LibCecInitialise(&_cecConfig);
std::string library = std::string("" CEC_LIBRARY);
_cecAdapter = LibCecInitialise(&_cecConfig, QFile::exists(QString::fromStdString(library)) ? library.c_str() : nullptr);
if(!_cecAdapter)
{
Error(_logger, "Failed loading libcec.so");
Error(_logger, "Failed to loading libcec.so");
return false;
}
Info(_logger, "CEC handler started");
auto adapters = getAdapters();
if (adapters.isEmpty())
{

View File

@@ -5,13 +5,14 @@ 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})
add_definitions(-DCEC_LIBRARY="${CEC_LIBRARIES}")
include_directories(${CEC_INCLUDE_DIRS})
target_link_libraries(cechandler
dl
${CEC_LIBRARIES}
Qt${QT_VERSION_MAJOR}::Core
${CMAKE_DL_LIBS}
)

View File

@@ -60,5 +60,10 @@ flatbuffers
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Core
)
if(ENABLE_MDNS)
target_link_libraries(flatbufserver mdns)
endif()
endif()

View File

@@ -37,6 +37,7 @@ FlatBufferConnection::FlatBufferConnection(const QString& origin, const QString&
FlatBufferConnection::~FlatBufferConnection()
{
Debug(_log, "Closing connection to: %s:%u", QSTRING_CSTR(_host), _port);
_timer.stop();
_socket.close();
}

View File

@@ -6,16 +6,18 @@
#include <utils/NetOrigin.h>
#include <utils/GlobalSignals.h>
// bonjour
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourserviceregister.h>
#endif
// qt
#include <QJsonObject>
#include <QTcpServer>
#include <QTcpSocket>
// Constants
namespace {
const char SERVICE_TYPE[] = "flatbuffer";
} //End of constants
FlatBufferServer::FlatBufferServer(const QJsonDocument& config, QObject* parent)
: QObject(parent)
, _server(new QTcpServer(this))
@@ -106,19 +108,8 @@ void FlatBufferServer::startServer()
else
{
Info(_log,"Started on port %d", _port);
#ifdef ENABLE_AVAHI
if(_serviceRegister == nullptr)
{
_serviceRegister = new BonjourServiceRegister(this);
_serviceRegister->registerService("_hyperiond-flatbuf._tcp", _port);
}
else if(_serviceRegister->getPort() != _port)
{
delete _serviceRegister;
_serviceRegister = new BonjourServiceRegister(this);
_serviceRegister->registerService("_hyperiond-flatbuf._tcp", _port);
}
#endif
emit publishService(SERVICE_TYPE, _port);
}
}
}

View File

@@ -1,5 +1,5 @@
// STL includes
#include <stdexcept>
#include <chrono>
// project includes
#include <forwarder/MessageForwarder.h>
@@ -12,23 +12,46 @@
#include <utils/NetUtils.h>
// qt includes
#include <QTcpServer>
#include <QTcpSocket>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QThread>
#include <flatbufserver/FlatBufferConnection.h>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#endif
// Constants
namespace {
const int DEFAULT_FORWARDER_FLATBUFFFER_PRIORITY = 140;
constexpr std::chrono::milliseconds CONNECT_TIMEOUT{500}; // JSON-socket connect timeout in ms
} //End of constants
MessageForwarder::MessageForwarder(Hyperion* hyperion)
: _hyperion(hyperion)
, _log(nullptr)
, _muxer(_hyperion->getMuxerInstance())
, _forwarder_enabled(true)
, _priority(140)
, _log(nullptr)
, _muxer(_hyperion->getMuxerInstance())
, _forwarder_enabled(false)
, _priority(DEFAULT_FORWARDER_FLATBUFFFER_PRIORITY)
, _messageForwarderFlatBufHelper(nullptr)
{
QString subComponent = hyperion->property("instance").toString();
_log= Logger::getInstance("NETFORWARDER", subComponent);
qRegisterMetaType<TargetHost>("TargetHost");
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType("jsonapi")));
#endif
// get settings updates
connect(_hyperion, &Hyperion::settingsChanged, this, &MessageForwarder::handleSettingsUpdate);
@@ -37,82 +60,23 @@ MessageForwarder::MessageForwarder(Hyperion* hyperion)
// connect with Muxer visible priority changes
connect(_muxer, &PriorityMuxer::visiblePriorityChanged, this, &MessageForwarder::handlePriorityChanges);
// init
handleSettingsUpdate(settings::NETFORWARD, _hyperion->getSetting(settings::NETFORWARD));
}
MessageForwarder::~MessageForwarder()
{
while (!_forwardClients.isEmpty())
{
delete _forwardClients.takeFirst();
}
stopJsonTargets();
stopFlatbufferTargets();
}
void MessageForwarder::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
{
if (type == settings::NETFORWARD)
{
// clear the current targets
_jsonTargets.clear();
_flatbufferTargets.clear();
while (!_forwardClients.isEmpty())
{
delete _forwardClients.takeFirst();
}
// build new one
const QJsonObject& obj = config.object();
if (!obj["json"].isNull())
{
const QJsonArray& addr = obj["json"].toArray();
for (const auto& entry : addr)
{
addJsonTarget(entry.toObject());
}
}
if (!obj["flat"].isNull())
{
const QJsonArray& addr = obj["flat"].toArray();
for (const auto& entry : addr)
{
addFlatbufferTarget(entry.toObject());
}
}
bool isForwarderEnabledinSettings = obj["enable"].toBool(false);
if (!_jsonTargets.isEmpty() && isForwarderEnabledinSettings && _forwarder_enabled)
{
for (const auto& targetHost : qAsConst(_jsonTargets))
{
InfoIf(isForwarderEnabledinSettings, _log, "Forwarding now to JSON-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
}
connect(_hyperion, &Hyperion::forwardJsonMessage, this, &MessageForwarder::forwardJsonMessage, Qt::UniqueConnection);
}
else if (_jsonTargets.isEmpty() || !isForwarderEnabledinSettings || !_forwarder_enabled)
{
disconnect(_hyperion, &Hyperion::forwardJsonMessage, nullptr, nullptr);
}
if (!_flatbufferTargets.isEmpty() && isForwarderEnabledinSettings && _forwarder_enabled)
{
for (const auto& targetHost : qAsConst(_flatbufferTargets))
{
InfoIf(isForwarderEnabledinSettings, _log, "Forwarding now to Flatbuffer-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
}
}
else if (_flatbufferTargets.isEmpty() || !isForwarderEnabledinSettings || !_forwarder_enabled)
{
disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr);
disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr);
}
// update comp state
_hyperion->setNewComponentState(hyperion::COMP_FORWARDER, isForwarderEnabledinSettings);
enableTargets(isForwarderEnabledinSettings, obj);
}
}
@@ -120,30 +84,59 @@ void MessageForwarder::handleCompStateChangeRequest(hyperion::Components compone
{
if (component == hyperion::COMP_FORWARDER && _forwarder_enabled != enable)
{
_forwarder_enabled = enable;
handleSettingsUpdate(settings::NETFORWARD, _hyperion->getSetting(settings::NETFORWARD));
Info(_log, "Forwarder change state to %s", (_forwarder_enabled ? "enabled" : "disabled"));
_hyperion->setNewComponentState(component, _forwarder_enabled);
Info(_log, "Forwarder is %s", (enable ? "enabled" : "disabled"));
QJsonDocument config {_hyperion->getSetting(settings::type::NETFORWARD)};
enableTargets(enable, config.object());
}
}
void MessageForwarder::enableTargets(bool enable, const QJsonObject& config)
{
if (!enable)
{
_forwarder_enabled = false;
stopJsonTargets();
stopFlatbufferTargets();
}
else
{
int jsonTargetNum = startJsonTargets(config);
int flatbufTargetNum = startFlatbufferTargets(config);
if (flatbufTargetNum > 0)
{
hyperion::Components activeCompId = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority()).componentId;
if (activeCompId == hyperion::COMP_GRABBER)
{
connect(_hyperion, &Hyperion::forwardSystemProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection);
}
else if (activeCompId == hyperion::COMP_V4L)
{
connect(_hyperion, &Hyperion::forwardV4lProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection);
}
}
if (jsonTargetNum > 0 || flatbufTargetNum > 0)
{
_forwarder_enabled = true;
}
else
{
_forwarder_enabled = false;
Warning(_log,"No JSON- nor Flatbuffer-Forwarder configured -> Forwarding disabled", _forwarder_enabled);
}
}
_hyperion->setNewComponentState(hyperion::COMP_FORWARDER, _forwarder_enabled);
}
void MessageForwarder::handlePriorityChanges(int priority)
{
const QJsonObject obj = _hyperion->getSetting(settings::NETFORWARD).object();
if (priority != 0 && _forwarder_enabled && obj["enable"].toBool())
if (priority != 0 && _forwarder_enabled)
{
hyperion::Components activeCompId = _hyperion->getPriorityInfo(priority).componentId;
if (activeCompId == hyperion::COMP_GRABBER || activeCompId == hyperion::COMP_V4L)
{
if (!obj["flat"].isNull())
{
const QJsonArray& addr = obj["flat"].toArray();
for (const auto& entry : addr)
{
addFlatbufferTarget(entry.toObject());
}
}
switch (activeCompId)
{
case hyperion::COMP_GRABBER:
@@ -177,69 +170,131 @@ void MessageForwarder::addJsonTarget(const QJsonObject& targetConfig)
{
TargetHost targetHost;
QString config_host = targetConfig["host"].toString();
if (NetUtils::resolveHostAddress(_log, config_host, targetHost.host))
{
int config_port = targetConfig["port"].toInt();
if (NetUtils::isValidPort(_log, config_port, config_host))
{
targetHost.port = static_cast<quint16>(config_port);
QString hostName = targetConfig["host"].toString();
int port = targetConfig["port"].toInt();
// verify loop with JSON-server
const QJsonObject& obj = _hyperion->getSetting(settings::JSONSERVER).object();
if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast<quint16>(obj["port"].toInt()))
if (!hostName.isEmpty())
{
if (NetUtils::resolveHostToAddress(_log, hostName, targetHost.host, port))
{
QString address = targetHost.host.toString();
if (hostName != address)
{
Error(_log, "Loop between JSON-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(config_host), config_port);
Info(_log, "Resolved hostname [%s] to address [%s]", QSTRING_CSTR(hostName), QSTRING_CSTR(address));
}
else
if (NetUtils::isValidPort(_log, port, targetHost.host.toString()))
{
if (_forwarder_enabled)
targetHost.port = static_cast<quint16>(port);
// verify loop with JSON-server
const QJsonObject& obj = _hyperion->getSetting(settings::JSONSERVER).object();
if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast<quint16>(obj["port"].toInt()))
{
Error(_log, "Loop between JSON-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), port);
}
else
{
if (_jsonTargets.indexOf(targetHost) == -1)
{
Info(_log, "JSON-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
Debug(_log, "JSON-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
_jsonTargets << targetHost;
}
else
{
Warning(_log, "JSON Forwarder settings: Duplicate target host configuration! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
Warning(_log, "JSON-Forwarder settings: Duplicate target host configuration! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
}
}
}
}
}
}
int MessageForwarder::startJsonTargets(const QJsonObject& config)
{
if (!config["jsonapi"].isNull())
{
_jsonTargets.clear();
const QJsonArray& addr = config["jsonapi"].toArray();
#ifdef ENABLE_MDNS
if (!addr.isEmpty())
{
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType("jsonapi")));
}
#endif
for (const auto& entry : addr)
{
addJsonTarget(entry.toObject());
}
if (!_jsonTargets.isEmpty())
{
for (const auto& targetHost : qAsConst(_jsonTargets))
{
Info(_log, "Forwarding now to JSON-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
}
connect(_hyperion, &Hyperion::forwardJsonMessage, this, &MessageForwarder::forwardJsonMessage, Qt::UniqueConnection);
}
}
return _jsonTargets.size();
}
void MessageForwarder::stopJsonTargets()
{
if (!_jsonTargets.isEmpty())
{
disconnect(_hyperion, &Hyperion::forwardJsonMessage, nullptr, nullptr);
for (const auto& targetHost : qAsConst(_jsonTargets))
{
Info(_log, "Stopped forwarding to JSON-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
}
_jsonTargets.clear();
}
}
void MessageForwarder::addFlatbufferTarget(const QJsonObject& targetConfig)
{
TargetHost targetHost;
QString config_host = targetConfig["host"].toString();
if (NetUtils::resolveHostAddress(_log, config_host, targetHost.host))
{
int config_port = targetConfig["port"].toInt();
if (NetUtils::isValidPort(_log, config_port, config_host))
{
targetHost.port = static_cast<quint16>(config_port);
QString hostName = targetConfig["host"].toString();
int port = targetConfig["port"].toInt();
// verify loop with Flatbuffer-server
const QJsonObject& obj = _hyperion->getSetting(settings::FLATBUFSERVER).object();
if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast<quint16>(obj["port"].toInt()))
if (!hostName.isEmpty())
{
if (NetUtils::resolveHostToAddress(_log, hostName, targetHost.host, port))
{
QString address = targetHost.host.toString();
if (hostName != address)
{
Error(_log, "Loop between Flatbuffer-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(config_host), config_port);
Info(_log, "Resolved hostname [%s] to address [%s]", QSTRING_CSTR(hostName), QSTRING_CSTR(address));
}
else
if (NetUtils::isValidPort(_log, port, targetHost.host.toString()))
{
if (_forwarder_enabled)
targetHost.port = static_cast<quint16>(port);
// verify loop with Flatbuffer-server
const QJsonObject& obj = _hyperion->getSetting(settings::FLATBUFSERVER).object();
if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast<quint16>(obj["port"].toInt()))
{
Error(_log, "Loop between Flatbuffer-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), port);
}
else
{
if (_flatbufferTargets.indexOf(targetHost) == -1)
{
Info(_log, "Flatbuffer-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
Debug(_log, "Flatbuffer-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
_flatbufferTargets << targetHost;
FlatBufferConnection* flatbuf = new FlatBufferConnection("Forwarder", targetHost.host.toString(), _priority, false, targetHost.port);
_forwardClients << flatbuf;
if (_messageForwarderFlatBufHelper != nullptr)
{
emit _messageForwarderFlatBufHelper->addClient("Forwarder", targetHost, _priority, false);
}
}
else
{
@@ -251,6 +306,66 @@ void MessageForwarder::addFlatbufferTarget(const QJsonObject& targetConfig)
}
}
int MessageForwarder::startFlatbufferTargets(const QJsonObject& config)
{
if (!config["flatbuffer"].isNull())
{
if (_messageForwarderFlatBufHelper == nullptr)
{
_messageForwarderFlatBufHelper = new MessageForwarderFlatbufferClientsHelper();
}
else
{
emit _messageForwarderFlatBufHelper->clearClients();
}
_flatbufferTargets.clear();
const QJsonArray& addr = config["flatbuffer"].toArray();
#ifdef ENABLE_MDNS
if (!addr.isEmpty())
{
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType("flatbuffer")));
}
#endif
for (const auto& entry : addr)
{
addFlatbufferTarget(entry.toObject());
}
if (!_flatbufferTargets.isEmpty())
{
for (const auto& targetHost : qAsConst(_flatbufferTargets))
{
Info(_log, "Forwarding now to Flatbuffer-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
}
}
}
return _flatbufferTargets.size();
}
void MessageForwarder::stopFlatbufferTargets()
{
if (!_flatbufferTargets.isEmpty())
{
disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr);
disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr);
if (_messageForwarderFlatBufHelper != nullptr)
{
delete _messageForwarderFlatBufHelper;
_messageForwarderFlatBufHelper = nullptr;
}
for (const auto& targetHost : qAsConst(_flatbufferTargets))
{
Info(_log, "Stopped forwarding to Flatbuffer-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port);
}
_flatbufferTargets.clear();
}
}
void MessageForwarder::forwardJsonMessage(const QJsonObject& message)
{
if (_forwarder_enabled)
@@ -259,7 +374,7 @@ void MessageForwarder::forwardJsonMessage(const QJsonObject& message)
for (const auto& targetHost : qAsConst(_jsonTargets))
{
client.connectToHost(targetHost.host, targetHost.port);
if (client.waitForConnected(500))
if (client.waitForConnected(CONNECT_TIMEOUT.count()))
{
sendJsonMessage(message, &client);
client.close();
@@ -270,11 +385,13 @@ void MessageForwarder::forwardJsonMessage(const QJsonObject& message)
void MessageForwarder::forwardFlatbufferMessage(const QString& /*name*/, const Image<ColorRgb>& image)
{
if (_forwarder_enabled)
if (_messageForwarderFlatBufHelper != nullptr)
{
for (int i = 0; i < _forwardClients.size(); i++)
bool isfree = _messageForwarderFlatBufHelper->isFree();
if (isfree && _forwarder_enabled)
{
_forwardClients.at(i)->setImage(image);
QMetaObject::invokeMethod(_messageForwarderFlatBufHelper, "forwardImage", Qt::QueuedConnection, Q_ARG(Image<ColorRgb>, image));
}
}
}
@@ -316,12 +433,72 @@ void MessageForwarder::sendJsonMessage(const QJsonObject& message, QTcpSocket* s
// parse reply data
QJsonParseError error;
QJsonDocument::fromJson(serializedReply, &error);
/* QJsonDocument reply = */ QJsonDocument::fromJson(serializedReply, &error);
if (error.error != QJsonParseError::NoError)
{
Error(_log, "Error while parsing reply: invalid json");
Error(_log, "Error while parsing reply: invalid JSON");
return;
}
}
MessageForwarderFlatbufferClientsHelper::MessageForwarderFlatbufferClientsHelper()
{
QThread* mainThread = new QThread();
mainThread->setObjectName("ForwarderHelperThread");
this->moveToThread(mainThread);
mainThread->start();
_free = true;
connect(this, &MessageForwarderFlatbufferClientsHelper::addClient, this, &MessageForwarderFlatbufferClientsHelper::addClientHandler);
connect(this, &MessageForwarderFlatbufferClientsHelper::clearClients, this, &MessageForwarderFlatbufferClientsHelper::clearClientsHandler);
}
MessageForwarderFlatbufferClientsHelper::~MessageForwarderFlatbufferClientsHelper()
{
_free=false;
while (!_forwardClients.isEmpty())
{
_forwardClients.takeFirst()->deleteLater();
}
QThread* oldThread = this->thread();
disconnect(oldThread, nullptr, nullptr, nullptr);
oldThread->quit();
oldThread->wait();
delete oldThread;
}
void MessageForwarderFlatbufferClientsHelper::addClientHandler(const QString& origin, const TargetHost& targetHost, int priority, bool skipReply)
{
FlatBufferConnection* flatbuf = new FlatBufferConnection(origin, targetHost.host.toString(), priority, skipReply, targetHost.port);
_forwardClients << flatbuf;
_free = true;
}
void MessageForwarderFlatbufferClientsHelper::clearClientsHandler()
{
while (!_forwardClients.isEmpty())
{
delete _forwardClients.takeFirst();
}
_free = false;
}
bool MessageForwarderFlatbufferClientsHelper::isFree() const
{
return _free;
}
void MessageForwarderFlatbufferClientsHelper::forwardImage(const Image<ColorRgb>& image)
{
_free = false;
for (int i = 0; i < _forwardClients.size(); i++)
{
_forwardClients.at(i)->setImage(image);
}
_free = true;
}

View File

@@ -41,7 +41,3 @@ endif()
if(ENABLE_FORWARDER)
target_link_libraries(hyperion forwarder)
endif()
if (ENABLE_AVAHI)
target_link_libraries(hyperion bonjour)
endif ()

View File

@@ -149,6 +149,7 @@ void Hyperion::start()
if (_instIndex == 0)
{
_messageForwarder = new MessageForwarder(this);
_messageForwarder->handleSettingsUpdate(settings::NETFORWARD, getSetting(settings::NETFORWARD));
}
#endif
@@ -698,7 +699,6 @@ void Hyperion::update()
// Smoothing is disabled
if (! _deviceSmooth->enabled())
{
//std::cout << "Hyperion::update()> Non-Smoothing - "; LedDevice::printLedValues ( _ledBuffer);
emit ledDeviceData(_ledBuffer);
}
else
@@ -710,11 +710,4 @@ void Hyperion::update()
}
}
}
#if 0
else
{
//LEDDevice is disabled
Debug(_log, "LEDDevice is disabled - no update required");
}
#endif
}

View File

@@ -22,11 +22,16 @@ HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent, boo
Hyperion* HyperionIManager::getHyperionInstance(quint8 instance)
{
Hyperion* pInstance {nullptr};
if(_runningInstances.contains(instance))
return _runningInstances.value(instance);
Warning(_log,"The requested instance index '%d' with name '%s' isn't running, return main instance", instance, QSTRING_CSTR(_instanceTable->getNamebyIndex(instance)));
return _runningInstances.value(0);
if (!_runningInstances.isEmpty())
{
Warning(_log,"The requested instance index '%d' with name '%s' isn't running, return main instance", instance, QSTRING_CSTR(_instanceTable->getNamebyIndex(instance)));
pInstance = _runningInstances.value(0);
}
return pInstance;
}
QVector<QVariantMap> HyperionIManager::getInstanceData() const

View File

@@ -22,15 +22,15 @@ const int PriorityMuxer::ENDLESS = -1;
PriorityMuxer::PriorityMuxer(int ledCount, QObject * parent)
: QObject(parent)
, _log(nullptr)
, _currentPriority(PriorityMuxer::LOWEST_PRIORITY)
, _previousPriority(_currentPriority)
, _manualSelectedPriority(MANUAL_SELECTED_PRIORITY)
, _prevVisComp (hyperion::Components::COMP_COLOR)
, _sourceAutoSelectEnabled(true)
, _updateTimer(new QTimer(this))
, _timer(new QTimer(this))
, _blockTimer(new QTimer(this))
, _log(nullptr)
, _currentPriority(PriorityMuxer::LOWEST_PRIORITY)
, _previousPriority(_currentPriority)
, _manualSelectedPriority(MANUAL_SELECTED_PRIORITY)
, _prevVisComp (hyperion::Components::COMP_COLOR)
, _sourceAutoSelectEnabled(true)
, _updateTimer(new QTimer(this))
, _timer(new QTimer(this))
, _blockTimer(new QTimer(this))
{
QString subComponent = parent->property("instance").toString();
_log= Logger::getInstance("MUXER", subComponent);

View File

@@ -41,6 +41,33 @@
},
"access": "advanced",
"propertyOrder": 4
},
"enableAttempts": {
"type": "integer",
"title": "edt_dev_general_enableAttempts_title",
"minimum": 0,
"maximum": 120,
"default": 12,
"required": true,
"options": {
"infoText": "edt_dev_general_enableAttempts_title_info"
},
"access": "advanced",
"propertyOrder": 5
},
"enableAttemptsInterval": {
"type": "integer",
"title": "edt_dev_general_enableAttemptsInterval_title",
"minimum": 5,
"maximum": 120,
"default": 15,
"required": true,
"append": "edt_append_s",
"options": {
"infoText": "edt_dev_general_enableAttemptsInterval_title_info"
},
"access": "advanced",
"propertyOrder": 6
}
},
"dependencies": {

View File

@@ -1,77 +1,106 @@
{
"type" : "object",
"title" : "edt_conf_fw_heading_title",
"required" : true,
"properties": {
"enable": {
"type": "boolean",
"title": "edt_conf_general_enable_title",
"required": true,
"default": false,
"propertyOrder": 1
},
"json": {
"type": "array",
"title": "edt_conf_fw_json_title",
"propertyOrder": 2,
"uniqueItems": true,
"items": {
"type": "object",
"title": "edt_conf_fw_json_itemtitle",
"required": true,
"properties": {
"host": {
"type": "string",
"format": "hostname_or_ip",
"minLength": 7,
"title": "edt_dev_spec_targetIpHost_title",
"required": true,
"propertyOrder": 1
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"default": 19444,
"title": "edt_dev_spec_port_title",
"required": true,
"access": "expert",
"propertyOrder": 2
}
}
}
},
"flat": {
"type": "array",
"title": "edt_conf_fw_flat_title",
"propertyOrder": 3,
"uniqueItems": true,
"items": {
"type": "object",
"title": "edt_conf_fw_flat_itemtitle",
"required": true,
"properties": {
"host": {
"type": "string",
"format": "hostname_or_ip",
"minLength": 7,
"title": "edt_dev_spec_targetIpHost_title",
"required": true,
"propertyOrder": 1
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"default": 19400,
"title": "edt_dev_spec_port_title",
"required": true,
"access": "expert",
"propertyOrder": 2
}
}
}
}
},
"additionalProperties": false
}
"type": "object",
"title": "edt_conf_fw_heading_title",
"required": true,
"properties": {
"enable": {
"type": "boolean",
"title": "edt_conf_general_enable_title",
"required": true,
"default": false,
"propertyOrder": 1
},
"jsonapiselect": {
"type": "array",
"uniqueItems": true,
"format": "select",
"title": "edt_conf_fw_json_services_discovered_title",
"propertyOrder": 2
},
"jsonapi": {
"type": "array",
"title": "edt_conf_fw_json_title",
"uniqueItems": true,
"access": "expert",
"items": {
"type": "object",
"title": "edt_conf_fw_json_itemtitle",
"properties": {
"name": {
"type": "string",
"title": "edt_conf_fw_service_name_title",
"required": true,
"access": "expert",
"propertyOrder": 1
},
"host": {
"type": "string",
"format": "hostname_or_ip",
"minLength": 7,
"title": "edt_dev_spec_targetIpHost_title",
"required": true,
"access": "expert",
"propertyOrder": 2
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"title": "edt_dev_spec_port_title",
"required": true,
"access": "expert",
"propertyOrder": 3
}
}
},
"propertyOrder": 3
},
"flatbufferselect": {
"type": "array",
"uniqueItems": true,
"format": "select",
"title": "edt_conf_fw_flat_services_discovered_title",
"propertyOrder": 4
},
"flatbuffer": {
"type": "array",
"title": "edt_conf_fw_flat_title",
"uniqueItems": true,
"access": "expert",
"items": {
"type": "object",
"title": "edt_conf_fw_flat_itemtitle",
"properties": {
"name": {
"type": "string",
"title": "edt_conf_fw_service_name_title",
"access": "expert",
"propertyOrder": 1
},
"host": {
"type": "string",
"format": "hostname_or_ip",
"minLength": 7,
"title": "edt_dev_spec_targetIpHost_title",
"required": true,
"access": "expert",
"propertyOrder": 2
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"default": 19400,
"title": "edt_dev_spec_port_title",
"required": true,
"access": "expert",
"propertyOrder": 3
}
}
},
"propertyOrder": 5
}
},
"additionalProperties": false
}

View File

@@ -13,3 +13,8 @@ target_link_libraries(jsonserver
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Gui
)
if(ENABLE_MDNS)
target_link_libraries(jsonserver mdns)
endif()

View File

@@ -1,37 +1,35 @@
// system includes
#include <stdexcept>
// project includes
#include "HyperionConfig.h"
#include <jsonserver/JsonServer.h>
#include "JsonClientConnection.h"
// bonjour include
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourserviceregister.h>
#endif
#include <utils/NetOrigin.h>
// qt includes
#include <QTcpServer>
#include <QTcpSocket>
#include <QJsonDocument>
#include <QByteArray>
// project includes
#include "HyperionConfig.h"
#include <jsonserver/JsonServer.h>
#include "JsonClientConnection.h"
#include <utils/NetOrigin.h>
// Constants
namespace {
const char SERVICE_TYPE[] = "jsonapi";
} //End of constants
JsonServer::JsonServer(const QJsonDocument& config)
: QObject()
, _server(new QTcpServer(this))
, _openConnections()
, _log(Logger::getInstance("JSONSERVER"))
, _netOrigin(NetOrigin::getInstance())
, _config(config)
{
Debug(_log, "Created instance");
// Set trigger for incoming connections
connect(_server, &QTcpServer::newConnection, this, &JsonServer::newConnection);
// init
handleSettingsUpdate(settings::JSONSERVER, config);
}
JsonServer::~JsonServer()
@@ -39,31 +37,29 @@ JsonServer::~JsonServer()
qDeleteAll(_openConnections);
}
void JsonServer::initServer()
{
// Set trigger for incoming connections
connect(_server, &QTcpServer::newConnection, this, &JsonServer::newConnection);
// init
handleSettingsUpdate(settings::JSONSERVER, _config);
}
void JsonServer::start()
{
if(_server->isListening())
return;
if (!_server->listen(QHostAddress::Any, _port))
if(!_server->isListening())
{
Error(_log,"Could not bind to port '%d', please use an available port", _port);
return;
if (!_server->listen(QHostAddress::Any, _port))
{
Error(_log,"Could not bind to port '%d', please use an available port", _port);
}
else
{
Info(_log, "Started on port %d", _port);
emit publishService(SERVICE_TYPE, _port);
}
}
Info(_log, "Started on port %d", _port);
#ifdef ENABLE_AVAHI
if(_serviceRegister == nullptr)
{
_serviceRegister = new BonjourServiceRegister(this);
_serviceRegister->registerService("_hyperiond-json._tcp", _port);
}
else if( _serviceRegister->getPort() != _port)
{
delete _serviceRegister;
_serviceRegister = new BonjourServiceRegister(this);
_serviceRegister->registerService("_hyperiond-json._tcp", _port);
}
#endif
}
void JsonServer::stop()

View File

@@ -141,3 +141,6 @@ if (ENABLE_DEV_USB_HID)
endif()
endif()
if(ENABLE_MDNS)
target_link_libraries(leddevice mdns)
endif()

View File

@@ -15,84 +15,91 @@
//std includes
#include <sstream>
#include <iomanip>
#include <chrono>
// Constants
namespace {
// Configuration settings
const char CONFIG_CURRENT_LED_COUNT[] = "currentLedCount";
const char CONFIG_COLOR_ORDER[] = "colorOrder";
const char CONFIG_AUTOSTART[] = "autoStart";
const char CONFIG_LATCH_TIME[] = "latchTime";
const char CONFIG_REWRITE_TIME[] = "rewriteTime";
// Configuration settings
const char CONFIG_CURRENT_LED_COUNT[] = "currentLedCount";
const char CONFIG_COLOR_ORDER[] = "colorOrder";
const char CONFIG_AUTOSTART[] = "autoStart";
const char CONFIG_LATCH_TIME[] = "latchTime";
const char CONFIG_REWRITE_TIME[] = "rewriteTime";
int DEFAULT_LED_COUNT = 1;
const char DEFAULT_COLOR_ORDER[] = "RGB";
const bool DEFAULT_IS_AUTOSTART = true;
int DEFAULT_LED_COUNT{ 1 };
const char DEFAULT_COLOR_ORDER[]{ "RGB" };
const bool DEFAULT_IS_AUTOSTART{ true };
const char CONFIG_ENABLE_ATTEMPTS[] = "enableAttempts";
const char CONFIG_ENABLE_ATTEMPTS_INTERVALL[] = "enableAttemptsInterval";
const int DEFAULT_MAX_ENABLE_ATTEMPTS{ 5 };
constexpr std::chrono::seconds DEFAULT_ENABLE_ATTEMPTS_INTERVAL{ 5 };
} //End of constants
LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent)
: QObject(parent)
, _devConfig(deviceConfig)
, _log(Logger::getInstance("LEDDEVICE"))
, _ledBuffer(0)
, _refreshTimer(nullptr)
, _refreshTimerInterval_ms(0)
, _latchTime_ms(0)
, _ledCount(0)
, _isRestoreOrigState(false)
, _isEnabled(false)
, _isDeviceInitialised(false)
, _isDeviceReady(false)
, _isOn(false)
, _isDeviceInError(false)
, _isInSwitchOff (false)
, _lastWriteTime(QDateTime::currentDateTime())
, _isRefreshEnabled (false)
, _isAutoStart(true)
, _devConfig(deviceConfig)
, _log(Logger::getInstance("LEDDEVICE"))
, _ledBuffer(0)
, _refreshTimer(nullptr)
, _enableAttemptsTimer(nullptr)
, _refreshTimerInterval_ms(0)
, _enableAttemptTimerInterval(DEFAULT_ENABLE_ATTEMPTS_INTERVAL)
, _enableAttempts(0)
, _maxEnableAttempts(DEFAULT_MAX_ENABLE_ATTEMPTS)
, _latchTime_ms(0)
, _ledCount(0)
, _isRestoreOrigState(false)
, _isEnabled(false)
, _isDeviceInitialised(false)
, _isDeviceReady(false)
, _isOn(false)
, _isDeviceInError(false)
, _lastWriteTime(QDateTime::currentDateTime())
, _isRefreshEnabled(false)
, _isAutoStart(true)
{
_activeDeviceType = deviceConfig["type"].toString("UNSPECIFIED").toLower();
}
LedDevice::~LedDevice()
{
delete _refreshTimer;
this->stopEnableAttemptsTimer();
this->stopRefreshTimer();
}
void LedDevice::start()
{
Info(_log, "Start LedDevice '%s'.", QSTRING_CSTR(_activeDeviceType));
// setup refreshTimer
if ( _refreshTimer == nullptr )
{
_refreshTimer = new QTimer(this);
_refreshTimer->setTimerType(Qt::PreciseTimer);
_refreshTimer->setInterval( _refreshTimerInterval_ms );
connect(_refreshTimer, &QTimer::timeout, this, &LedDevice::rewriteLEDs );
}
close();
_isDeviceInitialised = false;
// General initialisation and configuration of LedDevice
if ( init(_devConfig) )
if (init(_devConfig))
{
// Everything is OK -> enable device
_isDeviceInitialised = true;
if (_isAutoStart)
{
this->enable();
if (!_isEnabled)
{
Debug(_log, "Not enabled -> enable device");
enable();
}
}
}
}
void LedDevice::stop()
{
Debug(_log, "Stop device");
this->disable();
this->stopRefreshTimer();
Info(_log, " Stopped LedDevice '%s'", QSTRING_CSTR(_activeDeviceType) );
Info(_log, " Stopped LedDevice '%s'", QSTRING_CSTR(_activeDeviceType));
}
int LedDevice::open()
@@ -113,6 +120,7 @@ int LedDevice::close()
void LedDevice::setInError(const QString& errorMsg)
{
_isOn = false;
_isDeviceInError = true;
_isDeviceReady = false;
_isEnabled = false;
@@ -124,21 +132,53 @@ void LedDevice::setInError(const QString& errorMsg)
void LedDevice::enable()
{
if ( !_isEnabled )
Debug(_log, "Enable device %s'", QSTRING_CSTR(_activeDeviceType));
if (!_isEnabled)
{
if (_enableAttemptsTimer != nullptr && _enableAttemptsTimer->isActive())
{
_enableAttemptsTimer->stop();
}
_isDeviceInError = false;
if ( ! _isDeviceReady )
if (!_isDeviceInitialised)
{
_isDeviceInitialised = init(_devConfig);
}
if (!_isDeviceReady)
{
open();
}
if ( _isDeviceReady )
bool isEnableFailed(true);
if (_isDeviceReady)
{
_isEnabled = true;
if ( switchOn() )
if (switchOn())
{
stopEnableAttemptsTimer();
_isEnabled = true;
isEnableFailed = false;
emit enableStateChanged(_isEnabled);
Info(_log, "LedDevice '%s' enabled", QSTRING_CSTR(_activeDeviceType));
}
}
if (isEnableFailed)
{
emit enableStateChanged(false);
if (_maxEnableAttempts > 0)
{
Debug(_log, "Device's enablement failed - Start retry timer. Retried already done [%d], isEnabled: [%d]", _enableAttempts, _isEnabled);
startEnableAttemptsTimer();
}
else
{
Debug(_log, "Device's enablement failed");
}
}
}
@@ -146,9 +186,11 @@ void LedDevice::enable()
void LedDevice::disable()
{
if ( _isEnabled )
Debug(_log, "Disable device %s'", QSTRING_CSTR(_activeDeviceType));
if (_isEnabled)
{
_isEnabled = false;
this->stopEnableAttemptsTimer();
this->stopRefreshTimer();
switchOff();
@@ -163,47 +205,110 @@ void LedDevice::setActiveDeviceType(const QString& deviceType)
_activeDeviceType = deviceType;
}
bool LedDevice::init(const QJsonObject &deviceConfig)
bool LedDevice::init(const QJsonObject& deviceConfig)
{
Debug(_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData() );
Debug(_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData());
_colorOrder = deviceConfig[CONFIG_COLOR_ORDER].toString(DEFAULT_COLOR_ORDER);
_isAutoStart = deviceConfig[CONFIG_AUTOSTART].toBool(DEFAULT_IS_AUTOSTART);
setLedCount( deviceConfig[CONFIG_CURRENT_LED_COUNT].toInt(DEFAULT_LED_COUNT) ); // property injected to reflect real led count
setLatchTime( deviceConfig[CONFIG_LATCH_TIME].toInt( _latchTime_ms ) );
setRewriteTime ( deviceConfig[CONFIG_REWRITE_TIME].toInt( _refreshTimerInterval_ms) );
setLedCount(deviceConfig[CONFIG_CURRENT_LED_COUNT].toInt(DEFAULT_LED_COUNT)); // property injected to reflect real led count
setColorOrder(deviceConfig[CONFIG_COLOR_ORDER].toString(DEFAULT_COLOR_ORDER));
setLatchTime(deviceConfig[CONFIG_LATCH_TIME].toInt(_latchTime_ms));
setRewriteTime(deviceConfig[CONFIG_REWRITE_TIME].toInt(_refreshTimerInterval_ms));
setAutoStart(deviceConfig[CONFIG_AUTOSTART].toBool(DEFAULT_IS_AUTOSTART));
setEnableAttempts(deviceConfig[CONFIG_ENABLE_ATTEMPTS].toInt(DEFAULT_MAX_ENABLE_ATTEMPTS),
std::chrono::seconds(deviceConfig[CONFIG_ENABLE_ATTEMPTS_INTERVALL].toInt(DEFAULT_ENABLE_ATTEMPTS_INTERVAL.count()))
);
return true;
}
void LedDevice::startRefreshTimer()
{
if ( _isDeviceReady && _isEnabled )
if (_refreshTimerInterval_ms > 0)
{
_refreshTimer->start();
if (_isDeviceReady && _isOn)
{
// setup refreshTimer
if (_refreshTimer == nullptr)
{
_refreshTimer = new QTimer(this);
_refreshTimer->setTimerType(Qt::PreciseTimer);
connect(_refreshTimer, &QTimer::timeout, this, &LedDevice::rewriteLEDs);
}
_refreshTimer->setInterval(_refreshTimerInterval_ms);
//Debug(_log, "Start refresh timer with interval = %ims", _refreshTimer->interval());
_refreshTimer->start();
}
else
{
Debug(_log, "Device is not ready to start a refresh timer");
}
}
}
void LedDevice::stopRefreshTimer()
{
if ( _refreshTimer != nullptr )
if (_refreshTimer != nullptr)
{
//Debug(_log, "Stopping refresh timer");
_refreshTimer->stop();
delete _refreshTimer;
_refreshTimer = nullptr;
}
}
int LedDevice::updateLeds(const std::vector<ColorRgb>& ledValues)
void LedDevice::startEnableAttemptsTimer()
{
++_enableAttempts;
if (_enableAttempts <= _maxEnableAttempts)
{
if (_enableAttemptTimerInterval.count() > 0)
{
// setup enable retry timer
if (_enableAttemptsTimer == nullptr)
{
_enableAttemptsTimer = new QTimer(this);
_enableAttemptsTimer->setTimerType(Qt::PreciseTimer);
connect(_enableAttemptsTimer, &QTimer::timeout, this, &LedDevice::enable);
}
_enableAttemptsTimer->setInterval(static_cast<int>(_enableAttemptTimerInterval.count() * 1000)); //NOLINT
Info(_log, "Start %d. attempt of %d to enable the device in %d seconds", _enableAttempts, _maxEnableAttempts, _enableAttemptTimerInterval.count());
_enableAttemptsTimer->start();
}
}
else
{
Error(_log, "Device disabled. Maximum number of %d attempts enabling the device reached. Tried for %d seconds.", _maxEnableAttempts, _enableAttempts * _enableAttemptTimerInterval.count());
_enableAttempts = 0;
}
}
void LedDevice::stopEnableAttemptsTimer()
{
if (_enableAttemptsTimer != nullptr)
{
Debug(_log, "Stopping enable retry timer");
_enableAttemptsTimer->stop();
delete _enableAttemptsTimer;
_enableAttemptsTimer = nullptr;
_enableAttempts = 0;
}
}
int LedDevice::updateLeds(std::vector<ColorRgb> ledValues)
{
int retval = 0;
if ( !_isEnabled || !_isOn || !_isDeviceReady || _isDeviceInError )
if (!_isEnabled || !_isOn || !_isDeviceReady || _isDeviceInError)
{
//std::cout << "LedDevice::updateLeds(), LedDevice NOT ready! ";
retval = -1;
}
else
{
qint64 elapsedTimeMs = _lastWriteTime.msecsTo( QDateTime::currentDateTime() );
qint64 elapsedTimeMs = _lastWriteTime.msecsTo(QDateTime::currentDateTime());
if (_latchTime_ms == 0 || elapsedTimeMs >= _latchTime_ms)
{
//std::cout << "LedDevice::updateLeds(), Elapsed time since last write (" << elapsedTimeMs << ") ms > _latchTime_ms (" << _latchTime_ms << ") ms" << std::endl;
@@ -211,16 +316,16 @@ int LedDevice::updateLeds(const std::vector<ColorRgb>& ledValues)
_lastWriteTime = QDateTime::currentDateTime();
// if device requires refreshing, save Led-Values and restart the timer
if ( _isRefreshEnabled && _isEnabled )
if (_isRefreshEnabled && _isEnabled)
{
this->startRefreshTimer();
_lastLedValues = ledValues;
this->startRefreshTimer();
}
}
else
{
//std::cout << "LedDevice::updateLeds(), Skip write. elapsedTime (" << elapsedTimeMs << ") ms < _latchTime_ms (" << _latchTime_ms << ") ms" << std::endl;
if ( _isRefreshEnabled )
if (_isRefreshEnabled)
{
//Stop timer to allow for next non-refresh update
this->stopRefreshTimer();
@@ -234,18 +339,21 @@ int LedDevice::rewriteLEDs()
{
int retval = -1;
if ( _isDeviceReady && _isEnabled )
if (_isEnabled && _isOn && _isDeviceReady && !_isDeviceInError)
{
// qint64 elapsedTimeMs = _lastWriteTime.msecsTo(QDateTime::currentDateTime());
// std::cout << "LedDevice::rewriteLEDs(): Rewrite LEDs now, elapsedTime [" << elapsedTimeMs << "] ms" << std::endl;
// //:TESTING: Inject "white" output records to differentiate from normal writes
// _lastLedValues.clear();
// _lastLedValues.resize(static_cast<unsigned long>(_ledCount), ColorRgb::WHITE);
// printLedValues(_lastLedValues);
// //:TESTING:
// qint64 elapsedTimeMs = _lastWriteTime.msecsTo(QDateTime::currentDateTime());
// std::cout << "LedDevice::rewriteLEDs(): Rewrite LEDs now, elapsedTime [" << elapsedTimeMs << "] ms" << std::endl;
// //:TESTING: Inject "white" output records to differentiate from normal writes
// _lastLedValues.clear();
// _lastLedValues.resize(static_cast<unsigned long>(_ledCount), ColorRgb::WHITE);
// printLedValues(_lastLedValues);
// //:TESTING:
retval = write(_lastLedValues);
_lastWriteTime = QDateTime::currentDateTime();
if (!_lastLedValues.empty())
{
retval = write(_lastLedValues);
_lastWriteTime = QDateTime::currentDateTime();
}
}
else
{
@@ -257,6 +365,7 @@ int LedDevice::rewriteLEDs()
int LedDevice::writeBlack(int numberOfWrites)
{
Debug(_log, "Set LED strip to black to switch of LEDs");
return writeColor(ColorRgb::BLACK, numberOfWrites);
}
@@ -273,7 +382,7 @@ int LedDevice::writeColor(const ColorRgb& color, int numberOfWrites)
QTimer::singleShot(_latchTime_ms, &loop, &QEventLoop::quit);
loop.exec();
}
_lastLedValues = std::vector<ColorRgb>(static_cast<unsigned long>(_ledCount),color);
_lastLedValues = std::vector<ColorRgb>(static_cast<unsigned long>(_ledCount), color);
rc = write(_lastLedValues);
}
return rc;
@@ -281,24 +390,31 @@ int LedDevice::writeColor(const ColorRgb& color, int numberOfWrites)
bool LedDevice::switchOn()
{
bool rc = false;
bool rc{ false };
if ( _isOn )
if (_isOn)
{
Debug(_log, "Device %s is already on. Skipping.", QSTRING_CSTR(_activeDeviceType));
rc = true;
}
else
{
if ( _isEnabled &&_isDeviceInitialised )
if (_isDeviceReady)
{
if ( storeState() )
Info(_log, "Switching device %s ON", QSTRING_CSTR(_activeDeviceType));
if (storeState())
{
if ( powerOn() )
if (powerOn())
{
Info(_log, "Device %s is ON", QSTRING_CSTR(_activeDeviceType));
_isOn = true;
_isInSwitchOff = false;
emit enableStateChanged(_isEnabled);
rc = true;
}
else
{
Warning(_log, "Failed switching device %s ON", QSTRING_CSTR(_activeDeviceType));
}
}
}
}
@@ -307,32 +423,40 @@ bool LedDevice::switchOn()
bool LedDevice::switchOff()
{
bool rc = false;
bool rc{ false };
if ( !_isOn )
if (!_isOn)
{
rc = true;
}
else
{
if ( _isDeviceInitialised )
if (_isDeviceInitialised)
{
// Disable device to ensure no standard Led updates are written/processed
Info(_log, "Switching device %s OFF", QSTRING_CSTR(_activeDeviceType));
// Disable device to ensure no standard LED updates are written/processed
_isOn = false;
_isInSwitchOff = true;
rc = true;
if ( _isDeviceReady )
if (_isDeviceReady)
{
if ( _isRestoreOrigState )
if (_isRestoreOrigState)
{
//Restore devices state
restoreState();
}
else
{
powerOff();
if (powerOff())
{
Info(_log, "Device %s is OFF", QSTRING_CSTR(_activeDeviceType));
}
else
{
Warning(_log, "Failed switching device %s OFF", QSTRING_CSTR(_activeDeviceType));
}
}
}
}
@@ -342,10 +466,12 @@ bool LedDevice::switchOff()
bool LedDevice::powerOff()
{
bool rc = false;
bool rc{ false };
Debug(_log, "Power Off: %s", QSTRING_CSTR(_activeDeviceType));
// Simulate power-off by writing a final "Black" to have a defined outcome
if ( writeBlack() >= 0 )
if (writeBlack() >= 0)
{
rc = true;
}
@@ -354,15 +480,18 @@ bool LedDevice::powerOff()
bool LedDevice::powerOn()
{
bool rc = true;
bool rc{ true };
Debug(_log, "Power On: %s", QSTRING_CSTR(_activeDeviceType));
return rc;
}
bool LedDevice::storeState()
{
bool rc = true;
bool rc{ true };
if ( _isRestoreOrigState )
if (_isRestoreOrigState)
{
// Save device's original state
// _originalStateValues = get device's state;
@@ -373,9 +502,9 @@ bool LedDevice::storeState()
bool LedDevice::restoreState()
{
bool rc = true;
bool rc{ true };
if ( _isRestoreOrigState )
if (_isRestoreOrigState)
{
// Restore device's original state
// update device using _originalStateValues
@@ -393,7 +522,7 @@ QJsonObject LedDevice::discover(const QJsonObject& /*params*/)
QJsonArray deviceList;
devicesDiscovered.insert("devices", deviceList);
Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() );
Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
}
@@ -401,75 +530,98 @@ QString LedDevice::discoverFirst()
{
QString deviceDiscovered;
Debug(_log, "deviceDiscovered: [%s]", QSTRING_CSTR(deviceDiscovered) );
Debug(_log, "deviceDiscovered: [%s]", QSTRING_CSTR(deviceDiscovered));
return deviceDiscovered;
}
QJsonObject LedDevice::getProperties(const QJsonObject& params)
{
Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties;
QJsonObject deviceProperties;
properties.insert("properties", deviceProperties);
Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
return properties;
}
void LedDevice::setLogger(Logger* log)
{
_log = log;
}
void LedDevice::setLedCount(int ledCount)
{
assert(ledCount >= 0);
_ledCount = ledCount;
_ledRGBCount = _ledCount * sizeof(ColorRgb);
_ledCount = static_cast<uint>(ledCount);
_ledRGBCount = _ledCount * sizeof(ColorRgb);
_ledRGBWCount = _ledCount * sizeof(ColorRgbw);
Debug(_log, "LedCount set to %d", _ledCount);
}
void LedDevice::setLatchTime( int latchTime_ms )
void LedDevice::setColorOrder(const QString& colorOrder)
{
_colorOrder = colorOrder;
Debug(_log, "ColorOrder set to %s", QSTRING_CSTR(_colorOrder.toUpper()));
}
void LedDevice::setLatchTime(int latchTime_ms)
{
assert(latchTime_ms >= 0);
_latchTime_ms = latchTime_ms;
Debug(_log, "LatchTime updated to %dms", _latchTime_ms);
Debug(_log, "LatchTime set to %dms", _latchTime_ms);
}
void LedDevice::setRewriteTime( int rewriteTime_ms )
void LedDevice::setAutoStart(bool isAutoStart)
{
assert(rewriteTime_ms >= 0);
_isAutoStart = isAutoStart;
Debug(_log, "AutoStart %s", (_isAutoStart ? "enabled" : "disabled"));
}
//Check, if refresh timer was not initialised due to getProperties/identify sceanrios
if (_refreshTimer != nullptr)
void LedDevice::setRewriteTime(int rewriteTime_ms)
{
_refreshTimerInterval_ms = qMax(rewriteTime_ms, 0);
if (_refreshTimerInterval_ms > 0)
{
_refreshTimerInterval_ms = rewriteTime_ms;
_isRefreshEnabled = true;
if (_refreshTimerInterval_ms > 0)
if (_refreshTimerInterval_ms <= _latchTime_ms)
{
_isRefreshEnabled = true;
if (_refreshTimerInterval_ms <= _latchTime_ms)
{
int new_refresh_timer_interval = _latchTime_ms + 10;
Warning(_log, "latchTime(%d) is bigger/equal rewriteTime(%d), set rewriteTime to %dms", _latchTime_ms, _refreshTimerInterval_ms, new_refresh_timer_interval);
_refreshTimerInterval_ms = new_refresh_timer_interval;
_refreshTimer->setInterval(_refreshTimerInterval_ms);
}
Debug(_log, "Refresh interval = %dms", _refreshTimerInterval_ms);
_refreshTimer->setInterval(_refreshTimerInterval_ms);
_lastWriteTime = QDateTime::currentDateTime();
int new_refresh_timer_interval = _latchTime_ms + 10; //NOLINT
Warning(_log, "latchTime(%d) is bigger/equal rewriteTime(%d), set rewriteTime to %dms", _latchTime_ms, _refreshTimerInterval_ms, new_refresh_timer_interval);
_refreshTimerInterval_ms = new_refresh_timer_interval;
}
Debug(_log, "RewriteTime updated to %dms", _refreshTimerInterval_ms);
Debug(_log, "Refresh interval = %dms", _refreshTimerInterval_ms);
startRefreshTimer();
}
else
{
_isRefreshEnabled = false;
stopRefreshTimer();
}
}
void LedDevice::setEnableAttempts(int maxEnableRetries, std::chrono::seconds enableRetryTimerInterval)
{
stopEnableAttemptsTimer();
maxEnableRetries = qMax(maxEnableRetries, 0);
_enableAttempts = 0;
_maxEnableAttempts = maxEnableRetries;
_enableAttemptTimerInterval = enableRetryTimerInterval;
Debug(_log, "Max enable retries: %d, enable retry interval = %llds", _maxEnableAttempts, static_cast<int>(_enableAttemptTimerInterval.count()));
}
void LedDevice::printLedValues(const std::vector<ColorRgb>& ledValues)
{
std::cout << "LedValues [" << ledValues.size() <<"] [";
std::cout << "LedValues [" << ledValues.size() << "] [";
for (const ColorRgb& color : ledValues)
{
std::cout << color;
@@ -477,24 +629,24 @@ void LedDevice::printLedValues(const std::vector<ColorRgb>& ledValues)
std::cout << "]" << std::endl;
}
QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const int size, int number) const
QString LedDevice::uint8_t_to_hex_string(const uint8_t* data, const int size, int number)
{
if ( number <= 0 || number > size)
if (number <= 0 || number > size)
{
number = size;
}
QByteArray bytes (reinterpret_cast<const char*>(data), number);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
return bytes.toHex(':');
#else
return bytes.toHex();
#endif
QByteArray bytes(reinterpret_cast<const char*>(data), number);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
return bytes.toHex(':');
#else
return bytes.toHex();
#endif
}
QString LedDevice::toHex(const QByteArray& data, int number) const
QString LedDevice::toHex(const QByteArray& data, int number)
{
if ( number <= 0 || number > data.size())
if (number <= 0 || number > data.size())
{
number = data.size();
}
@@ -505,3 +657,47 @@ QString LedDevice::toHex(const QByteArray& data, int number) const
return data.left(number).toHex();
#endif
}
bool LedDevice::isInitialised() const
{
return _isDeviceInitialised;
}
bool LedDevice::isReady() const
{
return _isDeviceReady;
}
bool LedDevice::isInError() const
{
return _isDeviceInError;
}
int LedDevice::getLatchTime() const
{
return _latchTime_ms;
}
int LedDevice::getRewriteTime() const
{
return _refreshTimerInterval_ms;
}
int LedDevice::getLedCount() const
{
return static_cast<int>(_ledCount);
}
QString LedDevice::getActiveDeviceType() const
{
return _activeDeviceType;
}
QString LedDevice::getColorOrder() const
{
return _colorOrder;
}
bool LedDevice::componentState() const {
return _isEnabled;
}

View File

@@ -27,6 +27,7 @@
<file alias="schema-udpartnet">schemas/schema-artnet.json</file>
<file alias="schema-udph801">schemas/schema-h801.json</file>
<file alias="schema-udpraw">schemas/schema-udpraw.json</file>
<file alias="schema-udpddp">schemas/schema-udpddp.json</file>
<file alias="schema-ws2801">schemas/schema-ws2801.json</file>
<file alias="schema-ws2812spi">schemas/schema-ws2812spi.json</file>
<file alias="schema-apa104">schemas/schema-apa104.json</file>

View File

@@ -67,9 +67,6 @@ void LedDeviceWrapper::createLedDevice(const QJsonObject& config)
// further signals
connect(this, &LedDeviceWrapper::updateLeds, _ledDevice, &LedDevice::updateLeds, Qt::QueuedConnection);
connect(this, &LedDeviceWrapper::enable, _ledDevice, &LedDevice::enable);
connect(this, &LedDeviceWrapper::disable, _ledDevice, &LedDevice::disable);
connect(this, &LedDeviceWrapper::switchOn, _ledDevice, &LedDevice::switchOn);
connect(this, &LedDeviceWrapper::switchOff, _ledDevice, &LedDevice::switchOff);
@@ -81,6 +78,100 @@ void LedDeviceWrapper::createLedDevice(const QJsonObject& config)
thread->start();
}
void LedDeviceWrapper::handleComponentState(hyperion::Components component, bool state)
{
if (component == hyperion::COMP_LEDDEVICE)
{
if (state)
{
QMetaObject::invokeMethod(_ledDevice, "enable", Qt::BlockingQueuedConnection);
}
else
{
QMetaObject::invokeMethod(_ledDevice, "disable", Qt::BlockingQueuedConnection);
}
QMetaObject::invokeMethod(_ledDevice, "componentState", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _enabled));
}
}
void LedDeviceWrapper::handleInternalEnableState(bool newState)
{
_hyperion->setNewComponentState(hyperion::COMP_LEDDEVICE, newState);
_enabled = newState;
if (_enabled)
{
_hyperion->update();
}
}
void LedDeviceWrapper::stopDeviceThread()
{
// turns the LEDs off & stop refresh timers
emit stopLedDevice();
// get current thread
QThread* oldThread = _ledDevice->thread();
disconnect(oldThread, nullptr, nullptr, nullptr);
oldThread->quit();
oldThread->wait();
delete oldThread;
disconnect(_ledDevice, nullptr, nullptr, nullptr);
delete _ledDevice;
_ledDevice = nullptr;
}
QString LedDeviceWrapper::getActiveDeviceType() const
{
QString value = 0;
QMetaObject::invokeMethod(_ledDevice, "getActiveDeviceType", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, value));
return value;
}
unsigned int LedDeviceWrapper::getLedCount() const
{
int value = 0;
QMetaObject::invokeMethod(_ledDevice, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value));
return value;
}
QString LedDeviceWrapper::getColorOrder() const
{
QString value;
QMetaObject::invokeMethod(_ledDevice, "getColorOrder", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, value));
return value;
}
int LedDeviceWrapper::getLatchTime() const
{
int value = 0;
QMetaObject::invokeMethod(_ledDevice, "getLatchTime", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value));
return value;
}
bool LedDeviceWrapper::enabled() const
{
return _enabled;
}
int LedDeviceWrapper::addToDeviceMap(QString name, LedDeviceCreateFuncType funcPtr)
{
QMutexLocker lock(&_ledDeviceMapLock);
_ledDeviceMap.emplace(name,funcPtr);
return 0;
}
const LedDeviceRegistry& LedDeviceWrapper::getDeviceMap()
{
QMutexLocker lock(&_ledDeviceMapLock);
return _ledDeviceMap;
}
QJsonObject LedDeviceWrapper::getLedDeviceSchemas()
{
// make sure the resources are loaded (they may be left out after static linking)
@@ -115,101 +206,3 @@ QJsonObject LedDeviceWrapper::getLedDeviceSchemas()
return result;
}
int LedDeviceWrapper::addToDeviceMap(QString name, LedDeviceCreateFuncType funcPtr)
{
QMutexLocker lock(&_ledDeviceMapLock);
_ledDeviceMap.emplace(name,funcPtr);
return 0;
}
const LedDeviceRegistry& LedDeviceWrapper::getDeviceMap()
{
QMutexLocker lock(&_ledDeviceMapLock);
return _ledDeviceMap;
}
int LedDeviceWrapper::getLatchTime() const
{
int value = 0;
QMetaObject::invokeMethod(_ledDevice, "getLatchTime", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value));
return value;
}
QString LedDeviceWrapper::getActiveDeviceType() const
{
QString value = 0;
QMetaObject::invokeMethod(_ledDevice, "getActiveDeviceType", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, value));
return value;
}
QString LedDeviceWrapper::getColorOrder() const
{
QString value;
QMetaObject::invokeMethod(_ledDevice, "getColorOrder", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, value));
return value;
}
unsigned int LedDeviceWrapper::getLedCount() const
{
int value = 0;
QMetaObject::invokeMethod(_ledDevice, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value));
return value;
}
bool LedDeviceWrapper::enabled() const
{
return _enabled;
}
void LedDeviceWrapper::handleComponentState(hyperion::Components component, bool state)
{
if(component == hyperion::COMP_LEDDEVICE)
{
if ( state )
{
emit enable();
}
else
{
emit disable();
}
//Get device's state, considering situations where it is not ready
bool deviceState = false;
QMetaObject::invokeMethod(_ledDevice, "componentState", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, deviceState));
_hyperion->setNewComponentState(hyperion::COMP_LEDDEVICE, deviceState);
_enabled = deviceState;
}
}
void LedDeviceWrapper::handleInternalEnableState(bool newState)
{
_hyperion->setNewComponentState(hyperion::COMP_LEDDEVICE, newState);
_enabled = newState;
if (_enabled)
{
_hyperion->update();
}
}
void LedDeviceWrapper::stopDeviceThread()
{
// turns the LEDs off & stop refresh timers
emit stopLedDevice();
// get current thread
QThread* oldThread = _ledDevice->thread();
disconnect(oldThread, nullptr, nullptr, nullptr);
oldThread->quit();
oldThread->wait();
delete oldThread;
disconnect(_ledDevice, nullptr, nullptr, nullptr);
delete _ledDevice;
_ledDevice = nullptr;
}

View File

@@ -33,7 +33,7 @@ public:
///
/// Sets configuration
///
/// @para#endif // LEDEVICETEMPLATE_Hm deviceConfig the json device config
/// @param deviceConfig the json device config
/// @return true if success
bool init(const QJsonObject &deviceConfig) override;

View File

@@ -45,23 +45,16 @@ LedDeviceAtmoOrb::~LedDeviceAtmoOrb()
bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
bool isInitOK {false};
if ( LedDevice::init(deviceConfig) )
{
_multicastGroup = deviceConfig["host"].toString(MULTICAST_GROUP_DEFAULT_ADDRESS);
_multiCastGroupPort = static_cast<quint16>(deviceConfig["port"].toInt(MULTICAST_GROUP_DEFAULT_PORT));
_useOrbSmoothing = deviceConfig["useOrbSmoothing"].toBool(false);
_skipSmoothingDiff = deviceConfig["skipSmoothingDiff"].toInt(0);
QStringList orbIds = QStringUtils::split(deviceConfig["orbIds"].toString().simplified().remove(" "),",", QStringUtils::SplitBehavior::SkipEmptyParts);
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
Debug(_log, "LedCount : %d", this->getLedCount());
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms);
Debug(_log, "LatchTime : %d", this->getLatchTime());
Debug(_log, "MulticastGroup : %s", QSTRING_CSTR(_multicastGroup));
Debug(_log, "MulticastGroupPort: %d", _multiCastGroupPort);
Debug(_log, "Orb ID list : %s", QSTRING_CSTR(deviceConfig["orbIds"].toString()));

View File

@@ -8,21 +8,27 @@
#include <chrono>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#endif
#include <utils/NetUtils.h>
// Constants
namespace {
const bool verbose = false;
const bool verbose3 = false;
// Configuration settings
const char CONFIG_HOST[] = "host";
const char CONFIG_HW_LED_COUNT[] = "hardwareLedCount";
const int COLOLIGHT_BEADS_PER_MODULE = 19;
const int STREAM_DEFAULT_PORT = 8900;
// Cololight discovery service
const int API_DEFAULT_PORT = 8900;
const char DISCOVERY_ADDRESS[] = "255.255.255.255";
const quint16 DISCOVERY_PORT = 12345;
const char DISCOVERY_MESSAGE[] = "Z-SEARCH * \r\n";
@@ -46,6 +52,11 @@ LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig)
, _distance(0)
, _sequenceNumber(1)
{
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
#endif
_packetFixPart.append(reinterpret_cast<const char*>(PACKET_HEADER), sizeof(PACKET_HEADER));
_packetFixPart.append(reinterpret_cast<const char*>(PACKET_SECU), sizeof(PACKET_SECU));
}
@@ -57,22 +68,13 @@ LedDevice* LedDeviceCololight::construct(const QJsonObject& deviceConfig)
bool LedDeviceCololight::init(const QJsonObject& deviceConfig)
{
bool isInitOK = false;
bool isInitOK {false};
_port = API_DEFAULT_PORT;
if (ProviderUdp::init(deviceConfig))
if ( ProviderUdp::init(deviceConfig) )
{
// Initialise LedDevice configuration and execution environment
Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType()));
Debug(_log, "ColorOrder : %s", QSTRING_CSTR(this->getColorOrder()));
Debug(_log, "LatchTime : %d", this->getLatchTime());
if (initLedsConfiguration())
{
initDirectColorCmdTemplate();
isInitOK = true;
}
_hostName = _devConfig[ CONFIG_HOST ].toString();
_port = STREAM_DEFAULT_PORT;
isInitOK = true;
}
return isInitOK;
}
@@ -161,6 +163,27 @@ void LedDeviceCololight::initDirectColorCmdTemplate()
}
}
int LedDeviceCololight::open()
{
int retval = -1;
_isDeviceReady = false;
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
if (ProviderUdp::open() == 0)
{
if (initLedsConfiguration())
{
initDirectColorCmdTemplate();
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
}
return retval;
}
bool LedDeviceCololight::getInfo()
{
bool isCmdOK = false;
@@ -652,10 +675,19 @@ QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/)
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
QString discoveryMethod("ssdp");
QJsonArray deviceList;
#ifdef ENABLE_MDNS
QString discoveryMethod("mDNS");
deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(
MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT
);
#else
QString discoveryMethod("ssdp");
deviceList = discover();
#endif
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
devicesDiscovered.insert("devices", deviceList);
@@ -669,19 +701,16 @@ QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties;
QString hostName = params["host"].toString("");
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
QJsonObject propertiesDetails;
if (!hostName.isEmpty())
_hostName = params[CONFIG_HOST].toString("");
_port = STREAM_DEFAULT_PORT;
Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
QJsonObject deviceConfig;
deviceConfig.insert("host", hostName);
deviceConfig.insert("port", apiPort);
if (ProviderUdp::init(deviceConfig))
if (ProviderUdp::open() == 0)
{
if (getInfo())
{
@@ -717,16 +746,14 @@ void LedDeviceCololight::identify(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QString hostName = params["host"].toString("");
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
_hostName = params[CONFIG_HOST].toString("");
_port = STREAM_DEFAULT_PORT;
if (!hostName.isEmpty())
Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
QJsonObject deviceConfig;
deviceConfig.insert("host", hostName);
deviceConfig.insert("port", apiPort);
if (ProviderUdp::init(deviceConfig))
if (ProviderUdp::open() == 0)
{
if (setStateDirect(false) && setState(true))
{

View File

@@ -161,6 +161,13 @@ protected:
///
bool init(const QJsonObject& deviceConfig) override;
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///

View File

@@ -18,15 +18,20 @@ const int MAX_NUM_LEDS = 10000; // OPC can handle 21845 LEDs - in theory, fadeca
const int OPC_SET_PIXELS = 0; // OPC command codes
const int OPC_SYS_EX = 255; // OPC command codes
const int OPC_HEADER_SIZE = 4; // OPC header size
} //End of constants
// TCP elements
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const char DEFAULT_HOST[] = "127.0.0.1";
const int STREAM_DEFAULT_PORT = 7890;
} //End of constants
LedDeviceFadeCandy::LedDeviceFadeCandy(const QJsonObject& deviceConfig)
: LedDevice(deviceConfig)
, _client(nullptr)
, _host()
, _hostName()
, _port(STREAM_DEFAULT_PORT)
{
}
@@ -43,7 +48,7 @@ LedDevice* LedDeviceFadeCandy::construct(const QJsonObject& deviceConfig)
bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig)
{
bool isInitOK = false;
bool isInitOK {false};
if (LedDevice::init(deviceConfig))
{
@@ -55,11 +60,11 @@ bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig)
}
else
{
_host = deviceConfig["host"].toString("127.0.0.1");
_port = deviceConfig["port"].toInt(STREAM_DEFAULT_PORT);
_hostName = _devConfig[ CONFIG_HOST ].toString(DEFAULT_HOST);
_port = deviceConfig[CONFIG_PORT].toInt(STREAM_DEFAULT_PORT);
//If host not configured the init fails
if (_host.isEmpty())
if (_hostName.isEmpty())
{
this->setInError("No target hostname nor IP defined");
}
@@ -90,10 +95,7 @@ bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig)
_opc_data[1] = OPC_SET_PIXELS;
qToBigEndian<quint16>(static_cast<quint16>(_ledRGBCount), _opc_data.data() + 2);
if (initNetwork())
{
isInitOK = true;
}
isInitOK = true;
}
}
}
@@ -102,12 +104,11 @@ bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig)
bool LedDeviceFadeCandy::initNetwork()
{
bool isInitOK = false;
bool isInitOK = true;
if (_client == nullptr)
{
_client = new QTcpSocket(this);
isInitOK = true;
}
return isInitOK;
}
@@ -118,17 +119,20 @@ int LedDeviceFadeCandy::open()
QString errortext;
_isDeviceReady = false;
if (initNetwork())
{
// Try to open the LedDevice
if (!tryConnect())
{
errortext = QString("Failed to open device.");
this->setInError(errortext);
}
else
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
if (!tryConnect())
{
errortext = QString("Failed to open device.");
this->setInError(errortext);
}
else
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
return retval;
}
@@ -162,10 +166,10 @@ bool LedDeviceFadeCandy::tryConnect()
if (_client != nullptr)
{
if (_client->state() == QAbstractSocket::UnconnectedState) {
_client->connectToHost(_host, static_cast<quint16>(_port));
_client->connectToHost(_hostName, static_cast<quint16>(_port));
if (_client->waitForConnected(CONNECT_TIMEOUT.count()))
{
Info(_log, "fadecandy/opc: connected to %s:%d on channel %d", QSTRING_CSTR(_host), _port, _channel);
Info(_log, "fadecandy/opc: connected to %s:%d on channel %d", QSTRING_CSTR(_hostName), _port, _channel);
if (_setFcConfig)
{
sendFadeCandyConfiguration();

View File

@@ -130,7 +130,7 @@ private:
void sendFadeCandyConfiguration();
QTcpSocket* _client;
QString _host;
QString _hostName;
int _port;
int _channel;
QByteArray _opc_data;

View File

@@ -1,16 +1,23 @@
// Local-Hyperion includes
#include "LedDeviceNanoleaf.h"
#include <ssdp/SSDPDiscover.h>
#include <utils/QStringUtils.h>
//std includes
#include <sstream>
#include <iomanip>
// Qt includes
#include <QNetworkReply>
#include <QtEndian>
//std includes
#include <sstream>
#include <iomanip>
#include <ssdp/SSDPDiscover.h>
#include <utils/QStringUtils.h>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#endif
#include <utils/NetUtils.h>
// Constants
namespace {
@@ -18,7 +25,7 @@ const bool verbose = false;
const bool verbose3 = false;
// Configuration settings
const char CONFIG_ADDRESS[] = "host";
const char CONFIG_HOST[] = "host";
const char CONFIG_AUTH_TOKEN[] = "token";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_BRIGHTNESS[] = "brightness";
@@ -115,6 +122,10 @@ LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
, _extControlVersion(EXTCTRLVER_V2)
, _panelLedCount(0)
{
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
#endif
}
LedDevice* LedDeviceNanoleaf::construct(const QJsonObject& deviceConfig)
@@ -130,6 +141,8 @@ LedDeviceNanoleaf::~LedDeviceNanoleaf()
bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
{
bool isInitOK {false};
// Overwrite non supported/required features
setLatchTime(0);
setRewriteTime(0);
@@ -141,21 +154,19 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
DebugIf(verbose,_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData());
bool isInitOK = false;
if (LedDevice::init(deviceConfig))
if ( ProviderUdp::init(deviceConfig) )
{
int configuredLedCount = this->getLedCount();
Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType()));
Debug(_log, "LedCount : %d", configuredLedCount);
Debug(_log, "ColorOrder : %s", QSTRING_CSTR(this->getColorOrder()));
Debug(_log, "RewriteTime : %d", this->getRewriteTime());
Debug(_log, "LatchTime : %d", this->getLatchTime());
//Set hostname as per configuration and default port
_hostName = deviceConfig[CONFIG_HOST].toString();
_port = STREAM_CONTROL_DEFAULT_PORT;
_apiPort = API_DEFAULT_PORT;
_authToken = deviceConfig[CONFIG_AUTH_TOKEN].toString();
_isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE);
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX);
Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName) );
Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState);
Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
Debug(_log, "Set Brightness to : %d", _brightness);
@@ -178,37 +189,9 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
{
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toInt() == 0;
}
_startPos = deviceConfig[CONFIG_PANEL_START_POS].toInt(0);
//Set hostname as per configuration and_defaultHost default port
_hostName = deviceConfig[CONFIG_ADDRESS].toString();
_apiPort = API_DEFAULT_PORT;
_authToken = deviceConfig[CONFIG_AUTH_TOKEN].toString();
//If host not configured the init failed
if (_hostName.isEmpty())
{
this->setInError("No target hostname nor IP defined");
isInitOK = false;
}
else
{
if (initRestAPI(_hostName, _apiPort, _authToken))
{
// Read LedDevice configuration and validate against device configuration
if (initLedsConfiguration())
{
// Set UDP streaming host and port
_devConfig["host"] = _hostName;
_devConfig["port"] = STREAM_CONTROL_DEFAULT_PORT;
isInitOK = ProviderUdp::init(_devConfig);
Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName));
Debug(_log, "Port : %d", _port);
}
}
}
isInitOK = true;
}
return isInitOK;
}
@@ -358,18 +341,17 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
return isInitOK;
}
bool LedDeviceNanoleaf::initRestAPI(const QString& hostname, int port, const QString& token)
bool LedDeviceNanoleaf::openRestAPI()
{
bool isInitOK = false;
bool isInitOK {true};
if (_restApi == nullptr)
{
_restApi = new ProviderRestApi(hostname, port);
_restApi = new ProviderRestApi(_address.toString(), _apiPort);
_restApi->setLogger(_log);
//Base-path is api-path + authentication token
_restApi->setBasePath(QString(API_BASE_PATH).arg(token));
isInitOK = true;
_restApi->setBasePath(QString(API_BASE_PATH).arg(_authToken));
}
return isInitOK;
}
@@ -379,13 +361,27 @@ int LedDeviceNanoleaf::open()
int retval = -1;
_isDeviceReady = false;
if (ProviderUdp::open() == 0)
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
if ( openRestAPI() )
{
// Read LedDevice configuration and validate against device configuration
if (initLedsConfiguration())
{
if (ProviderUdp::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
}
else
{
_restApi->setHost(_address.toString());
_restApi->setPort(_apiPort);
}
}
return retval;
}
@@ -414,13 +410,23 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
QString discoveryMethod("ssdp");
QJsonArray deviceList;
#ifdef ENABLE_MDNS
QString discoveryMethod("mDNS");
deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(
MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT
);
#else
QString discoveryMethod("ssdp");
deviceList = discover();
#endif
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
@@ -431,27 +437,29 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties;
// Get Nanoleaf device properties
QString hostName = params["host"].toString("");
_hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;
_authToken = params["token"].toString("");
if (!hostName.isEmpty())
Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
QString authToken = params["token"].toString("");
QString filter = params["filter"].toString("");
initRestAPI(hostName, API_DEFAULT_PORT, authToken);
_restApi->setPath(filter);
// Perform request
httpResponse response = _restApi->get();
if (response.error())
if ( openRestAPI() )
{
Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
QString filter = params["filter"].toString("");
_restApi->setPath(filter);
// Perform request
httpResponse response = _restApi->get();
if (response.error())
{
Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
properties.insert("properties", response.getBody().object());
}
properties.insert("properties", response.getBody().object());
DebugIf(verbose,_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
}
return properties;
}
@@ -460,19 +468,24 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QString hostName = params["host"].toString("");
if (!hostName.isEmpty())
_hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
_authToken = params["token"].toString("");
Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
QString authToken = params["token"].toString("");
initRestAPI(hostName, API_DEFAULT_PORT, authToken);
_restApi->setPath("identify");
// Perform request
httpResponse response = _restApi->put();
if (response.error())
if ( openRestAPI() )
{
Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
_restApi->setPath("identify");
// Perform request
httpResponse response = _restApi->put();
if (response.error())
{
Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
}
}
}
@@ -662,7 +675,7 @@ bool LedDeviceNanoleaf::restoreState()
Warning (_log, "%s restoring effect failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
} else {
Warning (_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Turning device off", QSTRING_CSTR(_activeDeviceType));
Warning (_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Device is switched off", QSTRING_CSTR(_activeDeviceType));
_originalIsOn = false;
}
break;

View File

@@ -150,13 +150,9 @@ private:
///
/// @brief Initialise the access to the REST-API wrapper
///
/// @param[in] host
/// @param[in] port
/// @param[in] authentication token
///
/// @return True, if success
///
bool initRestAPI(const QString& hostname, int port, const QString& token);
bool openRestAPI();
///
/// @brief Get Nanoleaf device details and configuration
@@ -188,9 +184,7 @@ private:
///REST-API wrapper
ProviderRestApi* _restApi;
QString _hostName;
int _apiPort;
int _apiPort;
QString _authToken;
bool _topDown;

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@
// Qt includes
#include <QNetworkAccessManager>
#include <QEventLoop>
#include <QNetworkReply>
#include <QtCore/qmath.h>
#include <QStringList>
@@ -85,7 +84,7 @@ struct CiColor
///
/// @return color point
///
static CiColor rgbToCiColor(double red, double green, double blue, const CiColorTriangle &colorSpace);
static CiColor rgbToCiColor(double red, double green, double blue, const CiColorTriangle& colorSpace, bool candyGamma);
///
/// @param p the color point to check
@@ -149,8 +148,9 @@ public:
/// @param bridge the bridge
/// @param id the light id
///
PhilipsHueLight(Logger* log, unsigned int id, QJsonObject values, unsigned int ledidx);
~PhilipsHueLight();
PhilipsHueLight(Logger* log, int id, QJsonObject values, int ledidx,
int onBlackTimeToPowerOff,
int onBlackTimeToPowerOn);
///
/// @param on
@@ -167,11 +167,12 @@ public:
///
void setColor(const CiColor& color);
unsigned int getId() const;
int getId() const;
bool getOnOffState() const;
int getTransitionTime() const;
CiColor getColor() const;
bool hasColor() const;
///
/// @return the color space of the light determined by the model id reported by the bridge.
@@ -180,15 +181,21 @@ public:
void saveOriginalState(const QJsonObject& values);
QString getOriginalState() const;
bool isBusy();
bool isBlack(bool isBlack);
bool isWhite(bool isWhite);
void setBlack();
void blackScreenTriggered();
private:
Logger* _log;
/// light id
unsigned int _id;
unsigned int _ledidx;
int _id;
int _ledidx;
bool _on;
int _transitionTime;
CiColor _color;
bool _hasColor;
/// darkes blue color in hue lamp GAMUT = black
CiColor _colorBlack;
/// The model id of the hue lamp which is used to determine the color space.
@@ -201,6 +208,12 @@ private:
QString _originalState;
CiColor _originalColor;
qint64 _lastSendColorTime;
qint64 _lastBlackTime;
qint64 _lastWhiteTime;
bool _blackScreenTriggered;
qint64 _onBlackTimeToPowerOff;
qint64 _onBlackTimeToPowerOn;
};
class LedDevicePhilipsHueBridge : public ProviderUdpSSL
@@ -215,13 +228,9 @@ public:
///
/// @brief Initialise the access to the REST-API wrapper
///
/// @param[in] host
/// @param[in] port
/// @param[in] authentication token
///
/// @return True, if success
///
bool initRestAPI(const QString &hostname, int port, const QString &token );
bool openRestAPI();
///
/// @brief Perform a REST-API GET
@@ -238,20 +247,18 @@ public:
/// @param route the route of the POST request.
/// @param content the content of the POST request.
///
QJsonDocument post(const QString& route, const QString& content);
QJsonDocument put(const QString& route, const QString& content, bool supressError = false);
QJsonDocument getLightState(unsigned int lightId);
void setLightState(unsigned int lightId = 0, const QString &state = "");
QJsonDocument getLightState( int lightId);
void setLightState( int lightId = 0, const QString &state = "");
QMap<quint16,QJsonObject> getLightMap() const;
QMap<int,QJsonObject> getLightMap() const;
QMap<quint16,QJsonObject> getGroupMap() const;
QString getGroupName(quint16 groupId = 0) const;
QJsonArray getGroupLights(quint16 groupId = 0) const;
QMap<int,QJsonObject> getGroupMap() const;
QString getGroupName(int groupId = 0) const;
QJsonArray getGroupLights(int groupId = 0) const;
protected:
@@ -281,23 +288,66 @@ protected:
/// @brief Check, if Hue API response indicate error
///
/// @param[in] response from Hue-Bridge in JSON-format
/// @param[in] suppressError Treat an error as a warning
///
/// return True, Hue Bridge reports error
///
bool checkApiError(const QJsonDocument &response );
bool checkApiError(const QJsonDocument& response, bool supressError = false);
///
/// @brief Discover devices of this type available (for configuration).
/// @note Mainly used for network devices. Allows to find devices, e.g. via ssdp, mDNS or cloud ways.
///
/// @param[in] params Parameters used to overwrite discovery default behaviour
///
/// @return A JSON structure holding a list of devices found
///
QJsonObject discover(const QJsonObject& params) override;
///
/// @brief Get the Hue Bridge device's resource properties
///
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP",
/// "port" : port
/// "user" : "username",
/// "filter": "resource to query", root "/" is used, if empty
/// }
///@endcode
///
/// @param[in] params Parameters to query device
/// @return A JSON structure holding the device's properties
///
QJsonObject getProperties(const QJsonObject& params) override;
///
/// @brief Add an authorization/client-key to the Hue Bridge device
///
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP",
/// "port" : port
/// }
///@endcode
///
/// @param[in] params Parameters to query device
/// @return A JSON structure holding the authorization keys
///
QJsonObject addAuthorization(const QJsonObject& params) override;
///REST-API wrapper
ProviderRestApi* _restApi;
/// Ip address of the bridge
QString _hostname;
int _apiPort;
/// User name for the API ("newdeveloper")
QString _username;
QString _authToken;
bool _useHueEntertainmentAPI;
QJsonDocument getGroupState( unsigned int groupId );
QJsonDocument setGroupState( unsigned int groupId, bool state);
QJsonDocument getGroupState( int groupId );
QJsonDocument setGroupState( int groupId, bool state);
bool isStreamOwner(const QString &streamOwner) const;
bool initMaps();
@@ -308,6 +358,14 @@ protected:
private:
///
/// @brief Discover Philips-Hue devices available (for configuration).
/// Philips-Hue specific ssdp discovery
///
/// @return A JSON structure holding a list of devices found
///
QJsonArray discover();
QJsonDocument getAllBridgeInfos();
void setBridgeConfig( const QJsonDocument &doc );
void setLightsMap( const QJsonDocument &doc );
@@ -324,8 +382,8 @@ private:
bool _isHueEntertainmentReady;
QMap<quint16,QJsonObject> _lightsMap;
QMap<quint16,QJsonObject> _groupsMap;
QMap<int,QJsonObject> _lightsMap;
QMap<int,QJsonObject> _groupsMap;
};
/**
@@ -360,34 +418,6 @@ public:
/// @return LedDevice constructed
static LedDevice* construct(const QJsonObject &deviceConfig);
///
/// @brief Discover devices of this type available (for configuration).
/// @note Mainly used for network devices. Allows to find devices, e.g. via ssdp, mDNS or cloud ways.
///
/// @param[in] params Parameters used to overwrite discovery default behaviour
///
/// @return A JSON structure holding a list of devices found
///
QJsonObject discover(const QJsonObject& params) override;
///
/// @brief Get the Hue Bridge device's resource properties
///
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP
/// "port" : port
/// "user" : "username",
/// "filter": "resource to query", root "/" is used, if empty
/// }
///@endcode
///
/// @param[in] params Parameters to query device
/// @return A JSON structure holding the device's properties
///
QJsonObject getProperties(const QJsonObject& params) override;
///
/// @brief Send an update to the device to identify it.
///
@@ -412,7 +442,7 @@ public:
///
unsigned int getLightsCount() const { return _lightsCount; }
void setOnOffState(PhilipsHueLight& light, bool on);
void setOnOffState(PhilipsHueLight& light, bool on, bool force = false);
void setTransitionTime(PhilipsHueLight& light);
void setColor(PhilipsHueLight& light, CiColor& color);
void setState(PhilipsHueLight& light, bool on, const CiColor& color);
@@ -443,13 +473,6 @@ protected:
///
int open() override;
///
/// @brief Closes the output device.
///
/// @return Zero on success (i.e. device is closed), else negative
///
int close() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///
@@ -465,7 +488,7 @@ protected:
/// Depending on the configuration, the device may store its current state for later restore.
/// @see powerOn, storeState
///
/// @return True if success
/// @return True, if success
///
bool switchOn() override;
@@ -518,28 +541,17 @@ protected:
///
bool restoreState() override;
private slots:
void noSignalTimeout();
private:
bool initLeds();
///
/// @brief Creates new PhilipsHueLight(s) based on user lightid with bridge feedback
///
/// @param map Map of lightid/value pairs of bridge
///
void newLights(QMap<quint16, QJsonObject> map);
bool setLights();
/// creates new PhilipsHueLight(s) based on user lightid with bridge feedback
///
/// @param map Map of lightid/value pairs of bridge
///
bool updateLights(const QMap<quint16, QJsonObject> &map);
bool updateLights(const QMap<int, QJsonObject> &map);
///
/// @brief Set the number of LEDs supported by the device.
@@ -554,13 +566,9 @@ private:
bool startStream();
bool stopStream();
void writeStream();
void writeStream(bool flush = false);
int writeSingleLights(const std::vector<ColorRgb>& ledValues);
bool noSignalDetection();
void stopBlackTimeoutTimer();
QByteArray prepareStreamData() const;
///
@@ -574,32 +582,28 @@ private:
bool _isInitLeds;
/// Array of the light ids.
std::vector<quint16> _lightIds;
std::vector<int> _lightIds;
/// Array to save the lamps.
std::vector<PhilipsHueLight> _lights;
unsigned int _lightsCount;
quint16 _groupId;
int _lightsCount;
int _groupId;
double _brightnessMin;
double _brightnessMax;
bool _allLightsBlack;
QTimer* _blackLightsTimer;
int _blackLightsTimeout;
double _brightnessThreshold;
int _handshake_timeout_min;
int _handshake_timeout_max;
int _ssl_read_timeout;
double _blackLevel;
int _onBlackTimeToPowerOff;
int _onBlackTimeToPowerOn;
bool _candyGamma;
// TODO: Check what is the correct class
uint32_t _handshake_timeout_min;
uint32_t _handshake_timeout_max;
bool _stopConnection;
QString _groupName;
QString _streamOwner;
int start_retry_left;
int stop_retry_left;
qint64 _lastConfirm;
int _lastId;
bool _groupStreamState;
};

View File

@@ -65,13 +65,6 @@ bool LedDeviceRazer::init(const QJsonObject& deviceConfig)
// Initialise sub-class
if (LedDevice::init(deviceConfig))
{
// Initialise LedDevice configuration and execution environment
uint configuredLedCount = this->getLedCount();
Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType()));
Debug(_log, "LedCount : %u", configuredLedCount);
Debug(_log, "ColorOrder : %s", QSTRING_CSTR(this->getColorOrder()));
Debug(_log, "LatchTime : %d", this->getLatchTime());
Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms);
//Razer Chroma SDK allows localhost connection only
_hostname = API_DEFAULT_HOST;
@@ -86,6 +79,7 @@ bool LedDeviceRazer::init(const QJsonObject& deviceConfig)
Debug(_log, "Razer Device : %s", QSTRING_CSTR(_razerDeviceType));
Debug(_log, "Single Color : %d", _isSingleColor);
int configuredLedCount = this->getLedCount();
if (resolveDeviceProperties(_razerDeviceType))
{
if (_isSingleColor && configuredLedCount > 1)
@@ -125,6 +119,8 @@ bool LedDeviceRazer::initRestAPI(const QString& hostname, int port)
if (_restApi == nullptr)
{
_restApi = new ProviderRestApi(hostname, port);
_restApi->setLogger(_log);
_restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
isInitOK = true;

View File

@@ -1,6 +1,13 @@
#include "LedDeviceTpm2net.h"
#include <utils/NetUtils.h>
// Constants
namespace {
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const ushort TPM2_DEFAULT_PORT = 65506;
}
LedDeviceTpm2net::LedDeviceTpm2net(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig)
@@ -20,13 +27,14 @@ LedDevice* LedDeviceTpm2net::construct(const QJsonObject &deviceConfig)
bool LedDeviceTpm2net::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
_port = TPM2_DEFAULT_PORT;
bool isInitOK {false};
// Initialise sub-class
if ( ProviderUdp::init(deviceConfig) )
{
_hostName = _devConfig[ CONFIG_HOST ].toString();
_port = deviceConfig[CONFIG_PORT].toInt(TPM2_DEFAULT_PORT);
_tpm2_max = deviceConfig["max-packet"].toInt(170);
_tpm2ByteCount = 3 * _ledCount;
_tpm2TotalPackets = (_tpm2ByteCount / _tpm2_max) + ((_tpm2ByteCount % _tpm2_max) != 0);
@@ -38,6 +46,23 @@ bool LedDeviceTpm2net::init(const QJsonObject &deviceConfig)
return isInitOK;
}
int LedDeviceTpm2net::open()
{
int retval = -1;
_isDeviceReady = false;
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
if (ProviderUdp::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
return retval;
}
int LedDeviceTpm2net::write(const std::vector<ColorRgb> &ledValues)
{
int retVal = 0;

View File

@@ -41,6 +41,13 @@ private:
///
bool init(const QJsonObject &deviceConfig) override;
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///

View File

@@ -9,7 +9,16 @@
#include <QHostInfo>
#include <utils/NetUtils.h>
// Constants
namespace {
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const ushort ARTNET_DEFAULT_PORT = 6454;
}
LedDeviceUdpArtNet::LedDeviceUdpArtNet(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig)
@@ -23,13 +32,14 @@ LedDevice* LedDeviceUdpArtNet::construct(const QJsonObject &deviceConfig)
bool LedDeviceUdpArtNet::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
_port = ARTNET_DEFAULT_PORT;
bool isInitOK {false};
// Initialise sub-class
if ( ProviderUdp::init(deviceConfig) )
{
_hostName = _devConfig[ CONFIG_HOST ].toString();
_port = deviceConfig[CONFIG_PORT].toInt(ARTNET_DEFAULT_PORT);
_artnet_universe = deviceConfig["universe"].toInt(1);
_artnet_channelsPerFixture = deviceConfig["channelsPerFixture"].toInt(3);
@@ -38,6 +48,23 @@ bool LedDeviceUdpArtNet::init(const QJsonObject &deviceConfig)
return isInitOK;
}
int LedDeviceUdpArtNet::open()
{
int retval = -1;
_isDeviceReady = false;
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
if (ProviderUdp::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
return retval;
}
// populates the headers
void LedDeviceUdpArtNet::prepare(unsigned this_universe, unsigned this_sequence, unsigned this_dmxChannelCount)
{

View File

@@ -69,6 +69,13 @@ private:
///
bool init(const QJsonObject &deviceConfig) override;
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///

View File

@@ -0,0 +1,163 @@
#include "LedDeviceUdpDdp.h"
#include <QtEndian>
#include <utils/NetUtils.h>
// DDP header format
// header is 10 bytes (14 if TIME flag used)
struct ddp_hdr_struct {
uint8_t flags1;
uint8_t flags2;
uint8_t type;
uint8_t id;
uint32_t offset;
uint16_t len;
};
// Constants
namespace {
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const ushort DDP_DEFAULT_PORT = 4048;
namespace DDP {
// DDP protocol header definitions
struct Header {
uint8_t flags1;
uint8_t flags2;
uint8_t type;
uint8_t id;
uint8_t offset[4];
uint8_t len[2];
};
static constexpr int HEADER_LEN = (sizeof(struct Header)); // header is 10 bytes (14 if TIME flag used)
static constexpr int MAX_LEDS = 480;
static constexpr int CHANNELS_PER_PACKET = MAX_LEDS*3;
namespace flags1 {
static constexpr auto VER_MASK = 0xc0;
static constexpr auto VER1 = 0x40;
static constexpr auto PUSH = 0x01;
static constexpr auto QUERY = 0x02;
static constexpr auto REPLY = 0x04;
static constexpr auto STORAGE = 0x08;
static constexpr auto TIME = 0x10;
} // namespace flags1
namespace id {
static constexpr auto DISPLAY = 1;
static constexpr auto CONTROL = 246;
static constexpr auto CONFIG = 250;
static constexpr auto STATUS = 251;
static constexpr auto DMXTRANSIT = 254;
static constexpr auto ALLDEVICES = 255;
} // namespace id
} // namespace DDP
} //End of constants
LedDeviceUdpDdp::LedDeviceUdpDdp(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig)
,_packageSequenceNumber(0)
{
}
LedDevice* LedDeviceUdpDdp::construct(const QJsonObject &deviceConfig)
{
return new LedDeviceUdpDdp(deviceConfig);
}
bool LedDeviceUdpDdp::init(const QJsonObject &deviceConfig)
{
bool isInitOK {false};
if ( ProviderUdp::init(deviceConfig) )
{
_hostName = _devConfig[ CONFIG_HOST ].toString();
_port = deviceConfig[CONFIG_PORT].toInt(DDP_DEFAULT_PORT);
Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName) );
Debug(_log, "Port : %d", _port );
_ddpData.resize(DDP::HEADER_LEN + DDP::CHANNELS_PER_PACKET);
_ddpData[0] = DDP::flags1::VER1; // flags1
_ddpData[1] = 0; // flags2
_ddpData[2] = 1; // type
_ddpData[3] = DDP::id::DISPLAY; // id
isInitOK = true;
}
return isInitOK;
}
int LedDeviceUdpDdp::open()
{
int retval = -1;
_isDeviceReady = false;
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
if (ProviderUdp::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
return retval;
}
int LedDeviceUdpDdp::write(const std::vector<ColorRgb> &ledValues)
{
int rc {0};
int channelCount = static_cast<int>(_ledCount) * 3; // 1 channel for every R,G,B value
int packetCount = ((channelCount-1) / DDP::CHANNELS_PER_PACKET) + 1;
int channel = 0;
_ddpData[0] = DDP::flags1::VER1;
for (int currentPacket = 0; currentPacket < packetCount; currentPacket++)
{
if (_packageSequenceNumber > 15)
{
_packageSequenceNumber = 0;
}
int packetSize = DDP::CHANNELS_PER_PACKET;
if (currentPacket == (packetCount - 1))
{
// last packet, set the push flag
/*0*/_ddpData[0] = DDP::flags1::VER1 | DDP::flags1::PUSH;
if (channelCount % DDP::CHANNELS_PER_PACKET != 0)
{
packetSize = channelCount % DDP::CHANNELS_PER_PACKET;
}
}
/*1*/_ddpData[1] = static_cast<char>(_packageSequenceNumber++ & 0x0F);
/*4*/qToBigEndian<quint32>(static_cast<quint32>(channel), _ddpData.data() + 4);
/*8*/qToBigEndian<quint16>(static_cast<quint16>(packetSize), _ddpData.data() + 8);
_ddpData.replace(DDP::HEADER_LEN, channel, reinterpret_cast<const char*>(ledValues.data())+channel, packetSize);
_ddpData.resize(DDP::HEADER_LEN + packetSize);
rc = writeBytes(_ddpData);
if (rc != 0)
{
break;
}
channel += packetSize;
}
return rc;
}

View File

@@ -0,0 +1,62 @@
#ifndef LEDEVICEUDPDDP_H
#define LEDEVICEUDPDDP_H
// hyperion includes
#include "ProviderUdp.h"
///
/// Implementation of the LedDevice interface for sending LED colors via UDP and the Distributed Display Protocol (DDP)
/// http://www.3waylabs.com/ddp/#Data%20Types
///
class LedDeviceUdpDdp : public virtual ProviderUdp
{
public:
///
/// @brief Constructs a LED-device fed via DDP
///
/// @param deviceConfig Device's configuration as JSON-Object
///
explicit LedDeviceUdpDdp(const QJsonObject &deviceConfig);
///
/// @brief Constructs the LED-device
///
/// @param[in] deviceConfig Device's configuration as JSON-Object
/// @return LedDevice constructed
///
static LedDevice* construct(const QJsonObject &deviceConfig);
protected:
///
/// @brief Initialise the device's configuration
///
/// @param[in] deviceConfig the JSON device configuration
/// @return True, if success
///
bool init(const QJsonObject &deviceConfig) override;
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///
/// @param[in] ledValues The RGB-color per LED
/// @return Zero on success, else negative
///
int write(const std::vector<ColorRgb> & ledValues) override;
private:
QByteArray _ddpData;
int _packageSequenceNumber;
};
#endif // LEDEVICEUDPDDP_H

View File

@@ -8,6 +8,13 @@
// hyperion local includes
#include "LedDeviceUdpE131.h"
#include <utils/NetUtils.h>
// Constants
namespace {
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const ushort E131_DEFAULT_PORT = 5568;
@@ -23,6 +30,7 @@ const uint32_t VECTOR_E131_DATA_PACKET = 0x00000002;
//#define E131_NETWORK_DATA_LOSS_TIMEOUT 2500 // milli econds
//#define E131_DISCOVERY_UNIVERSE 64214
const int DMX_MAX = 512; // 512 usable slots
}
LedDeviceUdpE131::LedDeviceUdpE131(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig)
@@ -36,13 +44,14 @@ LedDevice* LedDeviceUdpE131::construct(const QJsonObject &deviceConfig)
bool LedDeviceUdpE131::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
_port = E131_DEFAULT_PORT;
bool isInitOK {false};
// Initialise sub-class
if ( ProviderUdp::init(deviceConfig) )
{
_hostName = _devConfig[ CONFIG_HOST ].toString();
_port = deviceConfig[CONFIG_PORT].toInt(E131_DEFAULT_PORT);
_e131_universe = deviceConfig["universe"].toInt(1);
_e131_source_name = deviceConfig["source-name"].toString("hyperion on "+QHostInfo::localHostName());
QString _json_cid = deviceConfig["cid"].toString("");
@@ -70,6 +79,23 @@ bool LedDeviceUdpE131::init(const QJsonObject &deviceConfig)
return isInitOK;
}
int LedDeviceUdpE131::open()
{
int retval = -1;
_isDeviceReady = false;
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
if (ProviderUdp::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
return retval;
}
// populates the headers
void LedDeviceUdpE131::prepare(unsigned this_universe, unsigned this_dmxChannelCount)
{

View File

@@ -114,6 +114,13 @@ private:
///
bool init(const QJsonObject &deviceConfig) override;
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///

View File

@@ -1,8 +1,12 @@
#include "LedDeviceUdpH801.h"
#include <utils/NetUtils.h>
// Constants
namespace {
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const ushort H801_DEFAULT_PORT = 30977;
const char H801_DEFAULT_HOST[] = "255.255.255.255";
@@ -20,16 +24,17 @@ LedDevice* LedDeviceUdpH801::construct(const QJsonObject &deviceConfig)
bool LedDeviceUdpH801::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
bool isInitOK {false};
/* The H801 port is fixed */
_latchTime_ms = 10;
_port = H801_DEFAULT_PORT;
_defaultHost = H801_DEFAULT_HOST;
// Initialise sub-class
if ( ProviderUdp::init(deviceConfig) )
{
_hostName = _devConfig[ CONFIG_HOST ].toString(H801_DEFAULT_HOST);
_port = deviceConfig[CONFIG_PORT].toInt(H801_DEFAULT_PORT);
_ids.clear();
QJsonArray lArray = deviceConfig["lightIds"].toArray();
for (int i = 0; i < lArray.size(); i++)
@@ -47,14 +52,28 @@ bool LedDeviceUdpH801::init(const QJsonObject &deviceConfig)
_message[_prefix_size + _colors + i * _id_size + 1] = (_ids[i] >> 0x08) & 0xFF;
_message[_prefix_size + _colors + i * _id_size + 2] = (_ids[i] >> 0x10) & 0xFF;
}
Debug(_log, "H801 using %s:%d", _address.toString().toStdString().c_str(), _port);
isInitOK = true;
}
return isInitOK;
}
int LedDeviceUdpH801::open()
{
int retval = -1;
_isDeviceReady = false;
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
if (ProviderUdp::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
return retval;
}
int LedDeviceUdpH801::write(const std::vector<ColorRgb> &ledValues)
{
ColorRgb color = ledValues[0];

View File

@@ -37,6 +37,13 @@ private:
///
bool init(const QJsonObject &deviceConfig) override;
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///

View File

@@ -1,10 +1,14 @@
#include "LedDeviceUdpRaw.h"
#include <utils/NetUtils.h>
// Constants
namespace {
const bool verbose = false;
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const ushort RAW_DEFAULT_PORT=5568;
const int UDP_MAX_LED_NUM = 490;
@@ -22,33 +26,46 @@ LedDevice* LedDeviceUdpRaw::construct(const QJsonObject &deviceConfig)
bool LedDeviceUdpRaw::init(const QJsonObject &deviceConfig)
{
_port = RAW_DEFAULT_PORT;
bool isInitOK {false};
bool isInitOK = false;
if ( LedDevice::init(deviceConfig) )
if ( ProviderUdp::init(deviceConfig) )
{
// Initialise LedDevice configuration and execution environment
int configuredLedCount = this->getLedCount();
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
Debug(_log, "LedCount : %d", configuredLedCount);
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
Debug(_log, "LatchTime : %d", this->getLatchTime());
if (configuredLedCount > UDP_MAX_LED_NUM)
if (this->getLedCount() > UDP_MAX_LED_NUM)
{
QString errorReason = QString("Device type %1 can only be run with maximum %2 LEDs!").arg(this->getActiveDeviceType()).arg(UDP_MAX_LED_NUM);
QString errorReason = QString("Device type %1 can only be run with maximum %2 LEDs for streaming protocol = UDP-RAW!").arg(this->getActiveDeviceType()).arg(UDP_MAX_LED_NUM);
this->setInError ( errorReason );
isInitOK = false;
}
else
{
// Initialise sub-class
isInitOK = ProviderUdp::init(deviceConfig);
_hostName = deviceConfig[ CONFIG_HOST ].toString();
_port = deviceConfig[CONFIG_PORT].toInt(RAW_DEFAULT_PORT);
Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName) );
Debug(_log, "Port : %d", _port );
isInitOK = true;
}
}
return isInitOK;
}
int LedDeviceUdpRaw::open()
{
int retval = -1;
_isDeviceReady = false;
if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
{
if (ProviderUdp::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
return retval;
}
int LedDeviceUdpRaw::write(const std::vector<ColorRgb> &ledValues)
{
const uint8_t * dataPtr = reinterpret_cast<const uint8_t *>(ledValues.data());
@@ -59,8 +76,11 @@ int LedDeviceUdpRaw::write(const std::vector<ColorRgb> &ledValues)
QJsonObject LedDeviceUdpRaw::getProperties(const QJsonObject& params)
{
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
QJsonObject properties;
Info(_log, "Get properties for %s", QSTRING_CSTR(_activeDeviceType));
QJsonObject propertiesDetails;
propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM);

View File

@@ -7,7 +7,7 @@
///
/// Implementation of the LedDevice interface for sending LED colors via UDP
///
class LedDeviceUdpRaw : public ProviderUdp
class LedDeviceUdpRaw : public virtual ProviderUdp
{
public:
@@ -44,6 +44,13 @@ protected:
///
bool init(const QJsonObject &deviceConfig) override;
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///

View File

@@ -1,11 +1,18 @@
// Local-Hyperion includes
#include "LedDeviceWled.h"
#include <chrono>
#include <utils/QStringUtils.h>
#include <utils/WaitTime.h>
#include <QThread>
#include <chrono>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#endif
#include <utils/NetUtils.h>
#include <utils/version.hpp>
// Constants
namespace {
@@ -13,16 +20,22 @@ namespace {
const bool verbose = false;
// Configuration settings
const char CONFIG_ADDRESS[] = "host";
const char CONFIG_HOST[] = "host";
const char CONFIG_STREAM_PROTOCOL[] = "streamProtocol";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_BRIGHTNESS[] = "brightness";
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
const char CONFIG_SYNC_OVERWRITE[] = "overwriteSync";
// UDP elements
const quint16 STREAM_DEFAULT_PORT = 19446;
const char DEFAULT_STREAM_PROTOCOL[] = "DDP";
// UDP-RAW
const int UDP_STREAM_DEFAULT_PORT = 19446;
const int UDP_MAX_LED_NUM = 490;
// DDP
const char WLED_VERSION_DDP[] = "0.11.0";
// WLED JSON-API elements
const int API_DEFAULT_PORT = -1; //Use default port per communication scheme
@@ -46,7 +59,7 @@ constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
} //End of constants
LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig)
: ProviderUdp(deviceConfig), LedDeviceUdpDdp(deviceConfig), LedDeviceUdpRaw(deviceConfig)
,_restApi(nullptr)
,_apiPort(API_DEFAULT_PORT)
,_isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE)
@@ -54,7 +67,12 @@ LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig)
,_isSyncOverwrite(DEFAULT_IS_SYNC_OVERWRITE)
,_originalStateUdpnSend(false)
,_originalStateUdpnRecv(true)
,_isStreamDDP(true)
{
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
#endif
}
LedDeviceWled::~LedDeviceWled()
@@ -70,25 +88,30 @@ LedDevice* LedDeviceWled::construct(const QJsonObject &deviceConfig)
bool LedDeviceWled::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
bool isInitOK {false};
// Initialise LedDevice sub-class, ProviderUdp::init will be executed later, if connectivity is defined
if ( LedDevice::init(deviceConfig) )
QString streamProtocol = _devConfig[CONFIG_STREAM_PROTOCOL].toString(DEFAULT_STREAM_PROTOCOL);
if (streamProtocol != DEFAULT_STREAM_PROTOCOL)
{
// Initialise LedDevice configuration and execution environment
int configuredLedCount = this->getLedCount();
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
Debug(_log, "LedCount : %d", configuredLedCount);
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
Debug(_log, "LatchTime : %d", this->getLatchTime());
_isStreamDDP = false;
}
Debug(_log, "Stream protocol : %s", QSTRING_CSTR(streamProtocol));
Debug(_log, "Stream DDP : %d", _isStreamDDP);
if (configuredLedCount > UDP_MAX_LED_NUM)
{
QString errorReason = QString("Device type %1 can only be run with maximum %2 LEDs!").arg(this->getActiveDeviceType()).arg(UDP_MAX_LED_NUM);
this->setInError ( errorReason );
return false;
}
if (_isStreamDDP)
{
LedDeviceUdpDdp::init(deviceConfig);
}
else
{
_devConfig["port"] = UDP_STREAM_DEFAULT_PORT;
LedDeviceUdpRaw::init(_devConfig);
}
if (!_isDeviceInError)
{
_apiPort = API_DEFAULT_PORT;
_isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE);
_isSyncOverwrite = _devConfig[CONFIG_SYNC_OVERWRITE].toBool(DEFAULT_IS_SYNC_OVERWRITE);
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
@@ -99,57 +122,78 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
Debug(_log, "Set Brightness to : %d", _brightness);
//Set hostname as per configuration
QString hostName = deviceConfig[ CONFIG_ADDRESS ].toString();
//If host not configured the init fails
if ( hostName.isEmpty() )
{
this->setInError("No target hostname nor IP defined");
return false;
}
else
{
QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
_hostname = addressparts[0];
if ( addressparts.size() > 1 )
{
_apiPort = addressparts[1].toInt();
}
else
{
_apiPort = API_DEFAULT_PORT;
}
if ( initRestAPI( _hostname, _apiPort ) )
{
// Update configuration with hostname without port
_devConfig["host"] = _hostname;
_devConfig["port"] = STREAM_DEFAULT_PORT;
isInitOK = ProviderUdp::init(_devConfig);
Debug(_log, "Hostname/IP : %s", QSTRING_CSTR( _hostname ));
Debug(_log, "Port : %d", _port);
}
}
isInitOK = true;
}
return isInitOK;
}
bool LedDeviceWled::initRestAPI(const QString &hostname, int port)
bool LedDeviceWled::openRestAPI()
{
bool isInitOK = false;
bool isInitOK {true};
if ( _restApi == nullptr )
{
_restApi = new ProviderRestApi(hostname, port);
_restApi->setBasePath( API_BASE_PATH );
_restApi = new ProviderRestApi(_address.toString(), _apiPort);
_restApi->setLogger(_log);
isInitOK = true;
_restApi->setBasePath( API_BASE_PATH );
}
else
{
_restApi->setHost(_address.toString());
_restApi->setPort(_apiPort);
}
return isInitOK;
}
int LedDeviceWled::open()
{
int retval = -1;
_isDeviceReady = false;
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
if ( openRestAPI() )
{
if (_isStreamDDP)
{
if (LedDeviceUdpDdp::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
else
{
if (LedDeviceUdpRaw::open() == 0)
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
}
}
}
return retval;
}
int LedDeviceWled::close()
{
int retval = -1;
if (_isStreamDDP)
{
retval = LedDeviceUdpDdp::close();
}
else
{
retval = LedDeviceUdpRaw::close();
}
return retval;
}
QString LedDeviceWled::getOnOffRequest(bool isOn) const
{
QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
@@ -316,6 +360,16 @@ QJsonObject LedDeviceWled::discover(const QJsonObject& /*params*/)
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
QJsonArray deviceList;
#ifdef ENABLE_MDNS
QString discoveryMethod("mDNS");
deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(
MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT
);
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
#endif
devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose, _log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() );
@@ -327,41 +381,45 @@ QJsonObject LedDeviceWled::getProperties(const QJsonObject& params)
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
QJsonObject properties;
QString hostName = params["host"].toString("");
_hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;
if ( !hostName.isEmpty() )
Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
QString filter = params["filter"].toString("");
// Resolve hostname and port (or use default API port)
QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString apiHost = addressparts[0];
int apiPort;
if ( addressparts.size() > 1)
if ( openRestAPI() )
{
apiPort = addressparts[1].toInt();
}
else
{
apiPort = API_DEFAULT_PORT;
}
QString filter = params["filter"].toString("");
_restApi->setPath(filter);
initRestAPI(apiHost, apiPort);
_restApi->setPath(filter);
httpResponse response = _restApi->get();
if ( response.error() )
{
Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
httpResponse response = _restApi->get();
if ( response.error() )
{
Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
QJsonObject propertiesDetails = response.getBody().object();
QJsonObject propertiesDetails = response.getBody().object();
if (!propertiesDetails.isEmpty())
{
propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM);
semver::version currentVersion {""};
if (currentVersion.setVersion(propertiesDetails.value("ver").toString().toStdString()))
{
semver::version ddpVersion{WLED_VERSION_DDP};
if (currentVersion < ddpVersion)
{
Warning(_log, "DDP streaming not supported by your WLED device version [%s], minimum version expected [%s]. Fall back to UDP-Streaming (%d LEDs max)", currentVersion.getVersion().c_str(), ddpVersion.getVersion().c_str(), UDP_MAX_LED_NUM);
if (!propertiesDetails.isEmpty())
{
propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM);
}
}
else
{
Info(_log, "DDP streaming is supported by your WLED device version [%s]. No limitation in number of LEDs.", currentVersion.getVersion().c_str(), ddpVersion.getVersion().c_str());
}
}
properties.insert("properties", propertiesDetails);
}
properties.insert("properties", propertiesDetails);
DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
}
@@ -372,41 +430,40 @@ void LedDeviceWled::identify(const QJsonObject& params)
{
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QString hostName = params["host"].toString("");
_hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;
if ( !hostName.isEmpty() )
Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
// Resolve hostname and port (or use default API port)
QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString apiHost = addressparts[0];
int apiPort;
if ( addressparts.size() > 1)
if ( openRestAPI() )
{
apiPort = addressparts[1].toInt();
_isRestoreOrigState = true;
storeState();
QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25);
sendStateUpdateRequest(request);
wait(DEFAULT_IDENTIFY_TIME);
restoreState();
}
else
{
apiPort = API_DEFAULT_PORT;
}
initRestAPI(apiHost, apiPort);
_isRestoreOrigState = true;
storeState();
QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25);
sendStateUpdateRequest(request);
wait(DEFAULT_IDENTIFY_TIME);
restoreState();
}
}
int LedDeviceWled::write(const std::vector<ColorRgb> &ledValues)
{
const uint8_t * dataPtr = reinterpret_cast<const uint8_t *>(ledValues.data());
int rc {0};
return writeBytes( _ledRGBCount, dataPtr);
if (_isStreamDDP)
{
rc = LedDeviceUdpDdp::write(ledValues);
}
else
{
rc = LedDeviceUdpRaw::write(ledValues);
}
return rc;
}

View File

@@ -4,12 +4,13 @@
// LedDevice includes
#include <leddevice/LedDevice.h>
#include "ProviderRestApi.h"
#include "ProviderUdp.h"
#include "LedDeviceUdpDdp.h"
#include "LedDeviceUdpRaw.h"
///
/// Implementation of a WLED-device
///
class LedDeviceWled : public ProviderUdp
class LedDeviceWled : public LedDeviceUdpDdp, LedDeviceUdpRaw
{
public:
@@ -81,6 +82,20 @@ protected:
///
bool init(const QJsonObject &deviceConfig) override;
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// @brief Closes the UDP device.
///
/// @return Zero on success (i.e. device is closed), else negative
///
int close() override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///
@@ -127,11 +142,9 @@ private:
///
/// @brief Initialise the access to the REST-API wrapper
///
/// @param[in] host
/// @param[in] port
/// @return True, if success
///
bool initRestAPI(const QString &hostname, int port );
bool openRestAPI();
///
/// @brief Get command to power WLED-device on or off
@@ -148,10 +161,12 @@ private:
bool sendStateUpdateRequest(const QString &request);
QString resolveAddress (const QString& hostName);
///REST-API wrapper
ProviderRestApi* _restApi;
QString _hostname;
QString _hostAddress;
int _apiPort;
QJsonObject _originalStateProperties;
@@ -162,6 +177,8 @@ private:
bool _isSyncOverwrite;
bool _originalStateUdpnSend;
bool _originalStateUdpnRecv;
bool _isStreamDDP;
};
#endif // LEDDEVICEWLED_H

View File

@@ -1,7 +1,7 @@
#include "LedDeviceYeelight.h"
#include "LedDeviceYeelight.h"
#include <ssdp/SSDPDiscover.h>
#include <utils/QStringUtils.h>
#include <chrono>
#include <thread>
// Qt includes
#include <QEventLoop>
@@ -11,8 +11,15 @@
#include <QColor>
#include <QDateTime>
#include <chrono>
#include <thread>
#include <ssdp/SSDPDiscover.h>
#include <utils/QStringUtils.h>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#endif
#include <utils/NetUtils.h>
// Constants
namespace {
@@ -28,6 +35,9 @@ constexpr std::chrono::milliseconds CONNECT_STREAM_TIMEOUT{1000}; // device stre
const bool TEST_CORRELATION_IDS = false; //Ignore, if yeelight sends responses in different order as request commands
// Configuration settings
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const char CONFIG_LIGHTS [] = "lights";
const char CONFIG_COLOR_MODEL [] = "colorModel";
@@ -111,7 +121,6 @@ YeelightLight::YeelightLight( Logger *log, const QString &hostname, quint16 port
,_isInMusicMode(false)
{
_name = hostname;
}
YeelightLight::~YeelightLight()
@@ -151,25 +160,29 @@ bool YeelightLight::open()
}
else
{
_tcpSocket->connectToHost( _host, _port);
if ( _tcpSocket->waitForConnected( CONNECT_TIMEOUT.count() ) )
QHostAddress address;
if (NetUtils::resolveHostToAddress(_log, _host, address))
{
if ( _tcpSocket->state() != QAbstractSocket::ConnectedState )
_tcpSocket->connectToHost( address.toString(), _port);
if ( _tcpSocket->waitForConnected( CONNECT_TIMEOUT.count() ) )
{
if ( _tcpSocket->state() != QAbstractSocket::ConnectedState )
{
this->setInError( _tcpSocket->errorString() );
rc = false;
}
else
{
log (2,"open()","Successfully opened Yeelight: %s", QSTRING_CSTR(_host));
rc = true;
}
}
else
{
this->setInError( _tcpSocket->errorString() );
rc = false;
}
else
{
log (2,"open()","Successfully opened Yeelight: %s", QSTRING_CSTR(_host));
rc = true;
}
}
else
{
this->setInError( _tcpSocket->errorString() );
rc = false;
}
}
return rc;
@@ -1006,6 +1019,10 @@ LedDeviceYeelight::LedDeviceYeelight(const QJsonObject &deviceConfig)
,_debuglevel(0)
,_musicModeServerPort(-1)
{
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
#endif
}
LedDeviceYeelight::~LedDeviceYeelight()
@@ -1034,12 +1051,6 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
if ( LedDevice::init(deviceConfig) )
{
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
Debug(_log, "LedCount : %d", this->getLedCount());
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
Debug(_log, "RewriteTime : %d", this->getRewriteTime());
Debug(_log, "LatchTime : %d", this->getLatchTime());
//Get device specific configuration
if ( deviceConfig[ CONFIG_COLOR_MODEL ].isString() )
@@ -1102,8 +1113,9 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
int configuredYeelightsCount = 0;
for (const QJsonValueRef light : configuredYeelightLights)
{
QString hostName = light.toObject().value("host").toString();
int port = light.toObject().value("port").toInt(API_DEFAULT_PORT);
QString hostName = light.toObject().value(CONFIG_HOST).toString();
int port = light.toObject().value(CONFIG_PORT).toInt(API_DEFAULT_PORT);
if ( !hostName.isEmpty() )
{
QString name = light.toObject().value("name").toString();
@@ -1133,9 +1145,8 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
_lightsAddressList.clear();
for (int j = 0; j < static_cast<int>( configuredLedCount ); ++j)
{
QString hostName = configuredYeelightLights[j].toObject().value("host").toString();
int port = configuredYeelightLights[j].toObject().value("port").toInt(API_DEFAULT_PORT);
QString hostName = configuredYeelightLights[j].toObject().value(CONFIG_HOST).toString();
int port = configuredYeelightLights[j].toObject().value(CONFIG_PORT).toInt(API_DEFAULT_PORT);
_lightsAddressList.append( { hostName, port} );
}
@@ -1160,13 +1171,10 @@ bool LedDeviceYeelight::startMusicModeServer()
if ( ! _tcpMusicModeServer->isListening() )
{
if (! _tcpMusicModeServer->listen())
if (! _tcpMusicModeServer->listen(QHostAddress::AnyIPv4))
{
QString errorReason = QString ("(%1) %2").arg(_tcpMusicModeServer->serverError()).arg( _tcpMusicModeServer->errorString());
Error( _log, "Error: MusicModeServer: %s", QSTRING_CSTR(errorReason));
QString errorReason = QString ("Failed to start music mode server: (%1) %2").arg(_tcpMusicModeServer->serverError()).arg( _tcpMusicModeServer->errorString());
this->setInError ( errorReason );
Error( _log, "Failed to start music mode server");
}
else
{
@@ -1182,12 +1190,14 @@ bool LedDeviceYeelight::startMusicModeServer()
}
if (_musicModeServerAddress.isNull())
{
Error(_log, "Failed to resolve IP for music mode server");
_tcpMusicModeServer->close();
QString errorReason = QString ("Network error - failed to resolve IP for music mode server");
this->setInError ( errorReason );
}
}
}
if ( _tcpMusicModeServer->isListening() )
if ( !_isDeviceInError && _tcpMusicModeServer->isListening() )
{
_musicModeServerPort = _tcpMusicModeServer->serverPort();
Debug (_log, "The music mode server is running at %s:%d", QSTRING_CSTR(_musicModeServerAddress.toString()), _musicModeServerPort);
@@ -1391,10 +1401,21 @@ QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/)
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
QString discoveryMethod("ssdp");
QJsonArray deviceList;
deviceList = discover();
#ifdef ENABLE_MDNS
QString discoveryMethod("mDNS");
deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(
MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT
);
#else
QString discoveryMethod("ssdp");
deviceList = discover();
#endif
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() );
@@ -1407,21 +1428,22 @@ QJsonObject LedDeviceYeelight::getProperties(const QJsonObject& params)
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
QJsonObject properties;
QString hostName = params["hostname"].toString("");
quint16 apiPort = static_cast<quint16>( params["port"].toInt(API_DEFAULT_PORT) );
QString hostName = params[CONFIG_HOST].toString("");
quint16 apiPort = static_cast<quint16>( params[CONFIG_PORT].toInt(API_DEFAULT_PORT) );
if ( !hostName.isEmpty() )
Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(hostName) );
QHostAddress address;
if (NetUtils::resolveHostToAddress(_log, hostName, address))
{
YeelightLight yeelight(_log, hostName, apiPort);
//yeelight.setDebuglevel(3);
YeelightLight yeelight(_log, address.toString(), apiPort);
if ( yeelight.open() )
{
properties.insert("properties", yeelight.getProperties());
yeelight.close();
}
}
Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
return properties;
}
@@ -1430,15 +1452,15 @@ void LedDeviceYeelight::identify(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
QString hostName = params["hostname"].toString("");
quint16 apiPort = static_cast<quint16>( params["port"].toInt(API_DEFAULT_PORT) );
Debug (_log, "apiHost [%s], apiPort [%d]", QSTRING_CSTR(hostName), apiPort);
QString hostName = params[CONFIG_HOST].toString("");
quint16 apiPort = static_cast<quint16>( params[CONFIG_PORT].toInt(API_DEFAULT_PORT) );
if ( !hostName.isEmpty() )
Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(hostName) );
QHostAddress address;
if (NetUtils::resolveHostToAddress(_log, hostName, address))
{
YeelightLight yeelight(_log, hostName, apiPort);
//yeelight.setDebuglevel(3);
YeelightLight yeelight(_log, address.toString(), apiPort);
if ( yeelight.open() )
{
yeelight.identify();

View File

@@ -449,8 +449,8 @@ public:
/// Following parameters are required
/// @code
/// {
/// "hostname" : "hostname or IP",
/// "port" : port, default port 55443 is used when not provided
/// "host" : "hostname or IP",
/// "port" : port, default port 55443 is used when not provided
/// }
///@endcode
///
@@ -465,8 +465,8 @@ public:
/// Following parameters are required
/// @code
/// {
/// "hostname" : "hostname or IP",
/// "port" : port, default port 55443 is used when not provided
/// "host" : "hostname or IP",
/// "port" : port, default port 55443 is used when not provided
/// }
///@endcode
///

View File

@@ -136,13 +136,19 @@ httpResponse ProviderRestApi::get(const QUrl& url)
QNetworkRequest request(_networkRequestHeaders);
request.setUrl(url);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
_networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count());
#endif
QNetworkReply* reply = _networkManager->get(request);
// Connect requestFinished signal to quit slot of the loop.
QEventLoop loop;
QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count());
#endif
// Go into the loop until the request is finished.
loop.exec();
@@ -178,12 +184,18 @@ httpResponse ProviderRestApi::put(const QUrl &url, const QByteArray &body)
QNetworkRequest request(_networkRequestHeaders);
request.setUrl(url);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
_networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count());
#endif
QNetworkReply* reply = _networkManager->put(request, body);
// Connect requestFinished signal to quit slot of the loop.
QEventLoop loop;
QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count());
#endif
// Go into the loop until the request is finished.
loop.exec();
@@ -220,10 +232,19 @@ httpResponse ProviderRestApi::post(const QUrl& url, const QByteArray& body)
QNetworkRequest request(_networkRequestHeaders);
request.setUrl(url);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
_networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count());
#endif
QNetworkReply* reply = _networkManager->post(request, body);
// Connect requestFinished signal to quit slot of the loop.
QEventLoop loop;
QEventLoop::connect(reply,&QNetworkReply::finished,&loop,&QEventLoop::quit);
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count());
#endif
// Go into the loop until the request is finished.
loop.exec();
@@ -249,6 +270,10 @@ httpResponse ProviderRestApi::deleteResource(const QUrl& url)
QNetworkRequest request(_networkRequestHeaders);
request.setUrl(url);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
_networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count());
#endif
QNetworkReply* reply = _networkManager->deleteResource(request);
// Connect requestFinished signal to quit slot of the loop.
QEventLoop loop;
@@ -256,6 +281,10 @@ httpResponse ProviderRestApi::deleteResource(const QUrl& url)
// Go into the loop until the request is finished.
loop.exec();
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count());
#endif
httpResponse response;
if (reply->operation() == QNetworkAccessManager::DeleteOperation)
{
@@ -331,16 +360,9 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
}
errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise);
}
else {
else
{
errorReason = reply->errorString();
if ( reply->error() == QNetworkReply::OperationCanceledError )
{
//Do not report errors caused by request cancellation because of timeouts
Debug(_log, "Reply: [%s]", QSTRING_CSTR(errorReason) );
}
else
{
response.setError(true);
response.setErrorReason(errorReason);

View File

@@ -77,12 +77,12 @@ public:
private:
QJsonDocument _responseBody;
QJsonDocument _responseBody {};
bool _hasError = false;
QString _errorReason;
int _httpStatusCode = 0;
QNetworkReply::NetworkError _networkReplyError = QNetworkReply::NoError;
QNetworkReply::NetworkError _networkReplyError { QNetworkReply::NoError };
};
///
@@ -291,6 +291,13 @@ public:
///
void removeAllHeaders() { _networkRequestHeaders = QNetworkRequest(); }
///
/// @brief Set the common logger for LED-devices.
///
/// @param[in] log The logger to be used
///
void setLogger(Logger* log) { _log = log; }
private:
///

View File

@@ -10,16 +10,19 @@
#include <QUdpSocket>
#include <QHostInfo>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#endif
#include <utils/NetUtils.h>
// Local Hyperion includes
#include "ProviderUdp.h"
const ushort MAX_PORT = 65535;
ProviderUdp::ProviderUdp(const QJsonObject& deviceConfig)
: LedDevice(deviceConfig)
, _udpSocket(nullptr)
, _port(1)
, _defaultHost("127.0.0.1")
, _port(-1)
{
_latchTime_ms = 0;
}
@@ -29,83 +32,38 @@ ProviderUdp::~ProviderUdp()
delete _udpSocket;
}
bool ProviderUdp::init(const QJsonObject& deviceConfig)
{
bool isInitOK = false;
// Initialise sub-class
if (LedDevice::init(deviceConfig))
{
QString host = deviceConfig["host"].toString(_defaultHost);
if (_address.setAddress(host))
{
Debug(_log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString()));
}
else
{
QHostInfo hostInfo = QHostInfo::fromName(host);
if (hostInfo.error() == QHostInfo::NoError)
{
_address = hostInfo.addresses().first();
Debug(_log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host));
}
else
{
QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString());
this->setInError(errortext);
isInitOK = false;
}
}
if (!_isDeviceInError)
{
int config_port = deviceConfig["port"].toInt(_port);
if (config_port <= 0 || config_port > MAX_PORT)
{
QString errortext = QString("Invalid target port [%1]!").arg(config_port);
this->setInError(errortext);
isInitOK = false;
}
else
{
_port = static_cast<quint16>(config_port);
Debug(_log, "UDP socket will write to %s port: %u", QSTRING_CSTR(_address.toString()), _port);
_udpSocket = new QUdpSocket(this);
isInitOK = true;
}
}
}
return isInitOK;
}
int ProviderUdp::open()
{
int retval = -1;
_isDeviceReady = false;
// Try to bind the UDP-Socket
if (_udpSocket != nullptr)
if (!_isDeviceInError)
{
if (_udpSocket->state() != QAbstractSocket::BoundState)
{
QHostAddress localAddress = QHostAddress::Any;
quint16 localPort = 0;
if (!_udpSocket->bind(localAddress, localPort))
if (_udpSocket == nullptr)
{
QString warntext = QString("Could not bind local address: %1, (%2) %3").arg(localAddress.toString()).arg(_udpSocket->error()).arg(_udpSocket->errorString());
Warning(_log, "%s", QSTRING_CSTR(warntext));
_udpSocket = new QUdpSocket(this);
}
// Try to bind the UDP-Socket
if (_udpSocket != nullptr)
{
Info(_log, "Stream UDP data to %s port: %d", QSTRING_CSTR(_address.toString()), _port);
if (_udpSocket->state() != QAbstractSocket::BoundState)
{
QHostAddress localAddress = QHostAddress::Any;
quint16 localPort = 0;
if (!_udpSocket->bind(localAddress, localPort))
{
QString warntext = QString("Could not bind local address: %1, (%2) %3").arg(localAddress.toString()).arg(_udpSocket->error()).arg(_udpSocket->errorString());
Warning(_log, "%s", QSTRING_CSTR(warntext));
}
}
retval = 0;
}
else
{
this->setInError(" Open error. UDP Socket not initialised!");
}
}
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
else
{
this->setInError(" Open error. UDP Socket not initialised!");
}
return retval;
}
@@ -131,7 +89,7 @@ int ProviderUdp::close()
int ProviderUdp::writeBytes(const unsigned size, const uint8_t* data)
{
int rc = 0;
qint64 bytesWritten = _udpSocket->writeDatagram(reinterpret_cast<const char*>(data), size, _address, _port);
qint64 bytesWritten = _udpSocket->writeDatagram(reinterpret_cast<const char*>(data), size, _address, static_cast<quint16>(_port));
if (bytesWritten == -1 || bytesWritten != size)
{
@@ -144,7 +102,7 @@ int ProviderUdp::writeBytes(const unsigned size, const uint8_t* data)
int ProviderUdp::writeBytes(const QByteArray& bytes)
{
int rc = 0;
qint64 bytesWritten = _udpSocket->writeDatagram(bytes, _address, _port);
qint64 bytesWritten = _udpSocket->writeDatagram(bytes, _address, static_cast<quint16>(_port));
if (bytesWritten == -1 || bytesWritten != bytes.size())
{

View File

@@ -32,14 +32,6 @@ public:
protected:
///
/// @brief Initialise the UDP device's configuration and network address details
///
/// @param[in] deviceConfig the JSON device configuration
/// @return True, if success
///
bool init(const QJsonObject& deviceConfig) override;
///
/// @brief Opens the output device.
///
@@ -74,10 +66,10 @@ protected:
int writeBytes(const QByteArray& bytes);
///
QUdpSocket* _udpSocket;
QUdpSocket* _udpSocket;
QString _hostName;
QHostAddress _address;
quint16 _port;
QString _defaultHost;
int _port;
};
#endif // PROVIDERUDP_H

File diff suppressed because it is too large Load Diff

View File

@@ -23,19 +23,6 @@
#if defined(MBEDTLS_PLATFORM_C)
#include <mbedtls/platform.h>
#else
#include <stdio.h>
#include <stdlib.h>
#define mbedtls_time time
#define mbedtls_time_t time_t
#define mbedtls_printf printf
#define mbedtls_fprintf fprintf
#define mbedtls_snprintf snprintf
#define mbedtls_calloc calloc
#define mbedtls_free free
#define mbedtls_exit exit
#define MBEDTLS_EXIT_SUCCESS EXIT_SUCCESS
#define MBEDTLS_EXIT_FAILURE EXIT_FAILURE
#endif
#include <string.h>
@@ -50,12 +37,6 @@
#include <mbedtls/error.h>
#include <mbedtls/debug.h>
//----------- END mbedtls
constexpr std::chrono::milliseconds STREAM_SSL_HANDSHAKE_TIMEOUT_MIN{400};
constexpr std::chrono::milliseconds STREAM_SSL_HANDSHAKE_TIMEOUT_MAX{1000};
constexpr std::chrono::milliseconds STREAM_SSL_READ_TIMEOUT{0};
class ProviderUdpSSL : public LedDevice
{
Q_OBJECT
@@ -71,6 +52,11 @@ public:
///
~ProviderUdpSSL() override;
///
QString _hostName;
QHostAddress _address;
int _port;
protected:
///
@@ -102,6 +88,18 @@ protected:
///
bool initNetwork();
///
/// @brief Start astreaming connection
///
/// @return True, if success
///
bool startConnection();
///
/// @brief Stop the streaming connection
///
void stopConnection();
///
/// Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the
/// values are latched.
@@ -109,7 +107,7 @@ protected:
/// @param[in] size The length of the data
/// @param[in] data The data
///
void writeBytes(unsigned int size, const uint8_t *data);
void writeBytes(unsigned int size, const uint8_t* data, bool flush = false);
///
/// get ciphersuites list from mbedtls_ssl_list_ciphersuites
@@ -118,36 +116,16 @@ protected:
///
virtual const int * getCiphersuites() const;
void sslLog(const QString &msg, const char* errorType = "debug");
void sslLog(const char* msg, const char* errorType = "debug");
void configLog(const char* msg, const char* type, ...);
/**
* Debug callback for mbed TLS
* Just prints on the USB serial port
*/
static void ProviderUdpSSLDebug(void* ctx, int level, const char* file, int line, const char* str);
/**
* Certificate verification callback for mbed TLS
* Here we only use it to display information on each cert in the chain
*/
static int ProviderUdpSSLVerify(void* data, mbedtls_x509_crt* crt, int depth, uint32_t* flags);
///
/// closeSSLNotify and freeSSLConnection
///
void closeSSLConnection();
private:
bool initConnection();
bool seedingRNG();
bool setupStructure();
bool startUPDConnection();
bool setupPSK();
bool startSSLHandshake();
void handleReturn(int ret);
QString errorMsg(int ret);
void closeSSLNotify();
void freeSSLConnection();
@@ -160,24 +138,19 @@ private:
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_timing_delay_context timer;
QMutex _hueMutex;
QString _transport_type;
QString _custom;
QHostAddress _address;
QString _defaultHost;
int _port;
int _ssl_port;
QString _server_name;
QString _psk;
QString _psk_identity;
uint32_t _read_timeout;
int _handshake_attempts;
uint32_t _handshake_timeout_min;
uint32_t _handshake_timeout_max;
unsigned int _handshake_attempts;
int _retry_left;
bool _stopConnection;
bool _debugStreamer;
int _debugLevel;
bool _streamReady;
bool _streamPaused;
};
#endif // PROVIDERUDPSSL_H

View File

@@ -79,4 +79,4 @@ private:
};
#endif // LEDEVICETEMPLATE_H
#endif // LEDEVICEPIBLASTER_H

View File

@@ -44,13 +44,6 @@ bool ProviderRs232::init(const QJsonObject &deviceConfig)
// Initialise sub-class
if ( LedDevice::init(deviceConfig) )
{
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
Debug(_log, "LedCount : %d", this->getLedCount());
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms);
Debug(_log, "LatchTime : %d", this->getLatchTime());
_deviceName = deviceConfig["output"].toString("auto");
_isAutoDeviceName = _deviceName.toLower() == "auto";
@@ -89,7 +82,6 @@ int ProviderRs232::open()
{
int retval = -1;
_isDeviceReady = false;
_isInSwitchOff = false;
// open device physically
if ( tryOpen(_delayAfterConnect_ms) )
@@ -190,18 +182,6 @@ bool ProviderRs232::tryOpen(int delayAfterConnect_ms)
{
QString errortext = QString("Invalid serial device name: %1 %2!").arg(_deviceName, _location);
this->setInError( errortext );
// List available device
for (auto &port : QSerialPortInfo::availablePorts() ) {
Debug(_log, "Avail. serial device: [%s]-(%s|%s), Manufacturer: %s, Description: %s",
QSTRING_CSTR(port.portName()),
QSTRING_CSTR(QString("0x%1").arg(port.vendorIdentifier(), 0, 16)),
QSTRING_CSTR(QString("0x%1").arg(port.productIdentifier(), 0, 16)),
QSTRING_CSTR(port.manufacturer()),
QSTRING_CSTR(port.description())
);
}
return false;
}
}
@@ -295,17 +275,21 @@ QString ProviderRs232::discoverFirst()
return "";
}
QJsonObject ProviderRs232::discover(const QJsonObject& /*params*/)
QJsonObject ProviderRs232::discover(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
QJsonArray deviceList;
bool showAll = params["discoverAll"].toBool(false);
// Discover serial Devices
for (auto &port : QSerialPortInfo::availablePorts() )
{
if ( !port.isNull() && port.vendorIdentifier() != 0)
if ( !port.isNull() && (showAll || port.vendorIdentifier() != 0) )
{
QJsonObject portInfo;
portInfo.insert("description", port.description());
@@ -364,6 +348,8 @@ void ProviderRs232::identify(const QJsonObject& params)
QString deviceName = params["output"].toString("");
if (!deviceName.isEmpty())
{
Info(_log, "Identify %s, device: %s", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(deviceName) );
_devConfig = params;
init(_devConfig);
{

View File

@@ -74,12 +74,22 @@ protected:
bool powerOff() override;
///
/// @brief Discover first devices of a serial device available (for configuration)
/// @brief Discover first device of serial devices available (for configuration)
///
/// @return A string of the device found
///
QString discoverFirst() override;
///
/// @brief Discover serial devices available (for configuration).
///
/// Following parameters can be provided optional
/// @code
/// {
/// "discoverAll" : true/false , "true", in case devices without vendor-id are to be included in the discovery result
/// }
///@endcode
///
/// @param[in] params Parameters used to overwrite discovery default behaviour
///
/// @return A JSON structure holding a list of devices found

View File

@@ -37,7 +37,6 @@ ProviderSpi::ProviderSpi(const QJsonObject &deviceConfig)
{
memset(&_spi, 0, sizeof(_spi));
_latchTime_ms = 1;
_isInSwitchOff = false;
}
ProviderSpi::~ProviderSpi()
@@ -69,7 +68,6 @@ int ProviderSpi::open()
int retval = -1;
QString errortext;
_isDeviceReady = false;
_isInSwitchOff = false;
const int bitsPerWord = 8;

View File

@@ -37,43 +37,74 @@
},
"useEntertainmentAPI": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_useEntertainmentAPI_title",
"default": true,
"propertyOrder": 5
},
"transitiontime": {
"type": "number",
"title": "edt_dev_spec_transistionTime_title",
"default": 1,
"append": "x100ms",
"options": {
"dependencies": {
"useEntertainmentAPI": false
}
},
"propertyOrder": 6
},
"switchOffOnBlack": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_switchOffOnBlack_title",
"default": false,
"propertyOrder": 7
"propertyOrder": 6
},
"restoreOriginalState": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_restoreOriginalState_title",
"default": true,
"default": false,
"propertyOrder": 7
},
"blackLevel": {
"type": "number",
"format": "stepper",
"title": "edt_dev_spec_brightnessThreshold_title",
"default": 0.009,
"step": 0.01,
"minimum": 0.001,
"maximum": 1.0,
"propertyOrder": 8
},
"onBlackTimeToPowerOff": {
"type": "integer",
"format": "stepper",
"step": 50,
"title": "edt_dev_spec_onBlackTimeToPowerOff",
"append": "edt_append_ms",
"minimum": 100,
"maximum": 100000,
"default": 600,
"required": true,
"propertyOrder": 9
},
"onBlackTimeToPowerOn": {
"type": "integer",
"format": "stepper",
"step": 50,
"title": "edt_dev_spec_onBlackTimeToPowerOn",
"append": "edt_append_ms",
"minimum": 100,
"maximum": 100000,
"default": 300,
"required": true,
"propertyOrder": 9
},
"candyGamma": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_candyGamma_title",
"default": true,
"propertyOrder": 10
},
"lightIds": {
"type": "array",
"title": "edt_dev_spec_lightid_title",
"minimum": 1,
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1,
"required": true,
"minimum": 0,
"title": "edt_dev_spec_lightid_itemtitle"
},
"options": {
@@ -81,10 +112,12 @@
"useEntertainmentAPI": false
}
},
"propertyOrder": 9
"propertyOrder": 11
},
"groupId": {
"type": "number",
"format": "stepper",
"step": 1,
"title": "edt_dev_spec_groupId_title",
"default": 0,
"options": {
@@ -92,41 +125,11 @@
"useEntertainmentAPI": true
}
},
"propertyOrder": 10
},
"blackLightsTimeout": {
"type": "number",
"title": "edt_dev_spec_blackLightsTimeout_title",
"default": 15000,
"step": 500,
"minimum": 10000,
"maximum": 60000,
"access": "advanced",
"append": "edt_append_ms",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 11
},
"brightnessThreshold": {
"type": "number",
"title": "edt_dev_spec_brightnessThreshold_title",
"default": 0,
"step": 0.005,
"minimum": 0,
"maximum": 1.0,
"access": "advanced",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 12
},
"brightnessFactor": {
"type": "number",
"format": "stepper",
"title": "edt_dev_spec_brightnessFactor_title",
"default": 1.0,
"step": 0.25,
@@ -135,6 +138,82 @@
"access": "advanced",
"propertyOrder": 13
},
"handshakeTimeoutMin": {
"type": "number",
"format": "stepper",
"title": "edt_dev_spec_sslHSTimeoutMin_title",
"default": 600,
"step": 100,
"minimum": 100,
"maximum": 30000,
"access": "expert",
"append": "edt_append_ms",
"required": true,
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 14
},
"handshakeTimeoutMax": {
"type": "number",
"format": "stepper",
"title": "edt_dev_spec_sslHSTimeoutMax_title",
"default": 1000,
"step": 100,
"minimum": 100,
"maximum": 30000,
"access": "expert",
"append": "edt_append_ms",
"required": true,
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 15
},
"verbose": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_verbose_title",
"default": false,
"access": "expert",
"propertyOrder": 16
},
"transitiontime": {
"type": "number",
"title": "edt_dev_spec_transistionTime_title",
"default": 1,
"minimum": 0,
"maximum": 100000,
"required": true,
"append": "x100ms",
"options": {
"dependencies": {
"useEntertainmentAPI": false
}
},
"propertyOrder": 17
},
"blackLightsTimeout": {
"type": "number",
"default": 5000,
"options": {
"hidden": true
},
"propertyOrder": 18
},
"brightnessThreshold": {
"type": "number",
"title": "edt_dev_spec_brightnessThreshold_title",
"default": 0.0001,
"options": {
"hidden": true
},
"propertyOrder": 19
},
"brightnessMin": {
"type": "number",
"title": "edt_dev_spec_brightnessMin_title",
@@ -144,11 +223,9 @@
"maximum": 1.0,
"access": "advanced",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
"hidden": true
},
"propertyOrder": 14
"propertyOrder": 20
},
"brightnessMax": {
"type": "number",
@@ -159,93 +236,8 @@
"maximum": 1.0,
"access": "advanced",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
"hidden": true
},
"propertyOrder": 15
},
"sslReadTimeout": {
"type": "number",
"title": "edt_dev_spec_sslReadTimeout_title",
"default": 0,
"step": 100,
"minimum": 0,
"maximum": 30000,
"access": "expert",
"append": "edt_append_ms",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 16
},
"sslHSTimeoutMin": {
"type": "number",
"title": "edt_dev_spec_sslHSTimeoutMin_title",
"default": 400,
"step": 100,
"minimum": 0,
"maximum": 30000,
"access": "expert",
"append": "edt_append_ms",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 17
},
"sslHSTimeoutMax": {
"type": "number",
"title": "edt_dev_spec_sslHSTimeoutMax_title",
"default": 1000,
"step": 100,
"minimum": 0,
"maximum": 30000,
"access": "expert",
"append": "edt_append_ms",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 18
},
"verbose": {
"type": "boolean",
"title": "edt_dev_spec_verbose_title",
"default": false,
"access": "expert",
"propertyOrder": 19
},
"debugStreamer": {
"type": "boolean",
"title": "edt_dev_spec_debugStreamer_title",
"default": false,
"access": "expert",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 20
},
"debugLevel": {
"type": "string",
"title": "edt_dev_spec_debugLevel_title",
"enum": [ "0", "1", "2", "3", "4" ],
"default": "0",
"options": {
"enum_titles": [ "edt_conf_enum_dl_nodebug", "edt_conf_enum_dl_error", "edt_conf_enum_dl_statechange", "edt_conf_enum_dl_informational", "edt_conf_enum_dl_verbose" ],
"dependencies": {
"useEntertainmentAPI": true
}
},
"minimum": 0,
"maximum": 4,
"access": "expert",
"propertyOrder": 21
}
},

View File

@@ -0,0 +1,32 @@
{
"type": "object",
"required": true,
"properties": {
"host": {
"type": "string",
"title": "edt_dev_spec_targetIpHost_title",
"format": "hostname_or_ip",
"propertyOrder": 1
},
"port": {
"type": "integer",
"title": "edt_dev_spec_port_title",
"default": 4048,
"minimum": 0,
"maximum": 65535,
"access": "expert",
"propertyOrder": 2
},
"latchTime": {
"type": "integer",
"title": "edt_dev_spec_latchtime_title",
"default": 0,
"append": "edt_append_ms",
"minimum": 0,
"maximum": 1000,
"access": "expert",
"propertyOrder": 3
}
},
"additionalProperties": true
}

View File

@@ -24,6 +24,17 @@
"required": true,
"propertyOrder": 2
},
"streamProtocol": {
"type": "string",
"title": "edt_dev_spec_stream_protocol_title",
"enum": [ "DDP", "RAW" ],
"default": "DDP",
"options": {
"enum_titles": [ "edt_conf_enum_udp_ddp", "edt_conf_enum_udp_raw" ]
},
"access": "expert",
"propertyOrder": 3
},
"restoreOriginalState": {
"type": "boolean",
"format": "checkbox",
@@ -33,7 +44,7 @@
"options": {
"infoText": "edt_dev_spec_restoreOriginalState_title_info"
},
"propertyOrder": 3
"propertyOrder": 4
},
"overwriteSync": {
"type": "boolean",
@@ -42,7 +53,7 @@
"default": true,
"required": true,
"access": "advanced",
"propertyOrder": 4
"propertyOrder": 5
},
"overwriteBrightness": {
"type": "boolean",
@@ -51,7 +62,7 @@
"default": true,
"required": true,
"access": "advanced",
"propertyOrder": 5
"propertyOrder": 6
},
"brightness": {
"type": "integer",
@@ -65,7 +76,7 @@
}
},
"access": "advanced",
"propertyOrder": 6
"propertyOrder": 7
},
"latchTime": {
"type": "integer",
@@ -78,7 +89,7 @@
"options": {
"infoText": "edt_dev_spec_latchtime_title_info"
},
"propertyOrder": 7
"propertyOrder": 8
}
},
"additionalProperties": true

View File

@@ -0,0 +1,16 @@
# Define the current source locations
set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/mdns)
set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/mdns)
FILE ( GLOB MDNS_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
add_library(mdns ${MDNS_SOURCES})
include_directories(${QMDNS_INCLUDE_DIR})
target_link_libraries(mdns
hyperion-utils
${QMDNS_LIBRARIES}
)
target_include_directories(mdns PUBLIC ${QMDNS_INCLUDE_DIR})

471
libsrc/mdns/MdnsBrowser.cpp Normal file
View File

@@ -0,0 +1,471 @@
#include <mdns/MdnsBrowser.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/service.h>
//Qt includes
#include <QThread>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QHostAddress>
#include <QRegularExpression>
// Utility includes
#include <HyperionConfig.h>
#include <utils/Logger.h>
#include <utils/WaitTime.h>
#include <utils/NetUtils.h>
namespace {
const bool verboseBrowser = false;
} //End of constants
MdnsBrowser::MdnsBrowser(QObject* parent)
: QObject(parent)
, _log(Logger::getInstance("MDNS"))
{
qRegisterMetaType<QHostAddress>("QHostAddress");
}
MdnsBrowser::~MdnsBrowser()
{
qDeleteAll(_browsedServiceTypes);
}
void MdnsBrowser::browseForServiceType(const QByteArray& serviceType)
{
if (!_browsedServiceTypes.contains(serviceType))
{
DebugIf(verboseBrowser, _log, "Start new mDNS browser for serviceType [%s], Thread: %s", serviceType.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
QMdnsEngine::Browser* newBrowser = new QMdnsEngine::Browser(&_server, serviceType, &_cache);
QObject::connect(newBrowser, &QMdnsEngine::Browser::serviceAdded, this, &MdnsBrowser::onServiceAdded);
QObject::connect(newBrowser, &QMdnsEngine::Browser::serviceUpdated, this, &MdnsBrowser::onServiceUpdated);
QObject::connect(newBrowser, &QMdnsEngine::Browser::serviceRemoved, this, &MdnsBrowser::onServiceRemoved);
_browsedServiceTypes.insert(serviceType, newBrowser);
}
else
{
DebugIf(verboseBrowser, _log, "Use existing mDNS browser for serviceType [%s], Thread: %s", serviceType.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
}
}
void MdnsBrowser::onServiceAdded(const QMdnsEngine::Service& service)
{
DebugIf(verboseBrowser, _log, "Discovered service [%s] at host: %s, port: %u, Thread: %s", service.name().constData(), service.hostname().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName()));
emit serviceFound(service);
}
void MdnsBrowser::onServiceUpdated(const QMdnsEngine::Service& service)
{
DebugIf(verboseBrowser, _log, "[%s], Name: [%s], Port: [%u], Thread: %s", service.type().constData(), service.name().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName()));
}
void MdnsBrowser::onServiceRemoved(const QMdnsEngine::Service& service)
{
DebugIf(verboseBrowser, _log, "[%s], Name: [%s], Port: [%u], Thread: %s", service.type().constData(), service.name().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName()));
emit serviceRemoved(service);
}
QHostAddress MdnsBrowser::getHostFirstAddress(const QByteArray& hostname)
{
DebugIf(verboseBrowser, _log, "for hostname [%s], Thread: %s", hostname.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
QByteArray toBeResolvedHostName {hostname};
QHostAddress hostAddress;
if (toBeResolvedHostName.endsWith(".local"))
{
toBeResolvedHostName.append('.');
}
if (toBeResolvedHostName.endsWith(".local."))
{
QList<QMdnsEngine::Record> aRecords;
if (_cache.lookupRecords(toBeResolvedHostName, QMdnsEngine::A, aRecords))
{
foreach(QMdnsEngine::Record record, aRecords)
{
// Do not publish link local addresses
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
if (!record.address().isLinkLocal())
#else
if (!record.address().toString().startsWith("fe80"))
#endif
{
hostAddress = record.address();
DebugIf(verboseBrowser, _log, "Hostname [%s] translates to IPv4-address [%s]", toBeResolvedHostName.constData(), QSTRING_CSTR(hostAddress.toString()));
break;
}
}
}
else
{
QList<QMdnsEngine::Record> aaaaRecords;
if (_cache.lookupRecords(toBeResolvedHostName, QMdnsEngine::AAAA, aaaaRecords))
{
foreach(QMdnsEngine::Record record, aaaaRecords)
{
// Do not publish link local addresses
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
if (!record.address().isLinkLocal())
#else
if (!record.address().toString().startsWith("fe80"))
#endif
{
hostAddress = record.address();
DebugIf(verboseBrowser, _log, "Hostname [%s] translates to IPv6-address [%s]", toBeResolvedHostName.constData(), QSTRING_CSTR(hostAddress.toString()));
break;
}
}
}
else
{
DebugIf(verboseBrowser, _log, "IP-address for hostname [%s] not yet in cache, start resolver.", toBeResolvedHostName.constData());
qRegisterMetaType<QMdnsEngine::Message>("Message");
auto* resolver = new QMdnsEngine::Resolver(&_server, toBeResolvedHostName, &_cache);
connect(resolver, &QMdnsEngine::Resolver::resolved, this, &MdnsBrowser::onHostNameResolved);
}
}
}
return hostAddress;
}
void MdnsBrowser::onHostNameResolved(const QHostAddress& address)
{
DebugIf(verboseBrowser, _log, "for address [%s], Thread: %s", QSTRING_CSTR(address.toString()), QSTRING_CSTR(QThread::currentThread()->objectName()));
// Do not publish link local addresses
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
if (!address.isLinkLocal())
#else
if (!address.toString().startsWith("fe80"))
#endif
{
emit addressResolved(address);
}
}
bool MdnsBrowser::resolveAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress, std::chrono::milliseconds timeout)
{
DebugIf(verboseBrowser, _log, "Get address for hostname [%s], Thread: %s", QSTRING_CSTR(hostname), QSTRING_CSTR(QThread::currentThread()->objectName()));
bool isHostAddressOK{ false };
if (hostname.endsWith(".local") || hostname.endsWith(".local."))
{
hostAddress = getHostFirstAddress(hostname.toUtf8());
if (hostAddress.isNull())
{
DebugIf(verboseBrowser, _log, "Wait for resolver on hostname [%s]", QSTRING_CSTR(hostname));
QEventLoop loop;
QTimer t;
QObject::connect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved, &loop, &QEventLoop::quit);
weakConnect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved,
[&hostAddress, hostname](const QHostAddress& resolvedAddress) {
DebugIf(verboseBrowser, Logger::getInstance("MDNS"), "Resolver resolved hostname [%s] to address [%s], Thread: %s", QSTRING_CSTR(hostname), QSTRING_CSTR(resolvedAddress.toString()), QSTRING_CSTR(QThread::currentThread()->objectName()));
hostAddress = resolvedAddress;
});
QTimer::connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit);
t.start(static_cast<int>(timeout.count()));
loop.exec();
}
if (!hostAddress.isNull())
{
Debug(log, "Resolved mDNS hostname [%s] to address [%s]", QSTRING_CSTR(hostname), QSTRING_CSTR(hostAddress.toString()));
isHostAddressOK = true;
}
else
{
Error(log, "Resolved mDNS hostname [%s] timed out", QSTRING_CSTR(hostname));
}
}
else
{
Error(log, "Hostname [%s] is not an mDNS hostname.", QSTRING_CSTR(hostname));
isHostAddressOK = false;
}
return isHostAddressOK;
}
QMdnsEngine::Record MdnsBrowser::getServiceInstanceRecord(const QByteArray& serviceInstance, const std::chrono::milliseconds waitTime) const
{
DebugIf(verboseBrowser, _log, "Get service instance [%s] details, Thread: %s",serviceInstance.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
QByteArray service{ serviceInstance };
if (!service.endsWith('.'))
{
service.append('.');
}
QMdnsEngine::Record srvRecord;
bool found{ false };
int retries = 5;
do
{
if (_cache.lookupRecord(service, QMdnsEngine::SRV, srvRecord))
{
found = true;
}
else
{
wait(waitTime);
--retries;
}
} while (!found && retries >= 0);
if (found)
{
DebugIf(verboseBrowser, _log, "Service record found for service instance [%s]", service.constData());
}
else
{
Debug(_log, "No service record found for service instance [%s]", service.constData());
}
return srvRecord;
}
QMdnsEngine::Service MdnsBrowser::getFirstService(const QByteArray& serviceType, const QString& filter, const std::chrono::milliseconds waitTime) const
{
DebugIf(verboseBrowser,_log, "Get first service of type [%s], matching name: [%s]", QSTRING_CSTR(QString(serviceType)), QSTRING_CSTR(filter));
QMdnsEngine::Service service;
QRegularExpression regEx(filter);
if (!regEx.isValid()) {
QString errorString = regEx.errorString();
int errorOffset = regEx.patternErrorOffset();
Error(_log, "Filtering regular expression [%s] error [%d]:[%s]", QSTRING_CSTR(filter), errorOffset, QSTRING_CSTR(errorString));
}
else
{
QList<QMdnsEngine::Record> ptrRecords;
bool found {false};
int retries = 3;
do
{
if (_cache.lookupRecords(serviceType, QMdnsEngine::PTR, ptrRecords))
{
for (int ptrCounter = 0; ptrCounter < ptrRecords.size(); ++ptrCounter)
{
QByteArray serviceNameFull = ptrRecords.at(ptrCounter).target();
QRegularExpressionMatch match = regEx.match(serviceNameFull.constData());
if (match.hasMatch())
{
QMdnsEngine::Record srvRecord;
if (!_cache.lookupRecord(serviceNameFull, QMdnsEngine::SRV, srvRecord))
{
DebugIf(verboseBrowser, _log, "No SRV record for [%s] found, skip entry", serviceNameFull.constData());
}
else
{
if (serviceNameFull.endsWith("." + serviceType))
{
service.setName(serviceNameFull.left(serviceNameFull.length() - serviceType.length() - 1));
}
else
{
service.setName(srvRecord.name());
}
service.setPort(srvRecord.port());
QByteArray hostName = srvRecord.target();
//Remove trailing dot
hostName.chop(1);
service.setHostname(hostName);
service.setAttributes(srvRecord.attributes());
found = true;
}
}
}
}
else
{
wait(waitTime);
--retries;
}
} while (!found && retries >= 0);
if (found)
{
DebugIf(verboseBrowser,_log, "Service of type [%s] found", serviceType.constData());
}
else
{
Debug(_log, "No service of type [%s] found", serviceType.constData());
}
}
return service;
}
QJsonArray MdnsBrowser::getServicesDiscoveredJson(const QByteArray& serviceType, const QString& filter, const std::chrono::milliseconds waitTime) const
{
DebugIf(verboseBrowser,_log, "Get services of type [%s], matching name: [%s], Thread: %s", QSTRING_CSTR(QString(serviceType)), QSTRING_CSTR(filter), QSTRING_CSTR(QThread::currentThread()->objectName()));
QJsonArray result;
QRegularExpression regEx(filter);
if (!regEx.isValid()) {
QString errorString = regEx.errorString();
int errorOffset = regEx.patternErrorOffset();
Error(_log, "Filtering regular expression [%s] error [%d]:[%s]", QSTRING_CSTR(filter), errorOffset, QSTRING_CSTR(errorString));
}
else
{
QList<QMdnsEngine::Record> ptrRecords;
int retries = 3;
do
{
if (_cache.lookupRecords(serviceType, QMdnsEngine::PTR, ptrRecords))
{
for (int ptrCounter = 0; ptrCounter < ptrRecords.size(); ++ptrCounter)
{
QByteArray serviceName = ptrRecords.at(ptrCounter).target();
QRegularExpressionMatch match = regEx.match(serviceName.constData());
if (match.hasMatch())
{
QMdnsEngine::Record srvRecord;
if (!_cache.lookupRecord(serviceName, QMdnsEngine::SRV, srvRecord))
{
Debug(_log, "No SRV record for [%s] found, skip entry", serviceName.constData());
}
else
{
QJsonObject obj;
QString domain = "local.";
obj.insert("id", serviceName.constData());
QString service = serviceName;
service.chop(1);
obj.insert("service", service);
obj.insert("type", serviceType.constData());
QString name;
if (serviceName.endsWith("." + serviceType))
{
name = serviceName.left(serviceName.length() - serviceType.length() - 1);
obj.insert("name", QString(name));
}
QByteArray hostName = srvRecord.target();
//Remove trailing dot
hostName.chop(1);
obj.insert("hostname", QString(hostName));
obj.insert("domain", domain);
//Tag records where the service is provided by this host
QByteArray localHostname = QHostInfo::localHostName().toUtf8();
localHostname = localHostname.replace('.', '-');
bool isSameHost {false};
if ( name == localHostname )
{
isSameHost = true;
}
obj.insert("sameHost", isSameHost);
quint16 port = srvRecord.port();
obj.insert("port", port);
QMdnsEngine::Record txtRecord;
if (_cache.lookupRecord(serviceName, QMdnsEngine::TXT, txtRecord))
{
QMap<QByteArray, QByteArray> txtAttributes = txtRecord.attributes();
QVariantMap txtMap;
QMapIterator<QByteArray, QByteArray> i(txtAttributes);
while (i.hasNext()) {
i.next();
txtMap.insert(i.key(), i.value());
}
obj.insert("txt", QJsonObject::fromVariantMap(txtMap));
}
result << obj;
}
}
}
}
if ( result.isEmpty())
{
wait(waitTime);
--retries;
}
} while (result.isEmpty() && retries >= 0);
if (!result.isEmpty())
{
DebugIf(verboseBrowser,_log, "result: [%s]", QString(QJsonDocument(result).toJson(QJsonDocument::Compact)).toUtf8().constData());
}
else
{
Debug(_log, "No service of type [%s] found", serviceType.constData());
}
}
return result;
}
void MdnsBrowser::printCache(const QByteArray& name, quint16 type) const
{
DebugIf(verboseBrowser,_log, "for type: ", QSTRING_CSTR(QMdnsEngine::typeName(type)));
QList<QMdnsEngine::Record> records;
if (_cache.lookupRecords(name, type, records))
{
qDebug() << "";
foreach(QMdnsEngine::Record record, records)
{
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << "], ttl : " << record.ttl();
switch (record.type()) {
case QMdnsEngine::PTR:
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", target : " << record.target();
break;
case QMdnsEngine::SRV:
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", target : " << record.target();
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", port : " << record.port();
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", priority : " << record.priority();
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", weight : " << record.weight();
break;
case QMdnsEngine::TXT:
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", attributes: " << record.attributes();
break;
case QMdnsEngine::NSEC:
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", nextDomNam: " << record.nextDomainName();
break;
case QMdnsEngine::A:
case QMdnsEngine::AAAA:
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", address : " << record.address();
break;
}
}
}
else
{
DebugIf(verboseBrowser,_log, "Cash is empty for type: ", QSTRING_CSTR(QMdnsEngine::typeName(type)));
}
}

View File

@@ -0,0 +1,85 @@
#include <mdns/MdnsProvider.h>
#include <mdns/MdnsServiceRegister.h>
//Qt includes
#include <QHostInfo>
#include <QThread>
// Utility includes
#include <utils/Logger.h>
#include <HyperionConfig.h>
#include <hyperion/AuthManager.h>
namespace {
const bool verboseProvider = false;
} //End of constants
MdnsProvider::MdnsProvider(QObject* parent)
: QObject(parent)
, _log(Logger::getInstance("MDNS"))
, _server(nullptr)
, _hostname(nullptr)
{
}
void MdnsProvider::init()
{
_server = new QMdnsEngine::Server();
_hostname = new QMdnsEngine::Hostname(_server);
connect(_hostname, &QMdnsEngine::Hostname::hostnameChanged, this, &MdnsProvider::onHostnameChanged);
DebugIf(verboseProvider, _log, "Hostname [%s], isRegistered [%d]", _hostname->hostname().constData(), _hostname->isRegistered());
}
MdnsProvider::~MdnsProvider()
{
qDeleteAll(_providedServiceTypes);
_hostname->deleteLater();
_server->deleteLater();
}
void MdnsProvider::publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName)
{
QMdnsEngine::Provider* provider(nullptr);
QByteArray type = MdnsServiceRegister::getServiceType(serviceType);
if (!type.isEmpty())
{
DebugIf(verboseProvider, _log, "Publish new mDNS serviceType [%s], Thread: %s", type.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
if (!_providedServiceTypes.contains(type))
{
provider = new QMdnsEngine::Provider(_server, _hostname);
_providedServiceTypes.insert(type, provider);
}
else
{
provider = _providedServiceTypes[type];
}
QMdnsEngine::Service service;
service.setType(type);
service.setPort(servicePort);
QByteArray name(QHostInfo::localHostName().toUtf8());
if (!serviceName.isEmpty())
{
name.prepend(serviceName + "@");
}
service.setName(name);
QByteArray id = AuthManager::getInstance()->getID().toUtf8();
const QMap<QByteArray, QByteArray> attributes = { {"id", id}, {"version", HYPERION_VERSION} };
service.setAttributes(attributes);
DebugIf(verboseProvider, _log, "[%s], Name: [%s], Port: [%u] ", service.type().constData(), service.name().constData(), service.port());
provider->update(service);
}
}
void MdnsProvider::onHostnameChanged(const QByteArray& hostname)
{
DebugIf(verboseProvider, _log, "mDNS-hostname changed to hostname [%s]", hostname.constData());
}

View File

@@ -35,7 +35,7 @@ endif()
target_link_libraries(protoclient
hyperion
hyperion-utils
libprotobuf
${PROTOBUF_LIBRARIES}
Qt${QT_VERSION_MAJOR}::Gui
)
@@ -45,3 +45,7 @@ target_link_libraries(protoserver
protoclient
Qt${QT_VERSION_MAJOR}::Gui
)
if(ENABLE_MDNS)
target_link_libraries(protoserver mdns)
endif()

View File

@@ -10,6 +10,13 @@
#include <QTcpServer>
#include <QTcpSocket>
// Constants
namespace {
const char SERVICE_TYPE[] = "protobuffer";
} //End of constants
ProtoServer::ProtoServer(const QJsonDocument& config, QObject* parent)
: QObject(parent)
, _server(new QTcpServer(this))
@@ -95,11 +102,12 @@ void ProtoServer::startServer()
{
if(!_server->listen(QHostAddress::Any, _port))
{
Error(_log,"Failed to bind port %d", _port);
Error(_log,"Failed to bind port %d", _port);
}
else
{
Info(_log,"Started on port %d", _port);
Info(_log,"Started on port %d", _port);
emit publishService(SERVICE_TYPE, _port);
}
}
}

View File

@@ -78,7 +78,7 @@ namespace JsonUtils {
++errorLine;
}
}
Error(log,"Failed to parse json data from %s: Error: %s at Line: %i, Column: %i", QSTRING_CSTR(path), QSTRING_CSTR(error.errorString()), errorLine, errorColumn);
Error(log, "Failed to parse json data from %s: Error: %s at Line: %i, Column: %i, Data: '%s'", QSTRING_CSTR(path), QSTRING_CSTR(error.errorString()), errorLine, errorColumn, QSTRING_CSTR(data));
return false;
}
return true;

View File

@@ -25,3 +25,7 @@ target_link_libraries(webserver
hyperion-api
Qt${QT_VERSION_MAJOR}::Network
)
if(ENABLE_MDNS)
target_link_libraries(webserver mdns)
endif()

View File

@@ -6,21 +6,25 @@
#include <QFileInfo>
#include <QJsonObject>
// bonjour
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourserviceregister.h>
#endif
// netUtil
#include <utils/NetUtils.h>
WebServer::WebServer(const QJsonDocument& config, bool useSsl, QObject * parent)
: QObject(parent)
// Constants
namespace {
const char HTTP_SERVICE_TYPE[] = "http";
const char HTTPS_SERVICE_TYPE[] = "https";
const char HYPERION_SERVICENAME[] = "Hyperion";
} //End of constants
WebServer::WebServer(const QJsonDocument& config, bool useSsl, QObject* parent)
: QObject(parent)
, _config(config)
, _useSsl(useSsl)
, _log(Logger::getInstance("WEBSERVER"))
, _server()
{
}
WebServer::~WebServer()
@@ -30,64 +34,60 @@ WebServer::~WebServer()
void WebServer::initServer()
{
Debug(_log, "Initialize Webserver");
_server = new QtHttpServer (this);
_server->setServerName (QStringLiteral ("Hyperion Webserver"));
Debug(_log, "Initialize %s-Webserver", _useSsl ? "https" : "http");
_server = new QtHttpServer(this);
_server->setServerName(QStringLiteral("Hyperion %1-Webserver").arg(_useSsl ? "https" : "http"));
if(_useSsl)
if (_useSsl)
{
_server->setUseSecure();
WEBSERVER_DEFAULT_PORT = 8092;
}
connect (_server, &QtHttpServer::started, this, &WebServer::onServerStarted);
connect (_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped);
connect (_server, &QtHttpServer::error, this, &WebServer::onServerError);
connect(_server, &QtHttpServer::started, this, &WebServer::onServerStarted);
connect(_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped);
connect(_server, &QtHttpServer::error, this, &WebServer::onServerError);
// create StaticFileServing
_staticFileServing = new StaticFileServing (this);
_staticFileServing = new StaticFileServing(this);
connect(_server, &QtHttpServer::requestNeedsReply, _staticFileServing, &StaticFileServing::onRequestNeedsReply);
// init
handleSettingsUpdate(settings::WEBSERVER, _config);
}
void WebServer::onServerStarted (quint16 port)
void WebServer::onServerStarted(quint16 port)
{
_inited = true;
Info(_log, "'%s' started on port %d",_server->getServerName().toStdString().c_str(), port);
Info(_log, "'%s' started on port %d", _server->getServerName().toStdString().c_str(), port);
#ifdef ENABLE_AVAHI
if(_serviceRegister == nullptr)
if (_useSsl)
{
_serviceRegister = new BonjourServiceRegister(this);
_serviceRegister->registerService("_hyperiond-http._tcp", port);
emit publishService(HTTPS_SERVICE_TYPE, _port, HYPERION_SERVICENAME);
}
else if( _serviceRegister->getPort() != port)
else
{
delete _serviceRegister;
_serviceRegister = new BonjourServiceRegister(this);
_serviceRegister->registerService("_hyperiond-http._tcp", port);
emit publishService(HTTP_SERVICE_TYPE, _port, HYPERION_SERVICENAME);
}
#endif
emit stateChange(true);
}
void WebServer::onServerStopped ()
void WebServer::onServerStopped()
{
Info(_log, "Stopped %s", _server->getServerName().toStdString().c_str());
emit stateChange(false);
}
void WebServer::onServerError (QString msg)
void WebServer::onServerError(QString msg)
{
Error(_log, "%s", msg.toStdString().c_str());
}
void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
{
if(type == settings::WEBSERVER)
if (type == settings::WEBSERVER)
{
Debug(_log, "Apply Webserver settings");
const QJsonObject& obj = config.object();
@@ -95,7 +95,7 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c
_baseUrl = obj["document_root"].toString(WEBSERVER_DEFAULT_PATH);
if ( (_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty())
if ((_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty())
{
QFileInfo info(_baseUrl);
if (!info.exists() || !info.isDir())
@@ -112,18 +112,18 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c
// ssl different port
quint16 newPort = _useSsl ? obj["sslPort"].toInt(WEBSERVER_DEFAULT_PORT) : obj["port"].toInt(WEBSERVER_DEFAULT_PORT);
if(_port != newPort)
if (_port != newPort)
{
_port = newPort;
stop();
}
// eval if the port is available, will be incremented if not
if(!_server->isListening())
if (!_server->isListening())
NetUtils::portAvailable(_port, _log);
// on ssl we want .key .cert and probably key password
if(_useSsl)
if (_useSsl)
{
QString keyPath = obj["keyPath"].toString(WEBSERVER_DEFAULT_KEY_PATH);
QString crtPath = obj["crtPath"].toString(WEBSERVER_DEFAULT_CRT_PATH);
@@ -132,7 +132,7 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c
QList<QSslCertificate> currCerts = _server->getCertificates();
// check keyPath
if ( (keyPath != WEBSERVER_DEFAULT_KEY_PATH) && !keyPath.trimmed().isEmpty())
if ((keyPath != WEBSERVER_DEFAULT_KEY_PATH) && !keyPath.trimmed().isEmpty())
{
QFileInfo kinfo(keyPath);
if (!kinfo.exists())
@@ -145,7 +145,7 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c
keyPath = WEBSERVER_DEFAULT_KEY_PATH;
// check crtPath
if ( (crtPath != WEBSERVER_DEFAULT_CRT_PATH) && !crtPath.trimmed().isEmpty())
if ((crtPath != WEBSERVER_DEFAULT_CRT_PATH) && !crtPath.trimmed().isEmpty())
{
QFileInfo cinfo(crtPath);
if (!cinfo.exists())
@@ -161,21 +161,22 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c
QFile cfile(crtPath);
cfile.open(QIODevice::ReadOnly);
QList<QSslCertificate> validList;
QList<QSslCertificate> cList = QSslCertificate::fromDevice(&cfile, QSsl::Pem);
QList<QSslCertificate> cList = QSslCertificate::fromDevice(&cfile, QSsl::Pem);
cfile.close();
// Filter for valid certs
for(const auto & entry : cList){
if(!entry.isNull() && QDateTime::currentDateTime().daysTo(entry.expiryDate()) > 0)
for (const auto& entry : cList) {
if (!entry.isNull() && QDateTime::currentDateTime().daysTo(entry.expiryDate()) > 0)
validList.append(entry);
else
Error(_log, "The provided SSL certificate is invalid/not supported/reached expiry date ('%s')", crtPath.toUtf8().constData());
}
if(!validList.isEmpty()){
Debug(_log,"Setup SSL certificate");
if (!validList.isEmpty()) {
Debug(_log, "Setup SSL certificate");
_server->setCertificates(validList);
} else {
}
else {
Error(_log, "No valid SSL certificate has been found ('%s')", crtPath.toUtf8().constData());
}
@@ -186,10 +187,11 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c
QSslKey key(&kfile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, obj["keyPassPhrase"].toString().toUtf8());
kfile.close();
if(key.isNull()){
if (key.isNull()) {
Error(_log, "The provided SSL key is invalid or not supported use RSA encrypt and PEM format ('%s')", keyPath.toUtf8().constData());
} else {
Debug(_log,"Setup private SSL key");
}
else {
Debug(_log, "Setup private SSL key");
_server->setPrivateKey(key);
}
}
@@ -209,7 +211,7 @@ void WebServer::stop()
_server->stop();
}
void WebServer::setSSDPDescription(const QString & desc)
void WebServer::setSSDPDescription(const QString& desc)
{
_staticFileServing->setSSDPDescription(desc);
}