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

@@ -402,13 +402,6 @@ signals:
///
void onStartInstanceResponse(const int &tan);
private slots:
///
/// @brief Is called whenever a Hyperion instance wants the current register list
/// @param callerInstance The instance should be returned in the answer call
///
void requestActiveRegister(QObject *callerInstance);
private:
void stopDataConnectionss();

View File

@@ -292,6 +292,12 @@ private:
///
void handleInputSourceCommand(const QJsonObject& message, const QString& command, int tan);
/// Handle an incoming JSON message to request remote hyperion servers providing a given hyperion service
///
/// @param message the incoming message
///
void handleServiceCommand(const QJsonObject &message, const QString &command, int tan);
///
/// Handle an incoming JSON message of unknown type
///

View File

@@ -6,10 +6,7 @@
// components def
#include <utils/Components.h>
// bonjour
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourrecord.h>
#endif
// videModes
#include <utils/VideoMode.h>
// settings
@@ -21,7 +18,6 @@
class Hyperion;
class ComponentRegister;
class BonjourBrowserWrapper;
class PriorityMuxer;
class JsonCB : public QObject
@@ -73,13 +69,6 @@ private slots:
/// @brief handle component state changes
///
void handleComponentState(hyperion::Components comp, bool state);
#ifdef ENABLE_AVAHI
///
/// @brief handle emits from bonjour wrapper
/// @param bRegisters The full register map
///
void handleBonjourChange(const QMap<QString,BonjourRecord>& bRegisters);
#endif
///
/// @brief handle emits from PriorityMuxer
@@ -140,10 +129,7 @@ private:
Hyperion* _hyperion;
/// pointer of comp register
ComponentRegister* _componentRegister;
#ifdef ENABLE_AVAHI
/// Bonjour instance
BonjourBrowserWrapper* _bonjour;
#endif
/// priority muxer instance
PriorityMuxer* _prioMuxer;
/// contains all available commands

View File

@@ -1,68 +0,0 @@
#pragma once
// qt incl
#include <QObject>
#include <QMap>
#include <QHostInfo>
#include <bonjour/bonjourrecord.h>
class BonjourServiceBrowser;
class BonjourServiceResolver;
class QTimer;
class BonjourBrowserWrapper : public QObject
{
Q_OBJECT
private:
friend class HyperionDaemon;
///
/// @brief Browse for hyperion services in bonjour, constructed from HyperionDaemon
/// Searching for hyperion http service by default
///
BonjourBrowserWrapper(QObject * parent = nullptr);
public:
///
/// @brief Browse for a service
///
bool browseForServiceType(const QString &serviceType);
///
/// @brief Get all available sessions
///
QMap<QString, BonjourRecord> getAllServices() { return _hyperionSessions; }
static BonjourBrowserWrapper* instance;
static BonjourBrowserWrapper *getInstance() { return instance; }
signals:
///
/// @brief Emits whenever a change happend
///
void browserChange( const QMap<QString, BonjourRecord> &bRegisters );
private:
/// map of service names and browsers
QMap<QString, BonjourServiceBrowser *> _browsedServices;
/// Resolver
BonjourServiceResolver *_bonjourResolver;
// contains all current active service sessions
QMap<QString, BonjourRecord> _hyperionSessions;
QString _bonjourCurrentServiceToResolve;
/// timer to resolve changes
QTimer *_timerBonjourResolver;
private slots:
///
/// @brief is called whenever a BonjourServiceBrowser emits change
void currentBonjourRecordsChanged( const QList<BonjourRecord> &list );
/// @brief new record resolved
void bonjourRecordResolved( const QHostInfo &hostInfo, int port );
///
/// @brief timer slot which updates regularly entries
///
void bonjourResolve();
};

View File

@@ -1,71 +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.
*/
#ifndef BONJOURRECORD_H
#define BONJOURRECORD_H
#include <QtCore/QMetaType>
#include <QtCore/QString>
class BonjourRecord
{
public:
BonjourRecord() : port(-1) {}
BonjourRecord(const QString &name, const QString &regType, const QString &domain)
: serviceName(name)
, registeredType(regType)
, replyDomain(domain)
, port(-1)
{}
BonjourRecord(const char *name, const char *regType, const char *domain)
: serviceName(QString::fromUtf8(name))
, registeredType(QString::fromUtf8(regType))
, replyDomain(QString::fromUtf8(domain))
, port(-1)
{
}
QString serviceName;
QString registeredType;
QString replyDomain;
QString hostName;
QString address;
int port;
bool operator==(const BonjourRecord &other) const
{
return serviceName == other.serviceName
&& registeredType == other.registeredType
&& replyDomain == other.replyDomain;
}
};
Q_DECLARE_METATYPE(BonjourRecord)
#endif // BONJOURRECORD_H

View File

@@ -1,69 +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.
*/
#ifndef BONJOURSERVICEBROWSER_H
#define BONJOURSERVICEBROWSER_H
#include <QtCore/QObject>
#ifndef PLATFORM_AMLOGIC
#include <dns_sd.h>
#else
#include <avahi-compat-libdns_sd/dns_sd.h>
#endif
#include "bonjour/bonjourrecord.h"
class QSocketNotifier;
class BonjourServiceBrowser : public QObject
{
Q_OBJECT
public:
BonjourServiceBrowser(QObject *parent = 0);
~BonjourServiceBrowser() override;
void browseForServiceType(const QString &serviceType);
inline QList<BonjourRecord> currentRecords() const { return bonjourRecords; }
inline QString serviceType() const { return browsingType; }
signals:
void currentBonjourRecordsChanged(const QList<BonjourRecord> &list);
void error(DNSServiceErrorType err);
private slots:
void bonjourSocketReadyRead();
private:
static void DNSSD_API bonjourBrowseReply(DNSServiceRef , DNSServiceFlags flags, quint32,
DNSServiceErrorType errorCode, const char *serviceName,
const char *regType, const char *replyDomain, void *context);
DNSServiceRef dnssref;
QSocketNotifier *bonjourSocket;
QList<BonjourRecord> bonjourRecords;
QString browsingType;
};
#endif // BONJOURSERVICEBROWSER_H

View File

@@ -1,76 +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.
*/
#ifndef BONJOURSERVICEREGISTER_H
#define BONJOURSERVICEREGISTER_H
#include <QtCore/QObject>
#include "bonjourrecord.h"
class QSocketNotifier;
#ifndef PLATFORM_AMLOGIC
#include <dns_sd.h>
#else
#include <avahi-compat-libdns_sd/dns_sd.h>
#endif
class BonjourServiceRegister : public QObject
{
Q_OBJECT
public:
BonjourServiceRegister(QObject *parent = 0);
~BonjourServiceRegister() override;
void registerService(const QString& service, int port);
void registerService(const BonjourRecord &record, quint16 servicePort, const std::vector<std::pair<std::string, std::string>>& txt = {});
inline BonjourRecord registeredRecord() const { return finalRecord; }
quint16 getPort() const { return _port; }
signals:
void error(DNSServiceErrorType error);
void serviceRegistered(const BonjourRecord &record);
private slots:
void bonjourSocketReadyRead();
private:
static void DNSSD_API bonjourRegisterService(DNSServiceRef sdRef, DNSServiceFlags,
DNSServiceErrorType errorCode, const char *name,
const char *regtype, const char *domain,
void *context);
DNSServiceRef dnssref;
QSocketNotifier *bonjourSocket;
BonjourRecord finalRecord;
// current port
quint16 _port = 0;
};
#endif // BONJOURSERVICEREGISTER_H

View File

@@ -1,71 +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.
*/
#ifndef BONJOURSERVICERESOLVER_H
#define BONJOURSERVICERESOLVER_H
#include <QtCore/QObject>
#ifndef PLATFORM_AMLOGIC
#include <dns_sd.h>
#else
#include <avahi-compat-libdns_sd/dns_sd.h>
#endif
class QSocketNotifier;
class QHostInfo;
class BonjourRecord;
class BonjourServiceResolver : public QObject
{
Q_OBJECT
public:
BonjourServiceResolver(QObject *parent);
~BonjourServiceResolver() override;
bool resolveBonjourRecord(const BonjourRecord &record);
signals:
void bonjourRecordResolved(const QHostInfo &hostInfo, int port);
void error(DNSServiceErrorType error);
private slots:
void bonjourSocketReadyRead();
void cleanupResolve();
void finishConnect(const QHostInfo &hostInfo);
private:
static void DNSSD_API bonjourResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags,
quint32 interfaceIndex, DNSServiceErrorType errorCode,
const char *fullName, const char *hosttarget, quint16 port,
quint16 txtLen, const char *txtRecord, void *context);
DNSServiceRef dnssref;
QSocketNotifier *bonjourSocket;
int bonjourPort;
};
#endif // BONJOURSERVICERESOLVER_H

View File

@@ -7,7 +7,6 @@
// qt
#include <QVector>
class BonjourServiceRegister;
class QTcpServer;
class FlatBufferClient;
class NetOrigin;
@@ -20,6 +19,7 @@ class NetOrigin;
class FlatBufferServer : public QObject
{
Q_OBJECT
public:
FlatBufferServer(const QJsonDocument& config, QObject* parent = nullptr);
~FlatBufferServer() override;
@@ -34,6 +34,12 @@ public slots:
void initServer();
signals:
///
/// @emits whenever the server would like to announce its service details
///
void publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = "");
private slots:
///
/// @brief Is called whenever a new socket wants to connect
@@ -64,7 +70,6 @@ private:
int _timeout;
quint16 _port;
const QJsonDocument _config;
BonjourServiceRegister * _serviceRegister = nullptr;
QVector<FlatBufferClient*> _openConnections;
};

View File

@@ -28,6 +28,17 @@
class Hyperion;
class QTcpSocket;
class FlatBufferConnection;
class MessageForwarderFlatbufferClientsHelper;
struct TargetHost {
QHostAddress host;
quint16 port;
bool operator == (TargetHost const& a) const
{
return ((host == a.host) && (port == a.port));
}
};
class MessageForwarder : public QObject
{
@@ -39,14 +50,15 @@ public:
void addJsonTarget(const QJsonObject& targetConfig);
void addFlatbufferTarget(const QJsonObject& targetConfig);
private slots:
public slots:
///
/// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor
/// @param type settingyType from enum
/// @param type settingsType from enum
/// @param config configuration object
///
void handleSettingsUpdate(settings::type type, const QJsonDocument &config);
private slots:
///
/// @brief Handle component state change MessageForwarder
/// @param component The component from enum
@@ -81,15 +93,13 @@ private slots:
private:
struct TargetHost {
QHostAddress host;
quint16 port;
void enableTargets(bool enable, const QJsonObject& config);
bool operator == (TargetHost const& a) const
{
return ((host == a.host) && (port == a.port));
}
};
int startJsonTargets(const QJsonObject& config);
void stopJsonTargets();
int startFlatbufferTargets(const QJsonObject& config);
void stopFlatbufferTargets();
/// Hyperion instance
Hyperion *_hyperion;
@@ -105,10 +115,37 @@ private:
/// Flatbuffer connection for forwarding
QList<TargetHost> _flatbufferTargets;
QList<FlatBufferConnection*> _forwardClients;
/// Flag if forwarder is enabled
bool _forwarder_enabled = true;
const int _priority;
MessageForwarderFlatbufferClientsHelper* _messageForwarderFlatBufHelper;
};
class MessageForwarderFlatbufferClientsHelper : public QObject
{
Q_OBJECT
public:
MessageForwarderFlatbufferClientsHelper();
~MessageForwarderFlatbufferClientsHelper();
signals:
void addClient(const QString& origin, const TargetHost& targetHost, int priority, bool skipReply);
void clearClients();
public slots:
bool isFree() const;
void forwardImage(const Image<ColorRgb>& image);
void addClientHandler(const QString& origin, const TargetHost& targetHost, int priority, bool skipReply);
void clearClientsHandler();
private:
QList<FlatBufferConnection*> _forwardClients;
bool _free;
};

View File

@@ -22,6 +22,10 @@
#include <sys/ipc.h>
#include <sys/shm.h>
#ifdef Bool
#undef Bool
#endif
class X11Grabber : public Grabber , public QAbstractNativeEventFilter
{
public:

View File

@@ -11,7 +11,6 @@
class QTcpServer;
class QTcpSocket;
class JsonClientConnection;
class BonjourServiceRegister;
class NetOrigin;
///
@@ -31,11 +30,18 @@ public:
JsonServer(const QJsonDocument& config);
~JsonServer() override;
void initServer();
///
/// @return the port number on which this TCP listens for incoming connections
///
uint16_t getPort() const;
signals:
///
/// @emits whenever the server would like to announce its service details
///
void publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = "");
private slots:
///
@@ -71,7 +77,7 @@ private:
/// port
uint16_t _port = 0;
BonjourServiceRegister * _serviceRegister = nullptr;
const QJsonDocument _config;
void start();
void stop();

View File

@@ -1,4 +1,4 @@
#ifndef LEDEVICE_H
#ifndef LEDEVICE_H
#define LEDEVICE_H
// qt includes
@@ -14,6 +14,7 @@
#include <vector>
#include <map>
#include <algorithm>
#include <chrono>
// Utility includes
#include <utils/ColorRgb.h>
@@ -50,6 +51,13 @@ public:
///
~LedDevice() override;
///
/// @brief Set the common logger for LED-devices.
///
/// @param[in] log The logger to be used
///
void setLogger(Logger* log);
///
/// @brief Set the current active LED-device type.
///
@@ -64,6 +72,8 @@ public:
///
void setLedCount(int ledCount);
void setColorOrder(const QString& colorOrder);
///
/// @brief Set a device's latch time.
///
@@ -83,6 +93,19 @@ public:
///
void setRewriteTime(int rewriteTime_ms);
/// @brief Set a device's enablement cycle's parameters.
///
/// @param[in] maxEnableRetries Maximum number of attempts to enable a device, if reached retries will be stopped
/// @param[in] enableAttemptsTimerInterval Interval in seconds between two enablement attempts
///
void setEnableAttempts(int maxEnablAttempts, std::chrono::seconds enableAttemptsTimerInterval);
/// @brief Enable a device automatically after Hyperion startup or not
///
/// @param[in] isAutoStart
///
void setAutoStart(bool isAutoStart);
///
/// @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.
@@ -120,6 +143,15 @@ public:
///
virtual void identify(const QJsonObject& /*params*/) {}
///
/// @brief Add an authorization/client-key or token to the device
///
/// Used in context of a set of devices of the same type.
///
/// @param[in] params Parameters to address device
/// @return A JSON structure holding the authorization key/token
virtual QJsonObject addAuthorization(const QJsonObject& /*params*/) { return QJsonObject(); }
///
/// @brief Check, if device is properly initialised
///
@@ -127,7 +159,7 @@ public:
///
/// @return True, if device is initialised
///
bool isInitialised() const { return _isDeviceInitialised; }
bool isInitialised() const;
///
/// @brief Check, if device is ready to be used.
@@ -136,14 +168,14 @@ public:
///
/// @return True, if device is ready
///
bool isReady() const { return _isDeviceReady; }
bool isReady() const;
///
/// @brief Check, if device is in error state.
///
/// @return True, if device is in error
///
bool isInError() const { return _isDeviceInError; }
bool isInError() const;
///
/// @brief Prints the color values to stdout.
@@ -152,13 +184,6 @@ public:
///
static void printLedValues(const std::vector<ColorRgb>& ledValues);
///
/// @brief Set the common logger for LED-devices.
///
/// @param[in] log The logger to be used
///
void setLogger(Logger* log) { _log = log; }
public slots:
///
@@ -181,47 +206,47 @@ public slots:
/// @param[in] ledValues The color per LED
/// @return Zero on success else negative (i.e. device is not ready)
///
virtual int updateLeds(const std::vector<ColorRgb>& ledValues);
virtual int updateLeds(std::vector<ColorRgb> ledValues);
///
/// @brief Get the currently defined LatchTime.
///
/// @return Latch time in milliseconds
///
int getLatchTime() const { return _latchTime_ms; }
int getLatchTime() const;
///
/// @brief Get the currently defined RewriteTime.
///
/// @return Rewrite time in milliseconds
///
int getRewriteTime() const { return _refreshTimerInterval_ms; }
int getRewriteTime() const;
///
/// @brief Get the number of LEDs supported by the device.
///
/// @return Number of device's LEDs, 0 = unknown number
///
int getLedCount() const { return _ledCount; }
int getLedCount() const;
///
/// @brief Get the current active LED-device type.
///
QString getActiveDeviceType() const { return _activeDeviceType; }
QString getActiveDeviceType() const;
///
/// @brief Get color order of device.
///
/// @return The color order
///
QString getColorOrder() const { return _colorOrder; }
QString getColorOrder() const;
///
/// @brief Get the LED-Device component's state.
///
/// @return True, if enabled
///
inline bool componentState() const { return _isEnabled; }
bool componentState() const;
///
/// @brief Enables the device for output.
@@ -257,11 +282,6 @@ public slots:
///
virtual bool switchOff();
bool switchOnOff(bool onState)
{
return onState == true ? switchOn() : switchOff();
}
signals:
///
/// @brief Emits whenever the LED-Device switches between on/off.
@@ -361,6 +381,16 @@ protected:
///
virtual bool restoreState();
///
/// @brief Start a new enable cycle
///
void startEnableAttemptsTimer();
///
/// @brief Stop a new enable cycle
///
void stopEnableAttemptsTimer();
///
/// @brief Converts an uint8_t array to hex string.
///
@@ -368,7 +398,7 @@ protected:
/// @param size of the array
/// @param number Number of array items to be converted.
/// @return array as string of hex values
QString uint8_t_to_hex_string(const uint8_t * data, const int size, int number = -1) const;
static QString uint8_t_to_hex_string(const uint8_t * data, const int size, int number = -1) ;
///
/// @brief Converts a ByteArray to hex string.
@@ -376,7 +406,7 @@ protected:
/// @param data ByteArray
/// @param number Number of array items to be converted.
/// @return array as string of hex values
QString toHex(const QByteArray& data, int number = -1) const;
static QString toHex(const QByteArray& data, int number = -1) ;
/// Current device's type
QString _activeDeviceType;
@@ -414,6 +444,7 @@ protected:
QJsonObject _orignalStateValues;
// Device states
/// Is the device enabled?
bool _isEnabled;
@@ -429,9 +460,6 @@ protected:
/// Is the device in error state and stopped?
bool _isDeviceInError;
/// Is the device in the switchOff process?
bool _isInSwitchOff;
/// Timestamp of last write
QDateTime _lastWriteTime;
@@ -459,6 +487,15 @@ private:
/// @brief Stop refresh cycle
void stopRefreshTimer();
/// Timer that enables a device (used to retry enablement, if enabled failed before)
QTimer* _enableAttemptsTimer;
// Device configuration parameters
std::chrono::seconds _enableAttemptTimerInterval;
int _enableAttempts;
int _maxEnableAttempts;
/// Is last write refreshing enabled?
bool _isRefreshEnabled;

View File

@@ -94,16 +94,6 @@ signals:
///
int updateLeds(const std::vector<ColorRgb>& ledValues);
///
/// @brief Enables the LED-Device.
///
void enable();
///
/// @brief Disables the LED-Device.
///
void disable();
///
/// @brief Switch the LEDs on.
///
@@ -113,7 +103,7 @@ signals:
/// @brief Switch the LEDs off.
///
void switchOff();
void stopLedDevice();
private slots:

170
include/mdns/MdnsBrowser.h Normal file
View File

@@ -0,0 +1,170 @@
#ifndef MDNS_BROWSER_H
#define MDNS_BROWSER_H
#include <chrono>
#include <type_traits>
#include <qmdnsengine/server.h>
#include <qmdnsengine/service.h>
#include <qmdnsengine/browser.h>
#include <qmdnsengine/cache.h>
#include <qmdnsengine/resolver.h>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/record.h>
// Qt includes
#include <QObject>
#include <QByteArray>
// Utility includes
#include <utils/Logger.h>
namespace {
constexpr std::chrono::milliseconds DEFAULT_DISCOVER_TIMEOUT{ 500 };
constexpr std::chrono::milliseconds DEFAULT_ADDRESS_RESOLVE_TIMEOUT{ 1000 };
} //End of constants
class MdnsBrowser : public QObject
{
Q_OBJECT
// Run MdnsBrowser as singleton
private:
///
/// @brief Browse for hyperion services in bonjour, constructed from HyperionDaemon
/// Searching for hyperion http service by default
///
// Run MdnsBrowser as singleton
MdnsBrowser(QObject* parent = nullptr);
~MdnsBrowser() override;
public:
static MdnsBrowser& getInstance()
{
static MdnsBrowser* instance = new MdnsBrowser();
return *instance;
}
MdnsBrowser(const MdnsBrowser&) = delete;
MdnsBrowser(MdnsBrowser&&) = delete;
MdnsBrowser& operator=(const MdnsBrowser&) = delete;
MdnsBrowser& operator=(MdnsBrowser&&) = delete;
QMdnsEngine::Service getFirstService(const QByteArray& serviceType, const QString& filter = ".*", const std::chrono::milliseconds waitTime = DEFAULT_DISCOVER_TIMEOUT) const;
QJsonArray getServicesDiscoveredJson(const QByteArray& serviceType, const QString& filter = ".*", const std::chrono::milliseconds waitTime = std::chrono::milliseconds{ 0 }) const;
void printCache(const QByteArray& name = QByteArray(), quint16 type = QMdnsEngine::ANY) const;
public slots:
///
/// @brief Browse for a service of type
///
void browseForServiceType(const QByteArray& serviceType);
QHostAddress getHostFirstAddress(const QByteArray& hostname);
void onHostNameResolved(const QHostAddress& address);
QMdnsEngine::Record getServiceInstanceRecord(const QByteArray& serviceInstance, const std::chrono::milliseconds waitTime = DEFAULT_DISCOVER_TIMEOUT) const;
bool resolveAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress, std::chrono::milliseconds timeout = DEFAULT_ADDRESS_RESOLVE_TIMEOUT);
Q_SIGNALS:
/**
* @brief Indicate that the specified service was updated
*
* This signal is emitted when the SRV record for a service (identified by
* its name and type) or a TXT record has changed.
*/
void serviceFound(const QMdnsEngine::Service& service);
/**
* @brief Indicate that the specified service was removed
*
* This signal is emitted when an essential record (PTR or SRV) is
* expiring from the cache. This will also occur when an updated PTR or
* SRV record is received with a TTL of 0.
*/
void serviceRemoved(const QMdnsEngine::Service& service);
void addressResolved(const QHostAddress address);
private slots:
void onServiceAdded(const QMdnsEngine::Service& service);
void onServiceUpdated(const QMdnsEngine::Service& service);
void onServiceRemoved(const QMdnsEngine::Service& service);
private:
// template <typename Func1, typename Func2, typename std::enable_if_t<std::is_member_pointer<Func2>::value, int> = 0>
// static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer<Func1>::Object* sender,
// Func1 signal,
// typename QtPrivate::FunctionPointer<Func2>::Object* receiver,
// Func2 slot)
// {
// QMetaObject::Connection conn_normal = QObject::connect(sender, signal, receiver, slot);
// QMetaObject::Connection* conn_delete = new QMetaObject::Connection();
// *conn_delete = QObject::connect(sender, signal, [conn_normal, conn_delete]() {
// QObject::disconnect(conn_normal);
// QObject::disconnect(*conn_delete);
// delete conn_delete;
// });
// return conn_normal;
// }
template <typename Func1, typename Func2, typename std::enable_if_t<!std::is_member_pointer<Func2>::value, int> = 0>
static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer<Func1>::Object* sender,
Func1 signal,
Func2 slot)
{
QMetaObject::Connection conn_normal = QObject::connect(sender, signal, slot);
QMetaObject::Connection* conn_delete = new QMetaObject::Connection();
*conn_delete = QObject::connect(sender, signal, [conn_normal, conn_delete]() {
QObject::disconnect(conn_normal);
QObject::disconnect(*conn_delete);
delete conn_delete;
});
return conn_normal;
}
// template <typename Func1, typename Func2, typename std::enable_if_t<!std::is_member_pointer<Func2>::value, int> = 0>
// static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer<Func1>::Object* sender,
// Func1 signal,
// typename QtPrivate::FunctionPointer<Func2>::Object* receiver,
// Func2 slot)
// {
// Q_UNUSED(receiver);
// QMetaObject::Connection conn_normal = QObject::connect(sender, signal, slot);
// QMetaObject::Connection* conn_delete = new QMetaObject::Connection();
// *conn_delete = QObject::connect(sender, signal, [conn_normal, conn_delete]() {
// QObject::disconnect(conn_normal);
// QObject::disconnect(*conn_delete);
// delete conn_delete;
// });
// return conn_normal;
// }
/// The logger instance for mDNS-Service
Logger* _log;
QMdnsEngine::Server _server;
QMdnsEngine::Cache _cache;
QMap<QByteArray, QMdnsEngine::Browser*> _browsedServiceTypes;
};
#endif // MDNSBROWSER_H

View File

@@ -0,0 +1,51 @@
#ifndef MDNSPROVIDER_H
#define MDNSPROVIDER_H
#include <qmdnsengine/server.h>
#include <qmdnsengine/hostname.h>
#include <qmdnsengine/provider.h>
#include <qmdnsengine/service.h>
// Qt includes
#include <QObject>
#include <QByteArray>
// Utility includes
#include <utils/Logger.h>
class MdnsProvider : public QObject
{
public:
MdnsProvider(QObject* parent = nullptr);
~MdnsProvider() override;
QList<QByteArray> getServiceTypesProvided() const { return _providedServiceTypes.keys(); }
public slots:
///
/// @brief Init MdnsProvider after thread start
///
void init();
void publishService (const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = "");
private slots:
void onHostnameChanged(const QByteArray& hostname);
private:
/// The logger instance for mDNS-Service
Logger* _log;
QMdnsEngine::Server* _server;
QMdnsEngine::Hostname* _hostname;
/// map of services provided
QMap<QByteArray, QMdnsEngine::Provider*> _providedServiceTypes;
};
#endif // MDNSPROVIDER_H

View File

@@ -0,0 +1,38 @@
#ifndef MDNSSERVICEREGISTER_H
#define MDNSSERVICEREGISTER_H
#include <QByteArray>
#include <QMap>
struct mdnsConfig
{
QByteArray serviceType;
QString serviceNameFilter;
};
typedef QMap<QString, mdnsConfig> MdnsServiceMap;
const MdnsServiceMap mDnsServiceMap = {
//Hyperion
{"jsonapi" , {"_hyperiond-json._tcp.local.", ".*"}},
{"flatbuffer" , {"_hyperiond-flatbuf._tcp.local.", ".*"}},
{"protobuffer" , {"_hyperiond-protobuf._tcp.local.", ".*"}},
{"http" , {"_http._tcp.local.", ".*"}},
{"https" , {"_https._tcp.local.", ".*"}},
//LED Devices
{"cololight" , {"_hap._tcp.local.", "ColoLight.*"}},
{"nanoleaf" , {"_nanoleafapi._tcp.local.", ".*"}},
{"philipshue" , {"_hue._tcp.local.", ".*"}},
{"wled" , {"_wled._tcp.local.", ".*"}},
{"yeelight" , {"_hap._tcp.local.", "Yeelight.*|YLBulb.*"}},
};
class MdnsServiceRegister {
public:
static QByteArray getServiceType(const QString &serviceType) { return mDnsServiceMap[serviceType].serviceType; }
static QString getServiceNameFilter(const QString &serviceType) { return mDnsServiceMap[serviceType].serviceNameFilter; }
static MdnsServiceMap getAllConfigs () { return mDnsServiceMap; }
};
#endif // MDNSSERVICEREGISTER_H

View File

@@ -24,6 +24,12 @@ public:
ProtoServer(const QJsonDocument& config, QObject* parent = nullptr);
~ProtoServer() override;
signals:
///
/// @emits whenever the server would like to announce its service details
///
void publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = "");
public slots:
///
/// @brief Handle settings update

View File

@@ -31,6 +31,30 @@ struct ColorRgb
static const ColorRgb YELLOW;
/// 'White' RgbColor (255, 255, 255)
static const ColorRgb WHITE;
ColorRgb() = default;
ColorRgb(uint8_t _red, uint8_t _green,uint8_t _blue):
red(_red),
green(_green),
blue(_blue)
{
}
ColorRgb operator-(const ColorRgb& b) const
{
ColorRgb a(*this);
a.red -= b.red;
a.green -= b.green;
a.blue -= b.blue;
return a;
}
QString toQString() const
{
return QString("(%1,%2,%3)").arg(red).arg(green).arg(blue);
}
};
/// Assert to ensure that the size of the structure is 'only' 3 bytes

View File

@@ -1,12 +1,17 @@
#pragma once
#include <utils/Logger.h>
#include <QTcpServer>
#include <QUrl>
#include <QHostAddress>
#include <QHostInfo>
#include <HyperionConfig.h>
#include <utils/Logger.h>
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#endif
namespace NetUtils {
const int MAX_PORT = 65535;
@@ -38,15 +43,15 @@ namespace NetUtils {
///
/// @brief Check if the port is in the valid range
/// @param log The logger of the caller to print///
/// @param[in] port The port to be tested
/// @param[in] port The port to be tested (port = -1 is ignored for testing)
/// @param[in] host A hostname/IP-address to make reference to during logging
/// @return True on success else false
///
inline bool isValidPort(Logger* log, int port, const QString& host)
{
if (port <= 0 || port > MAX_PORT)
if ((port <= 0 || port > MAX_PORT) && port != -1)
{
Error(log, "Invalid port [%d] for host: %s!", port, QSTRING_CSTR(host));
Error(log, "Invalid port [%d] for host: (%s)! - Port must be in range [0 - %d]", port, QSTRING_CSTR(host), MAX_PORT);
return false;
}
return true;
@@ -59,7 +64,7 @@ namespace NetUtils {
/// @param[in/out] port The resolved port, if available.
/// @return True on success else false
///
inline bool resolveHostPort(const QString& address, QString& host, quint16& port)
inline bool resolveHostPort(const QString& address, QString& host, int& port)
{
if (address.isEmpty())
{
@@ -91,38 +96,109 @@ namespace NetUtils {
}
///
/// @brief Check if the port is in the valid range
/// @param log The logger of the caller to print
/// @param[in] address The port to be tested
/// @param[out] hostAddress A hostname to make reference to during logging
/// @return True on success else false
/// @brief Resolve a hostname (DNS/mDNS) into an IP-address. A given IP address will be passed through
/// @param[in/out] log The logger of the caller to print
/// @param[in] hostname The hostname to be resolved
/// @param[out] hostAddress The resolved IP-Address
/// @return True on success else false
///
inline bool resolveHostAddress(Logger* log, const QString& address, QHostAddress& hostAddress)
inline bool resolveMdDnsHostToAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress)
{
bool isHostAddressOK{ false };
if (hostAddress.setAddress(address))
if (!hostname.isEmpty())
{
Debug(log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(hostAddress.toString()));
isHostAddressOK = true;
}
else
{
QHostInfo hostInfo = QHostInfo::fromName(address);
if (hostInfo.error() == QHostInfo::NoError)
#ifdef ENABLE_MDNS
if (hostname.endsWith(".local") || hostname.endsWith(".local."))
{
hostAddress = hostInfo.addresses().first();
Debug(log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(hostAddress.toString()), QSTRING_CSTR(address));
isHostAddressOK = true;
QHostAddress resolvedAddress;
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "resolveAddress",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, isHostAddressOK),
Q_ARG(Logger*, log), Q_ARG(QString, hostname), Q_ARG(QHostAddress&, resolvedAddress));
hostAddress = resolvedAddress;
}
else
#endif
{
QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(address).arg(hostInfo.error()).arg(hostInfo.errorString());
Error(log, "%s", QSTRING_CSTR(errortext));
isHostAddressOK = false;
if (hostAddress.setAddress(hostname))
{
//Debug(log, "IP-address (%s) not required to be resolved.", QSTRING_CSTR(hostAddress.toString()));
isHostAddressOK = true;
}
else
{
QHostInfo hostInfo = QHostInfo::fromName(hostname);
if (hostInfo.error() == QHostInfo::NoError)
{
hostAddress = hostInfo.addresses().at(0);
Debug(log, "Successfully resolved hostname (%s) to IP-address (%s)", QSTRING_CSTR(hostname), QSTRING_CSTR(hostAddress.toString()));
isHostAddressOK = true;
}
else
{
QString errortext = QString("Failed resolving hostname (%1) to IP-address. Error: (%2) %3").arg(hostname).arg(hostInfo.error()).arg(hostInfo.errorString());
Error(log, "%s", QSTRING_CSTR(errortext));
isHostAddressOK = false;
}
}
}
}
return isHostAddressOK;
}
}
///
/// @brief Resolve a hostname(DNS) or mDNS service name into an IP-address. A given IP address will be passed through
/// @param[in/out] log The logger of the caller to print
/// @param[in] hostname The hostname/mDNS service name to be resolved
/// @param[out] hostAddress The resolved IP-Address
/// @param[in/out] port The port provided by the mDNS service, if not mDNS the input port is returned
/// @return True on success else false
///
inline bool resolveHostToAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress, int& port)
{
bool areHostAddressPartOK{ false };
QString target {hostname};
#ifdef ENABLE_MDNS
if (hostname.endsWith("._tcp.local"))
{
//Treat hostname as service instance name that requires to be resolved into an mDNS-Hostname first
QMdnsEngine::Record service = MdnsBrowser::getInstance().getServiceInstanceRecord(hostname.toUtf8());
if (!service.target().isEmpty())
{
Info(log, "Resolved service [%s] to mDNS hostname [%s], service port [%d]", QSTRING_CSTR(hostname), service.target().constData(), service.port());
target = service.target();
port = service.port();
}
else
{
Error(log, "Cannot resolve mDNS hostname for given service [%s]!", QSTRING_CSTR(hostname));
return areHostAddressPartOK;
}
}
#endif
QHostAddress resolvedAddress;
if (NetUtils::resolveMdDnsHostToAddress(log, target, resolvedAddress))
{
hostAddress = resolvedAddress;
if (hostname != hostAddress.toString())
{
Info(log, "Resolved hostname (%s) to IP-address (%s)", QSTRING_CSTR(hostname), QSTRING_CSTR(hostAddress.toString()));
}
if (NetUtils::isValidPort(log, port, hostAddress.toString()))
{
areHostAddressPartOK = true;
}
}
return areHostAddressPartOK;
}
inline bool resolveHostToAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress)
{
int ignoredPort {MAX_PORT};
return resolveHostToAddress(log, hostname, hostAddress, ignoredPort);
}
}

View File

@@ -11,7 +11,6 @@
// settings
#include <utils/settings.h>
class BonjourServiceRegister;
class StaticFileServing;
class QtHttpServer;
@@ -52,6 +51,11 @@ signals:
///
void portChanged(quint16 port);
///
/// @emits whenever the server would like to announce its service details
///
void publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = "");
public slots:
///
/// @brief Init server after thread start
@@ -93,8 +97,6 @@ private:
const QString WEBSERVER_DEFAULT_CRT_PATH = ":/hyperion.crt";
const QString WEBSERVER_DEFAULT_KEY_PATH = ":/hyperion.key";
quint16 WEBSERVER_DEFAULT_PORT = 8090;
BonjourServiceRegister * _serviceRegister = nullptr;
};
#endif // WEBSERVER_H