mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
Merge remote-tracking branch 'upstream/master' into temperture
This commit is contained in:
@@ -294,13 +294,6 @@ bool API::setHyperionInstance(quint8 inst)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<hyperion::Components, bool> API::getAllComponents()
|
||||
{
|
||||
std::map<hyperion::Components, bool> comps;
|
||||
//QMetaObject::invokeMethod(_hyperion, "getAllComponents", Qt::BlockingQueuedConnection, Q_RETURN_ARG(std::map<hyperion::Components, bool>, comps));
|
||||
return comps;
|
||||
}
|
||||
|
||||
bool API::isHyperionEnabled()
|
||||
{
|
||||
int res;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"component":
|
||||
{
|
||||
"type" : "string",
|
||||
"enum" : ["ALL", "SMOOTHING", "BLACKBORDER", "FORWARDER", "BOBLIGHTSERVER", "GRABBER", "V4L", "LEDDEVICE"],
|
||||
"enum" : ["ALL", "SMOOTHING", "BLACKBORDER", "FORWARDER", "BOBLIGHTSERVER", "GRABBER", "V4L", "AUDIO", "LEDDEVICE"],
|
||||
"required": true
|
||||
},
|
||||
"state":
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"mappingType": {
|
||||
"type" : "string",
|
||||
"enum" : ["multicolor_mean", "unicolor_mean"]
|
||||
"enum" : ["multicolor_mean", "unicolor_mean", "multicolor_mean_squared", "dominant_color", "dominant_color_advanced"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -21,12 +21,26 @@
|
||||
#include <hyperion/GrabberWrapper.h>
|
||||
#include <grabber/QtGrabber.h>
|
||||
|
||||
#include <utils/WeakConnect.h>
|
||||
|
||||
#if defined(ENABLE_MF)
|
||||
#include <grabber/MFGrabber.h>
|
||||
#elif defined(ENABLE_V4L2)
|
||||
#include <grabber/V4L2Grabber.h>
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_AUDIO)
|
||||
#include <grabber/AudioGrabber.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <grabber/AudioGrabberWindows.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <grabber/AudioGrabberLinux.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_X11)
|
||||
#include <grabber/X11Grabber.h>
|
||||
#endif
|
||||
@@ -145,7 +159,6 @@ void JsonAPI::handleMessage(const QString &messageString, const QString &httpAut
|
||||
{
|
||||
const QString ident = "JsonRpc@" + _peerAddress;
|
||||
QJsonObject message;
|
||||
//std::cout << "JsonAPI::handleMessage | [" << static_cast<int>(_hyperion->getInstanceIndex()) << "] Received: ["<< messageString.toStdString() << "]" << std::endl;
|
||||
|
||||
// parse the message
|
||||
if (!JsonUtils::parse(ident, messageString, message, _log))
|
||||
@@ -556,27 +569,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
|
||||
info["ledDevices"] = ledDevices;
|
||||
|
||||
QJsonObject grabbers;
|
||||
|
||||
// *** Deprecated ***
|
||||
//QJsonArray availableGrabbers;
|
||||
//if ( GrabberWrapper::getInstance() != nullptr )
|
||||
//{
|
||||
// QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex());
|
||||
// QJsonArray activeGrabberNames;
|
||||
// for (auto grabberName : activeGrabbers)
|
||||
// {
|
||||
// activeGrabberNames.append(grabberName);
|
||||
// }
|
||||
|
||||
// grabbers["active"] = activeGrabberNames;
|
||||
//}
|
||||
//for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::ALL))
|
||||
//{
|
||||
// availableGrabbers.append(grabber);
|
||||
//}
|
||||
|
||||
//grabbers["available"] = availableGrabbers;
|
||||
|
||||
// SCREEN
|
||||
QJsonObject screenGrabbers;
|
||||
if (GrabberWrapper::getInstance() != nullptr)
|
||||
{
|
||||
@@ -596,6 +589,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
|
||||
}
|
||||
screenGrabbers["available"] = availableScreenGrabbers;
|
||||
|
||||
// VIDEO
|
||||
QJsonObject videoGrabbers;
|
||||
if (GrabberWrapper::getInstance() != nullptr)
|
||||
{
|
||||
@@ -615,8 +609,31 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
|
||||
}
|
||||
videoGrabbers["available"] = availableVideoGrabbers;
|
||||
|
||||
// AUDIO
|
||||
QJsonObject audioGrabbers;
|
||||
if (GrabberWrapper::getInstance() != nullptr)
|
||||
{
|
||||
QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::AUDIO);
|
||||
|
||||
QJsonArray activeGrabberNames;
|
||||
for (auto grabberName : activeGrabbers)
|
||||
{
|
||||
activeGrabberNames.append(grabberName);
|
||||
}
|
||||
|
||||
audioGrabbers["active"] = activeGrabberNames;
|
||||
}
|
||||
QJsonArray availableAudioGrabbers;
|
||||
for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::AUDIO))
|
||||
{
|
||||
availableAudioGrabbers.append(grabber);
|
||||
}
|
||||
audioGrabbers["available"] = availableAudioGrabbers;
|
||||
|
||||
grabbers.insert("screen", screenGrabbers);
|
||||
grabbers.insert("video", videoGrabbers);
|
||||
grabbers.insert("audio", audioGrabbers);
|
||||
|
||||
info["grabbers"] = grabbers;
|
||||
|
||||
info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode()));
|
||||
@@ -690,7 +707,6 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
|
||||
QJsonObject obj;
|
||||
obj.insert("friendly_name", entry["friendly_name"].toString());
|
||||
obj.insert("instance", entry["instance"].toInt());
|
||||
//obj.insert("last_use", entry["last_use"].toString());
|
||||
obj.insert("running", entry["running"].toBool());
|
||||
instanceInfo.append(obj);
|
||||
}
|
||||
@@ -699,7 +715,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
|
||||
// add leds configs
|
||||
info["leds"] = _hyperion->getSetting(settings::LEDS).array();
|
||||
|
||||
// BEGIN | The following entries are derecated but used to ensure backward compatibility with hyperion Classic remote control
|
||||
// BEGIN | The following entries are deprecated but used to ensure backward compatibility with hyperion Classic remote control
|
||||
// TODO Output the real transformation information instead of default
|
||||
|
||||
// HOST NAME
|
||||
@@ -760,7 +776,6 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
|
||||
const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority());
|
||||
if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
|
||||
{
|
||||
QJsonObject LEDcolor;
|
||||
// check if LED Color not Black (0,0,0)
|
||||
if ((priorityInfo.ledColors.begin()->red +
|
||||
priorityInfo.ledColors.begin()->green +
|
||||
@@ -1323,8 +1338,8 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &
|
||||
// use comment
|
||||
// for user authorized sessions
|
||||
AuthManager::AuthDefinition def;
|
||||
const QString res = API::createToken(comment, def);
|
||||
if (res.isEmpty())
|
||||
const QString createTokenResult = API::createToken(comment, def);
|
||||
if (createTokenResult.isEmpty())
|
||||
{
|
||||
QJsonObject newTok;
|
||||
newTok["comment"] = def.comment;
|
||||
@@ -1334,7 +1349,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &
|
||||
sendSuccessDataReply(QJsonDocument(newTok), command + "-" + subc, tan);
|
||||
return;
|
||||
}
|
||||
sendErrorReply(res, command + "-" + subc, tan);
|
||||
sendErrorReply(createTokenResult, command + "-" + subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1342,13 +1357,13 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &
|
||||
if (subc == "renameToken")
|
||||
{
|
||||
// use id/comment
|
||||
const QString res = API::renameToken(id, comment);
|
||||
if (res.isEmpty())
|
||||
const QString renameTokenResult = API::renameToken(id, comment);
|
||||
if (renameTokenResult.isEmpty())
|
||||
{
|
||||
sendSuccessReply(command + "-" + subc, tan);
|
||||
return;
|
||||
}
|
||||
sendErrorReply(res, command + "-" + subc, tan);
|
||||
sendErrorReply(renameTokenResult, command + "-" + subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1356,13 +1371,13 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &
|
||||
if (subc == "deleteToken")
|
||||
{
|
||||
// use id
|
||||
const QString res = API::deleteToken(id);
|
||||
if (res.isEmpty())
|
||||
const QString deleteTokenResult = API::deleteToken(id);
|
||||
if (deleteTokenResult.isEmpty())
|
||||
{
|
||||
sendSuccessReply(command + "-" + subc, tan);
|
||||
return;
|
||||
}
|
||||
sendErrorReply(res, command + "-" + subc, tan);
|
||||
sendErrorReply(deleteTokenResult, command + "-" + subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1370,7 +1385,6 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &
|
||||
if (subc == "requestToken")
|
||||
{
|
||||
// use id/comment
|
||||
const QString &comment = message["comment"].toString().trimmed();
|
||||
const bool &acc = message["accept"].toBool(true);
|
||||
if (acc)
|
||||
API::setNewTokenRequest(comment, id, tan);
|
||||
@@ -1387,7 +1401,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &
|
||||
if (API::getPendingTokenRequests(vec))
|
||||
{
|
||||
QJsonArray arr;
|
||||
for (const auto &entry : vec)
|
||||
for (const auto &entry : qAsConst(vec))
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["comment"] = entry.comment;
|
||||
@@ -1492,7 +1506,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &
|
||||
void JsonAPI::handleInstanceCommand(const QJsonObject &message, const QString &command, int tan)
|
||||
{
|
||||
const QString &subc = message["subcommand"].toString();
|
||||
const quint8 &inst = message["instance"].toInt();
|
||||
const quint8 &inst = static_cast<quint8>(message["instance"].toInt());
|
||||
const QString &name = message["name"].toString();
|
||||
|
||||
if (subc == "switchTo")
|
||||
@@ -1510,7 +1524,12 @@ void JsonAPI::handleInstanceCommand(const QJsonObject &message, const QString &c
|
||||
|
||||
if (subc == "startInstance")
|
||||
{
|
||||
connect(this, &API::onStartInstanceResponse, [=] (const int &tan) { sendSuccessReply(command + "-" + subc, tan); });
|
||||
//Only send update once
|
||||
weakConnect(this, &API::onStartInstanceResponse, [this, command, subc] (int tan)
|
||||
{
|
||||
sendSuccessReply(command + "-" + subc, tan);
|
||||
});
|
||||
|
||||
if (!API::startInstance(inst, tan))
|
||||
sendErrorReply("Can't start Hyperion instance index " + QString::number(inst), command + "-" + subc, tan);
|
||||
|
||||
@@ -1570,12 +1589,8 @@ void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString &
|
||||
QString full_command = command + "-" + subc;
|
||||
|
||||
// TODO: Validate that device type is a valid one
|
||||
/* if ( ! valid type )
|
||||
|
||||
{
|
||||
sendErrorReply("Unknown device", full_command, tan);
|
||||
}
|
||||
else
|
||||
*/ {
|
||||
QJsonObject config;
|
||||
config.insert("type", devType);
|
||||
LedDevice* ledDevice = nullptr;
|
||||
@@ -1637,17 +1652,13 @@ void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const QString
|
||||
QString full_command = command + "-" + subc;
|
||||
|
||||
// TODO: Validate that source type is a valid one
|
||||
/* if ( ! valid type )
|
||||
{
|
||||
sendErrorReply("Unknown device", full_command, tan);
|
||||
}
|
||||
else
|
||||
*/ {
|
||||
if (subc == "discover")
|
||||
{
|
||||
QJsonObject inputSourcesDiscovered;
|
||||
inputSourcesDiscovered.insert("sourceType", sourceType);
|
||||
QJsonArray videoInputs;
|
||||
QJsonArray audioInputs;
|
||||
|
||||
#if defined(ENABLE_V4L2) || defined(ENABLE_MF)
|
||||
|
||||
@@ -1664,6 +1675,24 @@ void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const QString
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_AUDIO)
|
||||
if (sourceType == "audio")
|
||||
{
|
||||
AudioGrabber* grabber;
|
||||
#ifdef WIN32
|
||||
grabber = new AudioGrabberWindows();
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
grabber = new AudioGrabberLinux();
|
||||
#endif
|
||||
QJsonObject params;
|
||||
audioInputs = grabber->discover(params);
|
||||
delete grabber;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
DebugIf(verbose, _log, "sourceType: [%s]", QSTRING_CSTR(sourceType));
|
||||
|
||||
@@ -1760,6 +1789,7 @@ void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const QString
|
||||
|
||||
}
|
||||
inputSourcesDiscovered["video_sources"] = videoInputs;
|
||||
inputSourcesDiscovered["audio_sources"] = audioInputs;
|
||||
|
||||
DebugIf(verbose, _log, "response: [%s]", QString(QJsonDocument(inputSourcesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
||||
|
||||
@@ -1873,6 +1903,7 @@ void JsonAPI::sendSuccessReply(const QString &command, int tan)
|
||||
{
|
||||
// create reply
|
||||
QJsonObject reply;
|
||||
reply["instance"] = _hyperion->getInstanceIndex();
|
||||
reply["success"] = true;
|
||||
reply["command"] = command;
|
||||
reply["tan"] = tan;
|
||||
@@ -1884,6 +1915,7 @@ void JsonAPI::sendSuccessReply(const QString &command, int tan)
|
||||
void JsonAPI::sendSuccessDataReply(const QJsonDocument &doc, const QString &command, int tan)
|
||||
{
|
||||
QJsonObject reply;
|
||||
reply["instance"] = _hyperion->getInstanceIndex();
|
||||
reply["success"] = true;
|
||||
reply["command"] = command;
|
||||
reply["tan"] = tan;
|
||||
@@ -1899,6 +1931,7 @@ void JsonAPI::sendErrorReply(const QString &error, const QString &command, int t
|
||||
{
|
||||
// create reply
|
||||
QJsonObject reply;
|
||||
reply["instance"] = _hyperion->getInstanceIndex();
|
||||
reply["success"] = false;
|
||||
reply["error"] = error;
|
||||
reply["command"] = command;
|
||||
@@ -2021,6 +2054,11 @@ void JsonAPI::handleInstanceStateChange(InstanceState state, quint8 instance, co
|
||||
handleInstanceSwitch();
|
||||
}
|
||||
break;
|
||||
|
||||
case InstanceState::H_STARTED:
|
||||
case InstanceState::H_STOPPED:
|
||||
case InstanceState::H_CREATED:
|
||||
case InstanceState::H_DELETED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -148,7 +148,6 @@ void JsonCB::resetSubscriptions()
|
||||
void JsonCB::setSubscriptionsTo(Hyperion* hyperion)
|
||||
{
|
||||
assert(hyperion);
|
||||
//std::cout << "JsonCB::setSubscriptions for instance [" << static_cast<int>(hyperion->getInstanceIndex()) << "] " << std::endl;
|
||||
|
||||
// get current subs
|
||||
QStringList currSubs(getSubscribedCommands());
|
||||
@@ -179,8 +178,6 @@ void JsonCB::doCallback(const QString& cmd, const QVariant& data)
|
||||
else
|
||||
obj["data"] = data.toJsonObject();
|
||||
|
||||
//std::cout << "JsonCB::doCallback | [" << static_cast<int>(_hyperion->getInstanceIndex()) << "] Send: [" << QJsonDocument(obj).toJson(QJsonDocument::Compact).toStdString() << "]" << std::endl;
|
||||
|
||||
emit newCallback(obj);
|
||||
}
|
||||
|
||||
@@ -398,7 +395,6 @@ void JsonCB::handleInstanceChange()
|
||||
QJsonObject obj;
|
||||
obj.insert("friendly_name", entry["friendly_name"].toString());
|
||||
obj.insert("instance", entry["instance"].toInt());
|
||||
//obj.insert("last_use", entry["last_use"].toString());
|
||||
obj.insert("running", entry["running"].toBool());
|
||||
arr.append(obj);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,5 @@ uint8_t BlackBorderDetector::calculateThreshold(double threshold) const
|
||||
|
||||
uint8_t blackborderThreshold = uint8_t(rgbThreshold);
|
||||
|
||||
//Debug(Logger::getInstance("BLACKBORDER"), "threshold set to %f (%d)", threshold , int(blackborderThreshold));
|
||||
|
||||
return blackborderThreshold;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
#include <hyperion/Hyperion.h>
|
||||
|
||||
@@ -33,6 +34,8 @@ BlackBorderProcessor::BlackBorderProcessor(Hyperion* hyperion, QObject* parent)
|
||||
|
||||
// listen for component state changes
|
||||
connect(_hyperion, &Hyperion::compStateChangeRequest, this, &BlackBorderProcessor::handleCompStateChangeRequest);
|
||||
|
||||
_detector = new BlackBorderDetector(_oldThreshold);
|
||||
}
|
||||
|
||||
BlackBorderProcessor::~BlackBorderProcessor()
|
||||
@@ -60,7 +63,7 @@ void BlackBorderProcessor::handleSettingsUpdate(settings::type type, const QJson
|
||||
_detectionMode = obj["mode"].toString("default");
|
||||
const double newThreshold = obj["threshold"].toDouble(5.0) / 100.0;
|
||||
|
||||
if (_oldThreshold != newThreshold)
|
||||
if (fabs(_oldThreshold - newThreshold) > std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
_oldThreshold = newThreshold;
|
||||
|
||||
@@ -140,8 +143,6 @@ bool BlackBorderProcessor::updateBorder(const BlackBorder & newDetectedBorder)
|
||||
// makes it look like the border detectionn is not working - since the new 3 line detection algorithm is more precise this became a problem specialy in dark scenes
|
||||
// wisc
|
||||
|
||||
// std::cout << "c: " << setw(2) << _currentBorder.verticalSize << " " << setw(2) << _currentBorder.horizontalSize << " p: " << setw(2) << _previousDetectedBorder.verticalSize << " " << setw(2) << _previousDetectedBorder.horizontalSize << " n: " << setw(2) << newDetectedBorder.verticalSize << " " << setw(2) << newDetectedBorder.horizontalSize << " c:i " << setw(2) << _consistentCnt << ":" << setw(2) << _inconsistentCnt << std::endl;
|
||||
|
||||
// set the consistency counter
|
||||
if (newDetectedBorder == _previousDetectedBorder)
|
||||
{
|
||||
|
||||
@@ -106,8 +106,6 @@ QString BoblightClientConnection::readMessage(const char* data, const size_t siz
|
||||
const int len = end - data + 1;
|
||||
const QString message = QString::fromLatin1(data, len);
|
||||
|
||||
//std::cout << bytes << ": \"" << message.toUtf8().constData() << "\"" << std::endl;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -124,7 +122,6 @@ void BoblightClientConnection::socketClosed()
|
||||
|
||||
void BoblightClientConnection::handleMessage(const QString& message)
|
||||
{
|
||||
//std::cout << "boblight message: " << message.toStdString() << std::endl;
|
||||
QStringList messageParts = QStringUtils::split(message, ' ', QStringUtils::SplitBehavior::SkipEmptyParts);
|
||||
if (!messageParts.isEmpty())
|
||||
{
|
||||
@@ -340,7 +337,6 @@ float BoblightClientConnection::parseFloat(const QString& s, bool *ok) const
|
||||
{
|
||||
if (ok)
|
||||
{
|
||||
//std::cout << "FAIL L " << q << ": " << s.toUtf8().constData() << std::endl;
|
||||
*ok = false;
|
||||
}
|
||||
return 0;
|
||||
@@ -348,7 +344,6 @@ float BoblightClientConnection::parseFloat(const QString& s, bool *ok) const
|
||||
|
||||
if (ok)
|
||||
{
|
||||
//std::cout << "OK " << d << ": " << s.toUtf8().constData() << std::endl;
|
||||
*ok = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <QFile>
|
||||
|
||||
/* Enable to turn on detailed CEC logs */
|
||||
// #define VERBOSE_CEC
|
||||
#define NO_VERBOSE_CEC
|
||||
|
||||
CECHandler::CECHandler()
|
||||
{
|
||||
@@ -138,9 +138,9 @@ bool CECHandler::openAdapter(const CECAdapterDescriptor & descriptor)
|
||||
|
||||
if(!_cecAdapter->Open(descriptor.strComName))
|
||||
{
|
||||
Error(_logger, QString("Failed to open the CEC adaper on port %1")
|
||||
.arg(descriptor.strComName)
|
||||
.toLocal8Bit());
|
||||
Error(_logger, "%s", QSTRING_CSTR(QString("Failed to open the CEC adaper on port %1")
|
||||
.arg(descriptor.strComName))
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -149,9 +149,9 @@ bool CECHandler::openAdapter(const CECAdapterDescriptor & descriptor)
|
||||
|
||||
void CECHandler::printAdapter(const CECAdapterDescriptor & descriptor) const
|
||||
{
|
||||
Info(_logger, QString("CEC Adapter:").toLocal8Bit());
|
||||
Info(_logger, QString("\tName : %1").arg(descriptor.strComName).toLocal8Bit());
|
||||
Info(_logger, QString("\tPath : %1").arg(descriptor.strComPath).toLocal8Bit());
|
||||
Info(_logger, "%s", QSTRING_CSTR(QString("CEC Adapter:")));
|
||||
Info(_logger, "%s", QSTRING_CSTR(QString("\tName : %1").arg(descriptor.strComName)));
|
||||
Info(_logger, "%s", QSTRING_CSTR(QString("\tPath : %1").arg(descriptor.strComPath)));
|
||||
}
|
||||
|
||||
QString CECHandler::scan() const
|
||||
@@ -180,12 +180,12 @@ QString CECHandler::scan() const
|
||||
|
||||
devices << device;
|
||||
|
||||
Info(_logger, QString("\tCECDevice: %1 / %2 / %3 / %4")
|
||||
.arg(device["name"].toString())
|
||||
.arg(device["vendor"].toString())
|
||||
.arg(device["address"].toString())
|
||||
.arg(device["power"].toString())
|
||||
.toLocal8Bit());
|
||||
Info(_logger, "%s", QSTRING_CSTR(QString("\tCECDevice: %1 / %2 / %3 / %4")
|
||||
.arg(device["name"].toString(),
|
||||
device["vendor"].toString(),
|
||||
device["address"].toString(),
|
||||
device["power"].toString()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,16 +305,16 @@ void CECHandler::onCecCommandReceived(void * context, const CECCommand * command
|
||||
{
|
||||
if (command->opcode == CEC::CEC_OPCODE_SET_STREAM_PATH)
|
||||
{
|
||||
Info(handler->_logger, QString("CEC source activated: %1")
|
||||
.arg(adapter->ToString(command->initiator))
|
||||
.toLocal8Bit());
|
||||
Info(handler->_logger, "%s", QSTRING_CSTR(QString("CEC source activated: %1")
|
||||
.arg(adapter->ToString(command->initiator)))
|
||||
);
|
||||
emit handler->cecEvent(CECEvent::On);
|
||||
}
|
||||
if (command->opcode == CEC::CEC_OPCODE_STANDBY)
|
||||
{
|
||||
Info(handler->_logger, QString("CEC source deactivated: %1")
|
||||
.arg(adapter->ToString(command->initiator))
|
||||
.toLocal8Bit());
|
||||
Info(handler->_logger, "%s", QSTRING_CSTR(QString("CEC source deactivated: %1")
|
||||
.arg(adapter->ToString(command->initiator)))
|
||||
);
|
||||
emit handler->cecEvent(CECEvent::Off);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ QSqlDatabase DBManager::getDB() const
|
||||
db.setDatabaseName(_rootPath+"/db/"+_dbn+".db");
|
||||
if(!db.open())
|
||||
{
|
||||
Error(_log, QSTRING_CSTR(db.lastError().text()));
|
||||
Error(_log, "%s", QSTRING_CSTR(db.lastError().text()));
|
||||
throw std::runtime_error("Failed to open database connection!");
|
||||
}
|
||||
return db;
|
||||
|
||||
@@ -121,17 +121,21 @@ void EffectEngine::handleUpdatedEffectList()
|
||||
// add smoothing configurations to Hyperion
|
||||
if (def.args["smoothing-custom-settings"].toBool())
|
||||
{
|
||||
int settlingTime_ms = def.args["smoothing-time_ms"].toInt();
|
||||
double ledUpdateFrequency_hz = def.args["smoothing-updateFrequency"].toDouble();
|
||||
unsigned updateDelay {0};
|
||||
|
||||
Debug(_log, "Effect \"%s\": Add custom smoothing settings [%d]. Type: Linear, Settling time: %dms, Interval: %.fHz ", QSTRING_CSTR(def.name), specificId, settlingTime_ms, ledUpdateFrequency_hz);
|
||||
|
||||
def.smoothCfg = _hyperion->updateSmoothingConfig(
|
||||
++specificId,
|
||||
def.args["smoothing-time_ms"].toInt(),
|
||||
def.args["smoothing-updateFrequency"].toDouble(),
|
||||
0 );
|
||||
//Debug( _log, "Customs Settings: Update effect %s, script %s, file %s, smoothCfg [%u]", QSTRING_CSTR(def.name), QSTRING_CSTR(def.script), QSTRING_CSTR(def.file), def.smoothCfg);
|
||||
++specificId,
|
||||
settlingTime_ms,
|
||||
ledUpdateFrequency_hz,
|
||||
updateDelay );
|
||||
}
|
||||
else
|
||||
{
|
||||
def.smoothCfg = SmoothingConfigID::SYSTEM;
|
||||
//Debug( _log, "Default Settings: Update effect %s, script %s, file %s, smoothCfg [%u]", QSTRING_CSTR(def.name), QSTRING_CSTR(def.script), QSTRING_CSTR(def.file), def.smoothCfg);
|
||||
}
|
||||
_availableEffects.push_back(def);
|
||||
}
|
||||
@@ -157,11 +161,18 @@ int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args,
|
||||
//In case smoothing information is provided dynamically use temp smoothing config item (2)
|
||||
if (smoothCfg == SmoothingConfigID::SYSTEM && args["smoothing-custom-settings"].toBool())
|
||||
{
|
||||
int settlingTime_ms = args["smoothing-time_ms"].toInt();
|
||||
double ledUpdateFrequency_hz = args["smoothing-updateFrequency"].toDouble();
|
||||
unsigned updateDelay {0};
|
||||
|
||||
Debug(_log, "Effect \"%s\": Apply dynamic smoothing settings, if smoothing. Type: Linear, Settling time: %dms, Interval: %.fHz ", QSTRING_CSTR(effectName), settlingTime_ms, ledUpdateFrequency_hz);
|
||||
|
||||
smoothCfg = _hyperion->updateSmoothingConfig(
|
||||
SmoothingConfigID::EFFECT_DYNAMIC,
|
||||
args["smoothing-time_ms"].toInt(),
|
||||
args["smoothing-updateFrequency"].toDouble(),
|
||||
0 );
|
||||
SmoothingConfigID::EFFECT_DYNAMIC,
|
||||
settlingTime_ms,
|
||||
ledUpdateFrequency_hz,
|
||||
updateDelay
|
||||
);
|
||||
}
|
||||
|
||||
if (pythonScript.isEmpty())
|
||||
|
||||
@@ -53,9 +53,12 @@ PyObject *EffectModule::json2python(const QJsonValue &jsonData)
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
case QJsonValue::Double:
|
||||
{
|
||||
if (std::round(jsonData.toDouble()) != jsonData.toDouble())
|
||||
double doubleIntegratlPart;
|
||||
double doubleFractionalPart = std::modf(jsonData.toDouble(), &doubleIntegratlPart);
|
||||
if (doubleFractionalPart > std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
return Py_BuildValue("d", jsonData.toDouble());
|
||||
|
||||
}
|
||||
return Py_BuildValue("i", jsonData.toInt());
|
||||
}
|
||||
case QJsonValue::Bool:
|
||||
@@ -184,7 +187,8 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
|
||||
PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args)
|
||||
{
|
||||
// bytearray of values
|
||||
int width, height;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
PyObject * bytearray = nullptr;
|
||||
if (PyArg_ParseTuple(args, "iiO", &width, &height, &bytearray))
|
||||
{
|
||||
@@ -391,8 +395,10 @@ PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
|
||||
int startRY = 0;
|
||||
int startX = 0;
|
||||
int startY = 0;
|
||||
int endX, width = getEffect()->_imageSize.width();
|
||||
int endY, height = getEffect()->_imageSize.height();
|
||||
int width = getEffect()->_imageSize.width();
|
||||
int endX {width};
|
||||
int height = getEffect()->_imageSize.height();
|
||||
int endY {height};
|
||||
int spread = 0;
|
||||
|
||||
bool argsOK = false;
|
||||
@@ -454,7 +460,9 @@ PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
PyObject * bytearray = nullptr;
|
||||
int centerX, centerY, angle;
|
||||
int centerX = 0;
|
||||
int centerY = 0;
|
||||
int angle = 0;
|
||||
int startX = 0;
|
||||
int startY = 0;
|
||||
int width = getEffect()->_imageSize.width();
|
||||
@@ -520,7 +528,13 @@ PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
PyObject * bytearray = nullptr;
|
||||
int centerX, centerY, radius, focalX, focalY, focalRadius, spread;
|
||||
int centerX = 0;
|
||||
int centerY = 0;
|
||||
int radius = 0;
|
||||
int focalX = 0;
|
||||
int focalY = 0;
|
||||
int focalRadius =0;
|
||||
int spread = 0;
|
||||
int startX = 0;
|
||||
int startY = 0;
|
||||
int width = getEffect()->_imageSize.width();
|
||||
@@ -599,7 +613,9 @@ PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args)
|
||||
PyObject * bytearray = nullptr;
|
||||
|
||||
int argCount = PyTuple_Size(args);
|
||||
int r, g, b;
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int a = 255;
|
||||
|
||||
bool argsOK = false;
|
||||
@@ -658,7 +674,9 @@ PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
|
||||
|
||||
QString brush;
|
||||
int argCount = PyTuple_Size(args);
|
||||
int radius, centerX, centerY;
|
||||
int radius = 0;
|
||||
int centerX = 0;
|
||||
int centerY = 0;
|
||||
int startAngle = 0;
|
||||
int spanAngle = 360;
|
||||
int r = 0;
|
||||
@@ -749,7 +767,9 @@ PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
|
||||
PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
int r, g, b;
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int a = 255;
|
||||
int startX = 0;
|
||||
int startY = 0;
|
||||
@@ -788,8 +808,10 @@ PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args)
|
||||
PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
int r, g, b;
|
||||
int a = 255;
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int a = 255;
|
||||
int startX = 0;
|
||||
int startY = 0;
|
||||
int thick = 1;
|
||||
@@ -826,8 +848,12 @@ PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
|
||||
PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
int r, g, b, x, y;
|
||||
int a = 255;
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int a = 255;
|
||||
int thick = 1;
|
||||
|
||||
bool argsOK = false;
|
||||
@@ -859,8 +885,10 @@ PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args)
|
||||
PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
int r, g, b;
|
||||
int a = 255;
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int a = 255;
|
||||
int startX = 0;
|
||||
int startY = 0;
|
||||
int thick = 1;
|
||||
@@ -898,7 +926,11 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
|
||||
PyObject* EffectModule::wrapImageSetPixel(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
int r, g, b, x, y;
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
if ( argCount == 5 && PyArg_ParseTuple(args, "iiiii", &x, &y, &r, &g, &b ) )
|
||||
{
|
||||
@@ -913,7 +945,8 @@ PyObject* EffectModule::wrapImageSetPixel(PyObject *self, PyObject *args)
|
||||
PyObject* EffectModule::wrapImageGetPixel(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
int x, y;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &x, &y) )
|
||||
{
|
||||
@@ -934,7 +967,8 @@ PyObject* EffectModule::wrapImageSave(PyObject *self, PyObject *args)
|
||||
PyObject* EffectModule::wrapImageMinSize(PyObject *self, PyObject *args)
|
||||
{
|
||||
int argCount = PyTuple_Size(args);
|
||||
int w, h;
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
int width = getEffect()->_imageSize.width();
|
||||
int height = getEffect()->_imageSize.height();
|
||||
|
||||
@@ -994,7 +1028,8 @@ PyObject* EffectModule::wrapImageCOffset(PyObject *self, PyObject *args)
|
||||
|
||||
PyObject* EffectModule::wrapImageCShear(PyObject *self, PyObject *args)
|
||||
{
|
||||
int sh,sv;
|
||||
int sh = 0;
|
||||
int sv = 0;
|
||||
int argCount = PyTuple_Size(args);
|
||||
|
||||
if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &sh, &sv ))
|
||||
|
||||
@@ -115,6 +115,9 @@ void MessageForwarder::enableTargets(bool enable, const QJsonObject& config)
|
||||
case hyperion::COMP_V4L:
|
||||
connect(_hyperion, &Hyperion::forwardV4lProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection);
|
||||
break;
|
||||
case hyperion::COMP_AUDIO:
|
||||
connect(_hyperion, &Hyperion::forwardAudioProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection);
|
||||
break;
|
||||
#if defined(ENABLE_FLATBUF_SERVER)
|
||||
case hyperion::COMP_FLATBUFSERVER:
|
||||
#endif
|
||||
@@ -138,7 +141,7 @@ void MessageForwarder::enableTargets(bool enable, const QJsonObject& config)
|
||||
else
|
||||
{
|
||||
_forwarder_enabled = false;
|
||||
Warning(_log,"No JSON- nor Flatbuffer-Forwarder configured -> Forwarding disabled", _forwarder_enabled);
|
||||
Warning(_log,"No JSON- nor Flatbuffer-Forwarder configured -> Forwarding disabled");
|
||||
}
|
||||
}
|
||||
_hyperion->setNewComponentState(hyperion::COMP_FORWARDER, _forwarder_enabled);
|
||||
@@ -153,6 +156,7 @@ void MessageForwarder::handlePriorityChanges(int priority)
|
||||
switch (activeCompId) {
|
||||
case hyperion::COMP_GRABBER:
|
||||
disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardAudioProtoMessage, nullptr, nullptr);
|
||||
#if defined(ENABLE_FLATBUF_SERVER) || defined(ENABLE_PROTOBUF_SERVER)
|
||||
disconnect(_hyperion, &Hyperion::forwardBufferMessage, nullptr, nullptr);
|
||||
#endif
|
||||
@@ -160,11 +164,20 @@ void MessageForwarder::handlePriorityChanges(int priority)
|
||||
break;
|
||||
case hyperion::COMP_V4L:
|
||||
disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardAudioProtoMessage, nullptr, nullptr);
|
||||
#if defined(ENABLE_FLATBUF_SERVER) || defined(ENABLE_PROTOBUF_SERVER)
|
||||
disconnect(_hyperion, &Hyperion::forwardBufferMessage, nullptr, nullptr);
|
||||
#endif
|
||||
connect(_hyperion, &Hyperion::forwardV4lProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection);
|
||||
break;
|
||||
case hyperion::COMP_AUDIO:
|
||||
disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr);
|
||||
#if defined(ENABLE_FLATBUF_SERVER) || defined(ENABLE_PROTOBUF_SERVER)
|
||||
disconnect(_hyperion, &Hyperion::forwardBufferMessage, nullptr, nullptr);
|
||||
#endif
|
||||
connect(_hyperion, &Hyperion::forwardAudioProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection);
|
||||
break;
|
||||
#if defined(ENABLE_FLATBUF_SERVER)
|
||||
case hyperion::COMP_FLATBUFSERVER:
|
||||
#endif
|
||||
@@ -172,6 +185,7 @@ void MessageForwarder::handlePriorityChanges(int priority)
|
||||
case hyperion::COMP_PROTOSERVER:
|
||||
#endif
|
||||
#if defined(ENABLE_FLATBUF_SERVER) || defined(ENABLE_PROTOBUF_SERVER)
|
||||
disconnect(_hyperion, &Hyperion::forwardAudioProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr);
|
||||
connect(_hyperion, &Hyperion::forwardBufferMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection);
|
||||
@@ -180,6 +194,7 @@ void MessageForwarder::handlePriorityChanges(int priority)
|
||||
default:
|
||||
disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardAudioProtoMessage, nullptr, nullptr);
|
||||
#if defined(ENABLE_FLATBUF_SERVER) || defined(ENABLE_PROTOBUF_SERVER)
|
||||
disconnect(_hyperion, &Hyperion::forwardBufferMessage, nullptr, nullptr);
|
||||
#endif
|
||||
@@ -373,6 +388,7 @@ void MessageForwarder::stopFlatbufferTargets()
|
||||
{
|
||||
disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr);
|
||||
disconnect(_hyperion, &Hyperion::forwardAudioProtoMessage, nullptr, nullptr);
|
||||
#if defined(ENABLE_FLATBUF_SERVER) || defined(ENABLE_PROTOBUF_SERVER)
|
||||
disconnect(_hyperion, &Hyperion::forwardBufferMessage, nullptr, nullptr);
|
||||
#endif
|
||||
|
||||
@@ -33,3 +33,7 @@ endif(ENABLE_QT)
|
||||
if (ENABLE_DX)
|
||||
add_subdirectory(directx)
|
||||
endif(ENABLE_DX)
|
||||
|
||||
if (ENABLE_AUDIO)
|
||||
add_subdirectory(audio)
|
||||
endif()
|
||||
|
||||
201
libsrc/grabber/audio/AudioGrabber.cpp
Normal file
201
libsrc/grabber/audio/AudioGrabber.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include <grabber/AudioGrabber.h>
|
||||
#include <math.h>
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
// Constants
|
||||
namespace {
|
||||
const uint16_t RESOLUTION = 255;
|
||||
}
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
|
||||
namespace QColorConstants
|
||||
{
|
||||
const QColor Black = QColor(0xFF, 0x00, 0x00);
|
||||
const QColor Red = QColor(0xFF, 0x00, 0x00);
|
||||
const QColor Green = QColor(0x00, 0xFF, 0x00);
|
||||
const QColor Blue = QColor(0x00, 0x00, 0xFF);
|
||||
const QColor Yellow = QColor(0xFF, 0xFF, 0x00);
|
||||
}
|
||||
#endif
|
||||
//End of constants
|
||||
|
||||
AudioGrabber::AudioGrabber()
|
||||
: Grabber("AudioGrabber")
|
||||
, _deviceProperties()
|
||||
, _device("none")
|
||||
, _hotColor(QColorConstants::Red)
|
||||
, _warnValue(80)
|
||||
, _warnColor(QColorConstants::Yellow)
|
||||
, _safeValue(45)
|
||||
, _safeColor(QColorConstants::Green)
|
||||
, _multiplier(0)
|
||||
, _tolerance(20)
|
||||
, _dynamicMultiplier(INT16_MAX)
|
||||
, _started(false)
|
||||
{
|
||||
}
|
||||
|
||||
AudioGrabber::~AudioGrabber()
|
||||
{
|
||||
freeResources();
|
||||
}
|
||||
|
||||
void AudioGrabber::freeResources()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioGrabber::setDevice(const QString& device)
|
||||
{
|
||||
_device = device;
|
||||
|
||||
if (_started)
|
||||
{
|
||||
this->stop();
|
||||
this->start();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGrabber::setConfiguration(const QJsonObject& config)
|
||||
{
|
||||
QJsonArray hotColorArray = config["hotColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(255), QVariant(0), QVariant(0) })));
|
||||
QJsonArray warnColorArray = config["warnColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(255), QVariant(255), QVariant(0) })));
|
||||
QJsonArray safeColorArray = config["safeColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(0), QVariant(255), QVariant(0) })));
|
||||
|
||||
_hotColor = QColor(hotColorArray.at(0).toInt(), hotColorArray.at(1).toInt(), hotColorArray.at(2).toInt());
|
||||
_warnColor = QColor(warnColorArray.at(0).toInt(), warnColorArray.at(1).toInt(), warnColorArray.at(2).toInt());
|
||||
_safeColor = QColor(safeColorArray.at(0).toInt(), safeColorArray.at(1).toInt(), safeColorArray.at(2).toInt());
|
||||
|
||||
_warnValue = config["warnValue"].toInt(80);
|
||||
_safeValue = config["safeValue"].toInt(45);
|
||||
_multiplier = config["multiplier"].toDouble(0);
|
||||
_tolerance = config["tolerance"].toInt(20);
|
||||
}
|
||||
|
||||
void AudioGrabber::resetMultiplier()
|
||||
{
|
||||
_dynamicMultiplier = INT16_MAX;
|
||||
}
|
||||
|
||||
void AudioGrabber::processAudioFrame(int16_t* buffer, int length)
|
||||
{
|
||||
// Apply Visualizer and Construct Image
|
||||
|
||||
// TODO: Pass Audio Frame to python and let the script calculate the image.
|
||||
|
||||
// TODO: Support Stereo capture with different meters per side
|
||||
|
||||
// Default VUMeter - Later Make this pluggable for different audio effects
|
||||
|
||||
double averageAmplitude = 0;
|
||||
// Calculate the the average amplitude value in the buffer
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
averageAmplitude += fabs(buffer[i]) / length;
|
||||
}
|
||||
|
||||
double * currentMultiplier;
|
||||
|
||||
if (_multiplier < std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
// Dynamically calculate multiplier.
|
||||
const double pendingMultiplier = INT16_MAX / fmax(1.0, averageAmplitude + ((_tolerance / 100.0) * averageAmplitude));
|
||||
|
||||
if (pendingMultiplier < _dynamicMultiplier)
|
||||
_dynamicMultiplier = pendingMultiplier;
|
||||
|
||||
currentMultiplier = &_dynamicMultiplier;
|
||||
}
|
||||
else
|
||||
{
|
||||
// User defined multiplier
|
||||
currentMultiplier = &_multiplier;
|
||||
}
|
||||
|
||||
// Apply multiplier to average amplitude
|
||||
const double result = averageAmplitude * (*currentMultiplier);
|
||||
|
||||
// Calculate the average percentage
|
||||
const double percentage = fmin(result / INT16_MAX, 1);
|
||||
|
||||
// Calculate the value
|
||||
const int value = static_cast<int>(ceil(percentage * RESOLUTION));
|
||||
|
||||
// Draw Image
|
||||
QImage image(1, RESOLUTION, QImage::Format_RGB888);
|
||||
image.fill(QColorConstants::Black);
|
||||
|
||||
int safePixelValue = static_cast<int>(round(( _safeValue / 100.0) * RESOLUTION));
|
||||
int warnPixelValue = static_cast<int>(round(( _warnValue / 100.0) * RESOLUTION));
|
||||
|
||||
for (int i = 0; i < RESOLUTION; i++)
|
||||
{
|
||||
QColor color = QColorConstants::Black;
|
||||
int position = RESOLUTION - i;
|
||||
|
||||
if (position < safePixelValue)
|
||||
{
|
||||
color = _safeColor;
|
||||
}
|
||||
else if (position < warnPixelValue)
|
||||
{
|
||||
color = _warnColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = _hotColor;
|
||||
}
|
||||
|
||||
if (position < value)
|
||||
{
|
||||
image.setPixelColor(0, i, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.setPixelColor(0, i, QColorConstants::Black);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to Image<ColorRGB>
|
||||
Image<ColorRgb> finalImage (static_cast<unsigned>(image.width()), static_cast<unsigned>(image.height()));
|
||||
for (int y = 0; y < image.height(); y++)
|
||||
{
|
||||
memcpy((unsigned char*)finalImage.memptr() + y * image.width() * 3, static_cast<unsigned char*>(image.scanLine(y)), image.width() * 3);
|
||||
}
|
||||
|
||||
emit newFrame(finalImage);
|
||||
}
|
||||
|
||||
Logger* AudioGrabber::getLog()
|
||||
{
|
||||
return _log;
|
||||
}
|
||||
|
||||
bool AudioGrabber::start()
|
||||
{
|
||||
resetMultiplier();
|
||||
|
||||
_started = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioGrabber::stop()
|
||||
{
|
||||
_started = false;
|
||||
}
|
||||
|
||||
void AudioGrabber::restart()
|
||||
{
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
|
||||
QJsonArray AudioGrabber::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
QJsonArray result; // Return empty result
|
||||
return result;
|
||||
}
|
||||
317
libsrc/grabber/audio/AudioGrabberLinux.cpp
Normal file
317
libsrc/grabber/audio/AudioGrabberLinux.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
#include <grabber/AudioGrabberLinux.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
typedef void* (*THREADFUNCPTR)(void*);
|
||||
|
||||
AudioGrabberLinux::AudioGrabberLinux()
|
||||
: AudioGrabber()
|
||||
, _isRunning{ false }
|
||||
, _captureDevice {nullptr}
|
||||
, _sampleRate(44100)
|
||||
{
|
||||
}
|
||||
|
||||
AudioGrabberLinux::~AudioGrabberLinux()
|
||||
{
|
||||
this->stop();
|
||||
}
|
||||
|
||||
void AudioGrabberLinux::refreshDevices()
|
||||
{
|
||||
Debug(_log, "Enumerating Audio Input Devices");
|
||||
|
||||
_deviceProperties.clear();
|
||||
|
||||
snd_ctl_t* deviceHandle;
|
||||
int soundCard {-1};
|
||||
int error {-1};
|
||||
int cardInput {-1};
|
||||
|
||||
snd_ctl_card_info_t* cardInfo;
|
||||
snd_pcm_info_t* deviceInfo;
|
||||
|
||||
snd_ctl_card_info_alloca(&cardInfo);
|
||||
snd_pcm_info_alloca(&deviceInfo);
|
||||
|
||||
while (snd_card_next(&soundCard) > -1)
|
||||
{
|
||||
if (soundCard < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
char cardId[32];
|
||||
sprintf(cardId, "hw:%d", soundCard);
|
||||
|
||||
if ((error = snd_ctl_open(&deviceHandle, cardId, SND_CTL_NONBLOCK)) < 0)
|
||||
{
|
||||
Error(_log, "Erorr opening device: (%i): %s", soundCard, snd_strerror(error));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((error = snd_ctl_card_info(deviceHandle, cardInfo)) < 0)
|
||||
{
|
||||
Error(_log, "Erorr getting hardware info: (%i): %s", soundCard, snd_strerror(error));
|
||||
snd_ctl_close(deviceHandle);
|
||||
continue;
|
||||
}
|
||||
|
||||
cardInput = -1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (snd_ctl_pcm_next_device(deviceHandle, &cardInput) < 0)
|
||||
Error(_log, "Error selecting device input");
|
||||
|
||||
if (cardInput < 0)
|
||||
break;
|
||||
|
||||
snd_pcm_info_set_device(deviceInfo, static_cast<uint>(cardInput));
|
||||
snd_pcm_info_set_subdevice(deviceInfo, 0);
|
||||
snd_pcm_info_set_stream(deviceInfo, SND_PCM_STREAM_CAPTURE);
|
||||
|
||||
if ((error = snd_ctl_pcm_info(deviceHandle, deviceInfo)) < 0)
|
||||
{
|
||||
if (error != -ENOENT)
|
||||
Error(_log, "Digital Audio Info: (%i): %s", soundCard, snd_strerror(error));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioGrabber::DeviceProperties device;
|
||||
|
||||
device.id = QString("hw:%1,%2").arg(snd_pcm_info_get_card(deviceInfo)).arg(snd_pcm_info_get_device(deviceInfo));
|
||||
device.name = QString("%1: %2").arg(snd_ctl_card_info_get_name(cardInfo),snd_pcm_info_get_name(deviceInfo));
|
||||
|
||||
Debug(_log, "Found sound card (%s): %s", QSTRING_CSTR(device.id), QSTRING_CSTR(device.name));
|
||||
|
||||
_deviceProperties.insert(device.id, device);
|
||||
}
|
||||
|
||||
snd_ctl_close(deviceHandle);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioGrabberLinux::configureCaptureInterface()
|
||||
{
|
||||
int error = -1;
|
||||
QString name = (_device.isEmpty() || _device == "auto") ? "default" : (_device);
|
||||
|
||||
if ((error = snd_pcm_open(&_captureDevice, QSTRING_CSTR(name) , SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to open audio device: %s, - %s", QSTRING_CSTR(_device), snd_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((error = snd_pcm_hw_params_malloc(&_captureDeviceConfig)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to create hardware parameters: %s", snd_strerror(error));
|
||||
snd_pcm_close(_captureDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((error = snd_pcm_hw_params_any(_captureDevice, _captureDeviceConfig)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to initialize hardware parameters: %s", snd_strerror(error));
|
||||
snd_pcm_hw_params_free(_captureDeviceConfig);
|
||||
snd_pcm_close(_captureDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((error = snd_pcm_hw_params_set_access(_captureDevice, _captureDeviceConfig, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to configure interleaved mode: %s", snd_strerror(error));
|
||||
snd_pcm_hw_params_free(_captureDeviceConfig);
|
||||
snd_pcm_close(_captureDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((error = snd_pcm_hw_params_set_format(_captureDevice, _captureDeviceConfig, SND_PCM_FORMAT_S16_LE)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to configure capture format: %s", snd_strerror(error));
|
||||
snd_pcm_hw_params_free(_captureDeviceConfig);
|
||||
snd_pcm_close(_captureDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((error = snd_pcm_hw_params_set_rate_near(_captureDevice, _captureDeviceConfig, &_sampleRate, nullptr)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to configure sample rate: %s", snd_strerror(error));
|
||||
snd_pcm_hw_params_free(_captureDeviceConfig);
|
||||
snd_pcm_close(_captureDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((error = snd_pcm_hw_params(_captureDevice, _captureDeviceConfig)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to configure hardware parameters: %s", snd_strerror(error));
|
||||
snd_pcm_hw_params_free(_captureDeviceConfig);
|
||||
snd_pcm_close(_captureDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
snd_pcm_hw_params_free(_captureDeviceConfig);
|
||||
|
||||
if ((error = snd_pcm_prepare(_captureDevice)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to prepare audio interface: %s", snd_strerror(error));
|
||||
snd_pcm_close(_captureDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((error = snd_pcm_start(_captureDevice)) < 0)
|
||||
{
|
||||
Error(_log, "Failed to start audio interface: %s", snd_strerror(error));
|
||||
snd_pcm_close(_captureDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGrabberLinux::start()
|
||||
{
|
||||
if (!_isEnabled)
|
||||
return false;
|
||||
|
||||
if (_isRunning.load(std::memory_order_acquire))
|
||||
return true;
|
||||
|
||||
Debug(_log, "Start Audio With %s", QSTRING_CSTR(getDeviceName(_device)));
|
||||
|
||||
if (!configureCaptureInterface())
|
||||
return false;
|
||||
|
||||
_isRunning.store(true, std::memory_order_release);
|
||||
|
||||
pthread_attr_t threadAttributes;
|
||||
int threadPriority = 1;
|
||||
|
||||
sched_param schedulerParameter;
|
||||
schedulerParameter.sched_priority = threadPriority;
|
||||
|
||||
if (pthread_attr_init(&threadAttributes) != 0)
|
||||
{
|
||||
Debug(_log, "Failed to create thread attributes");
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pthread_create(&_audioThread, &threadAttributes, static_cast<THREADFUNCPTR>(&AudioThreadRunner), static_cast<void*>(this)) != 0)
|
||||
{
|
||||
Debug(_log, "Failed to create audio capture thread");
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioGrabber::start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioGrabberLinux::stop()
|
||||
{
|
||||
if (!_isRunning.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
Debug(_log, "Stopping Audio Interface");
|
||||
|
||||
_isRunning.store(false, std::memory_order_release);
|
||||
|
||||
if (_audioThread != 0) {
|
||||
pthread_join(_audioThread, NULL);
|
||||
}
|
||||
|
||||
snd_pcm_close(_captureDevice);
|
||||
|
||||
AudioGrabber::stop();
|
||||
}
|
||||
|
||||
void AudioGrabberLinux::processAudioBuffer(snd_pcm_sframes_t frames)
|
||||
{
|
||||
if (!_isRunning.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
ssize_t bytes = snd_pcm_frames_to_bytes(_captureDevice, frames);
|
||||
|
||||
int16_t * buffer = static_cast<int16_t*>(calloc(static_cast<size_t>(bytes / 2), sizeof(int16_t)));
|
||||
|
||||
if (frames == 0)
|
||||
{
|
||||
buffer[0] = 0;
|
||||
processAudioFrame(buffer, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
snd_pcm_sframes_t framesRead = snd_pcm_readi(_captureDevice, buffer, static_cast<snd_pcm_uframes_t>(frames));
|
||||
|
||||
if (framesRead < frames)
|
||||
{
|
||||
Error(_log, "Error reading audio. Got %d frames instead of %d", framesRead, frames);
|
||||
}
|
||||
else
|
||||
{
|
||||
processAudioFrame(buffer, static_cast<int>(snd_pcm_frames_to_bytes(_captureDevice, framesRead)) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
QJsonArray AudioGrabberLinux::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
refreshDevices();
|
||||
|
||||
QJsonArray devices;
|
||||
|
||||
for (auto deviceIterator = _deviceProperties.begin(); deviceIterator != _deviceProperties.end(); ++deviceIterator)
|
||||
{
|
||||
// Device
|
||||
QJsonObject device;
|
||||
QJsonArray deviceInputs;
|
||||
|
||||
device["device"] = deviceIterator.key();
|
||||
device["device_name"] = deviceIterator.value().name;
|
||||
device["type"] = "audio";
|
||||
|
||||
devices.append(device);
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
QString AudioGrabberLinux::getDeviceName(const QString& devicePath) const
|
||||
{
|
||||
if (devicePath.isEmpty() || devicePath == "auto")
|
||||
{
|
||||
return "Default Audio Device";
|
||||
}
|
||||
|
||||
return _deviceProperties.value(devicePath).name;
|
||||
}
|
||||
|
||||
static void * AudioThreadRunner(void* params)
|
||||
{
|
||||
AudioGrabberLinux* This = static_cast<AudioGrabberLinux*>(params);
|
||||
|
||||
Debug(This->getLog(), "Audio Thread Started");
|
||||
|
||||
snd_pcm_sframes_t framesAvailable = 0;
|
||||
|
||||
while (This->_isRunning.load(std::memory_order_acquire))
|
||||
{
|
||||
snd_pcm_wait(This->_captureDevice, 1000);
|
||||
|
||||
if ((framesAvailable = snd_pcm_avail(This->_captureDevice)) > 0)
|
||||
This->processAudioBuffer(framesAvailable);
|
||||
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
Debug(This->getLog(), "Audio Thread Shutting Down");
|
||||
return nullptr;
|
||||
}
|
||||
354
libsrc/grabber/audio/AudioGrabberWindows.cpp
Normal file
354
libsrc/grabber/audio/AudioGrabberWindows.cpp
Normal file
@@ -0,0 +1,354 @@
|
||||
#include <grabber/AudioGrabberWindows.h>
|
||||
#include <QImage>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
#pragma comment(lib,"dsound.lib")
|
||||
#pragma comment(lib, "dxguid.lib")
|
||||
|
||||
// Constants
|
||||
namespace {
|
||||
const int AUDIO_NOTIFICATION_COUNT{ 4 };
|
||||
} //End of constants
|
||||
|
||||
AudioGrabberWindows::AudioGrabberWindows() : AudioGrabber()
|
||||
{
|
||||
}
|
||||
|
||||
AudioGrabberWindows::~AudioGrabberWindows()
|
||||
{
|
||||
this->stop();
|
||||
}
|
||||
|
||||
void AudioGrabberWindows::refreshDevices()
|
||||
{
|
||||
Debug(_log, "Refreshing Audio Devices");
|
||||
|
||||
_deviceProperties.clear();
|
||||
|
||||
// Enumerate Devices
|
||||
if (FAILED(DirectSoundCaptureEnumerate(DirectSoundEnumProcessor, (VOID*)&_deviceProperties)))
|
||||
{
|
||||
Error(_log, "Failed to enumerate audio devices.");
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioGrabberWindows::configureCaptureInterface()
|
||||
{
|
||||
CLSID deviceId {};
|
||||
|
||||
if (!this->_device.isEmpty() && this->_device != "auto")
|
||||
{
|
||||
LPCOLESTR clsid = reinterpret_cast<const wchar_t*>(_device.utf16());
|
||||
HRESULT res = CLSIDFromString(clsid, &deviceId);
|
||||
if (FAILED(res))
|
||||
{
|
||||
Error(_log, "Failed to get CLSID for '%s' with error: 0x%08x: %s", QSTRING_CSTR(_device), res, std::system_category().message(res).c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create Capture Device
|
||||
HRESULT res = DirectSoundCaptureCreate8(&deviceId, &recordingDevice, NULL);
|
||||
if (FAILED(res))
|
||||
{
|
||||
Error(_log, "Failed to create capture device: '%s' with error: 0x%08x: %s", QSTRING_CSTR(_device), res, std::system_category().message(res).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Define Audio Format & Create Buffer
|
||||
WAVEFORMATEX audioFormat { WAVE_FORMAT_PCM, 1, 44100, 88200, 2, 16, 0 };
|
||||
// wFormatTag, nChannels, nSamplesPerSec, mAvgBytesPerSec,
|
||||
// nBlockAlign, wBitsPerSample, cbSize
|
||||
|
||||
notificationSize = max(1024, audioFormat.nAvgBytesPerSec / 8);
|
||||
notificationSize -= notificationSize % audioFormat.nBlockAlign;
|
||||
|
||||
bufferCaptureSize = notificationSize * AUDIO_NOTIFICATION_COUNT;
|
||||
|
||||
DSCBUFFERDESC bufferDesc;
|
||||
bufferDesc.dwSize = sizeof(DSCBUFFERDESC);
|
||||
bufferDesc.dwFlags = 0;
|
||||
bufferDesc.dwBufferBytes = bufferCaptureSize;
|
||||
bufferDesc.dwReserved = 0;
|
||||
bufferDesc.lpwfxFormat = &audioFormat;
|
||||
bufferDesc.dwFXCount = 0;
|
||||
bufferDesc.lpDSCFXDesc = NULL;
|
||||
|
||||
// Create Capture Device's Buffer
|
||||
LPDIRECTSOUNDCAPTUREBUFFER preBuffer;
|
||||
if (FAILED(recordingDevice->CreateCaptureBuffer(&bufferDesc, &preBuffer, NULL)))
|
||||
{
|
||||
Error(_log, "Failed to create capture buffer: '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
recordingDevice->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferCapturePosition = 0;
|
||||
|
||||
// Query Capture8 Buffer
|
||||
if (FAILED(preBuffer->QueryInterface(IID_IDirectSoundCaptureBuffer8, (LPVOID*)&recordingBuffer)))
|
||||
{
|
||||
Error(_log, "Failed to retrieve recording buffer");
|
||||
preBuffer->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
preBuffer->Release();
|
||||
|
||||
// Create Notifications
|
||||
LPDIRECTSOUNDNOTIFY8 notify;
|
||||
|
||||
if (FAILED(recordingBuffer->QueryInterface(IID_IDirectSoundNotify8, (LPVOID *) ¬ify)))
|
||||
{
|
||||
Error(_log, "Failed to configure buffer notifications: '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
recordingDevice->Release();
|
||||
recordingBuffer->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create Events
|
||||
notificationEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
|
||||
if (notificationEvent == NULL)
|
||||
{
|
||||
Error(_log, "Failed to configure buffer notifications events: '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
notify->Release();
|
||||
recordingDevice->Release();
|
||||
recordingBuffer->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure Notifications
|
||||
DSBPOSITIONNOTIFY positionNotify[AUDIO_NOTIFICATION_COUNT];
|
||||
|
||||
for (int i = 0; i < AUDIO_NOTIFICATION_COUNT; i++)
|
||||
{
|
||||
positionNotify[i].dwOffset = (notificationSize * i) + notificationSize - 1;
|
||||
positionNotify[i].hEventNotify = notificationEvent;
|
||||
}
|
||||
|
||||
// Set Notifications
|
||||
notify->SetNotificationPositions(AUDIO_NOTIFICATION_COUNT, positionNotify);
|
||||
notify->Release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGrabberWindows::start()
|
||||
{
|
||||
if (!_isEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->isRunning.load(std::memory_order_acquire))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Test, if configured device currently exists
|
||||
refreshDevices();
|
||||
if (!_deviceProperties.contains(_device))
|
||||
{
|
||||
_device = "auto";
|
||||
Warning(_log, "Configured audio device is not available. Using '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
}
|
||||
|
||||
Info(_log, "Capture audio from %s", QSTRING_CSTR(getDeviceName(_device)));
|
||||
|
||||
if (!this->configureCaptureInterface())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(recordingBuffer->Start(DSCBSTART_LOOPING)))
|
||||
{
|
||||
Error(_log, "Failed starting audio capture from '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
return false;
|
||||
}
|
||||
|
||||
this->isRunning.store(true, std::memory_order_release);
|
||||
DWORD threadId;
|
||||
|
||||
this->audioThread = CreateThread(
|
||||
NULL,
|
||||
16,
|
||||
AudioThreadRunner,
|
||||
(void *) this,
|
||||
0,
|
||||
&threadId
|
||||
);
|
||||
|
||||
if (this->audioThread == NULL)
|
||||
{
|
||||
Error(_log, "Failed to create audio capture thread");
|
||||
|
||||
this->stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioGrabber::start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioGrabberWindows::stop()
|
||||
{
|
||||
if (!this->isRunning.load(std::memory_order_acquire))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Info(_log, "Shutting down audio capture from: '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
|
||||
this->isRunning.store(false, std::memory_order_release);
|
||||
|
||||
if (FAILED(recordingBuffer->Stop()))
|
||||
{
|
||||
Error(_log, "Audio capture failed to stop: '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
}
|
||||
|
||||
if (FAILED(recordingBuffer->Release()))
|
||||
{
|
||||
Error(_log, "Failed to release recording buffer: '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
}
|
||||
|
||||
if (FAILED(recordingDevice->Release()))
|
||||
{
|
||||
Error(_log, "Failed to release recording device: '%s'", QSTRING_CSTR(getDeviceName(_device)));
|
||||
}
|
||||
|
||||
CloseHandle(notificationEvent);
|
||||
CloseHandle(this->audioThread);
|
||||
|
||||
AudioGrabber::stop();
|
||||
}
|
||||
|
||||
DWORD WINAPI AudioGrabberWindows::AudioThreadRunner(LPVOID param)
|
||||
{
|
||||
AudioGrabberWindows* This = (AudioGrabberWindows*) param;
|
||||
|
||||
while (This->isRunning.load(std::memory_order_acquire))
|
||||
{
|
||||
DWORD result = WaitForMultipleObjects(1, &This->notificationEvent, true, 500);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
This->processAudioBuffer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Debug(This->_log, "Audio capture thread stopped.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioGrabberWindows::processAudioBuffer()
|
||||
{
|
||||
DWORD readPosition;
|
||||
DWORD capturePosition;
|
||||
|
||||
// Primary segment
|
||||
VOID* capturedAudio;
|
||||
DWORD capturedAudioLength;
|
||||
|
||||
// Wrap around segment
|
||||
VOID* capturedAudio2;
|
||||
DWORD capturedAudio2Length;
|
||||
|
||||
LONG lockSize;
|
||||
|
||||
if (FAILED(recordingBuffer->GetCurrentPosition(&capturePosition, &readPosition)))
|
||||
{
|
||||
// Failed to get current position
|
||||
Error(_log, "Failed to get buffer position.");
|
||||
return;
|
||||
}
|
||||
|
||||
lockSize = readPosition - bufferCapturePosition;
|
||||
|
||||
if (lockSize < 0)
|
||||
{
|
||||
lockSize += bufferCaptureSize;
|
||||
}
|
||||
|
||||
// Block Align
|
||||
lockSize -= (lockSize % notificationSize);
|
||||
|
||||
if (lockSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock Capture Buffer
|
||||
if (FAILED(recordingBuffer->Lock(bufferCapturePosition, lockSize, &capturedAudio, &capturedAudioLength,
|
||||
&capturedAudio2, &capturedAudio2Length, 0)))
|
||||
{
|
||||
// Handle Lock Error
|
||||
return;
|
||||
}
|
||||
|
||||
bufferCapturePosition += capturedAudioLength;
|
||||
bufferCapturePosition %= bufferCaptureSize; // Circular Buffer
|
||||
|
||||
int frameSize = capturedAudioLength + capturedAudio2Length;
|
||||
|
||||
int16_t * readBuffer = new int16_t[frameSize];
|
||||
|
||||
// Buffer wrapped around, read second position
|
||||
if (capturedAudio2 != NULL)
|
||||
{
|
||||
bufferCapturePosition += capturedAudio2Length;
|
||||
bufferCapturePosition %= bufferCaptureSize; // Circular Buffer
|
||||
}
|
||||
|
||||
// Copy Buffer into memory
|
||||
CopyMemory(readBuffer, capturedAudio, capturedAudioLength);
|
||||
|
||||
if (capturedAudio2 != NULL)
|
||||
{
|
||||
CopyMemory(readBuffer + capturedAudioLength, capturedAudio2, capturedAudio2Length);
|
||||
}
|
||||
|
||||
// Release Buffer Lock
|
||||
recordingBuffer->Unlock(capturedAudio, capturedAudioLength, capturedAudio2, capturedAudio2Length);
|
||||
|
||||
// Process Audio Frame
|
||||
this->processAudioFrame(readBuffer, frameSize);
|
||||
|
||||
delete[] readBuffer;
|
||||
}
|
||||
|
||||
QJsonArray AudioGrabberWindows::discover(const QJsonObject& params)
|
||||
{
|
||||
refreshDevices();
|
||||
|
||||
QJsonArray devices;
|
||||
|
||||
for (auto deviceIterator = _deviceProperties.begin(); deviceIterator != _deviceProperties.end(); ++deviceIterator)
|
||||
{
|
||||
// Device
|
||||
QJsonObject device;
|
||||
QJsonArray deviceInputs;
|
||||
|
||||
device["device"] = deviceIterator.value().id;
|
||||
device["device_name"] = deviceIterator.value().name;
|
||||
device["type"] = "audio";
|
||||
|
||||
devices.append(device);
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
QString AudioGrabberWindows::getDeviceName(const QString& devicePath) const
|
||||
{
|
||||
if (devicePath.isEmpty() || devicePath == "auto")
|
||||
{
|
||||
return "Default Device";
|
||||
}
|
||||
return _deviceProperties.value(devicePath).name;
|
||||
}
|
||||
63
libsrc/grabber/audio/AudioWrapper.cpp
Normal file
63
libsrc/grabber/audio/AudioWrapper.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <grabber/AudioWrapper.h>
|
||||
#include <hyperion/GrabberWrapper.h>
|
||||
#include <QObject>
|
||||
#include <QMetaType>
|
||||
|
||||
AudioWrapper::AudioWrapper()
|
||||
: GrabberWrapper("AudioGrabber", &_grabber)
|
||||
, _grabber()
|
||||
{
|
||||
// register the image type
|
||||
qRegisterMetaType<Image<ColorRgb>>("Image<ColorRgb>");
|
||||
|
||||
connect(&_grabber, &AudioGrabber::newFrame, this, &AudioWrapper::newFrame, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
AudioWrapper::~AudioWrapper()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool AudioWrapper::start()
|
||||
{
|
||||
return (_grabber.start() && GrabberWrapper::start());
|
||||
}
|
||||
|
||||
void AudioWrapper::stop()
|
||||
{
|
||||
_grabber.stop();
|
||||
GrabberWrapper::stop();
|
||||
}
|
||||
|
||||
void AudioWrapper::action()
|
||||
{
|
||||
// Dummy we will push the audio images
|
||||
}
|
||||
|
||||
void AudioWrapper::newFrame(const Image<ColorRgb>& image)
|
||||
{
|
||||
emit systemImage(_grabberName, image);
|
||||
}
|
||||
|
||||
void AudioWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
|
||||
{
|
||||
if (type == settings::AUDIO)
|
||||
{
|
||||
const QJsonObject& obj = config.object();
|
||||
|
||||
// set global grabber state
|
||||
setAudioGrabberState(obj["enable"].toBool(false));
|
||||
|
||||
if (getAudioGrabberState())
|
||||
{
|
||||
_grabber.setDevice(obj["device"].toString());
|
||||
_grabber.setConfiguration(obj);
|
||||
|
||||
_grabber.restart();
|
||||
}
|
||||
else
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
35
libsrc/grabber/audio/CMakeLists.txt
Normal file
35
libsrc/grabber/audio/CMakeLists.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
# Define the current source locations
|
||||
SET( CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber )
|
||||
SET( CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/audio )
|
||||
|
||||
|
||||
if (WIN32)
|
||||
FILE ( GLOB AUDIO_GRABBER_SOURCES "${CURRENT_HEADER_DIR}/Audio*Windows.h" "${CURRENT_HEADER_DIR}/AudioGrabber.h" "${CURRENT_HEADER_DIR}/AudioWrapper.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*Windows.cpp" "${CURRENT_SOURCE_DIR}/AudioGrabber.cpp" "${CURRENT_SOURCE_DIR}/AudioWrapper.cpp")
|
||||
elseif(${CMAKE_SYSTEM} MATCHES "Linux")
|
||||
FILE ( GLOB AUDIO_GRABBER_SOURCES "${CURRENT_HEADER_DIR}/Audio*Linux.h" "${CURRENT_HEADER_DIR}/AudioGrabber.h" "${CURRENT_HEADER_DIR}/AudioWrapper.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*Linux.cpp" "${CURRENT_SOURCE_DIR}/AudioGrabber.cpp" "${CURRENT_SOURCE_DIR}/AudioWrapper.cpp")
|
||||
elseif (APPLE)
|
||||
#TODO
|
||||
#FILE ( GLOB AUDIO_GRABBER_SOURCES "${CURRENT_HEADER_DIR}/Audio*Apple.h" "${CURRENT_HEADER_DIR}/AudioGrabber.h" "${CURRENT_HEADER_DIR}/AudioWrapper.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*Apple.cpp" "${CURRENT_SOURCE_DIR}/AudioGrabber.cpp" "${CURRENT_SOURCE_DIR}/AudioWrapper.cpp")
|
||||
endif()
|
||||
|
||||
add_library( audio-grabber ${AUDIO_GRABBER_SOURCES} )
|
||||
|
||||
set(AUDIO_LIBS hyperion)
|
||||
|
||||
|
||||
if (WIN32)
|
||||
set(AUDIO_LIBS ${AUDIO_LIBS} DSound)
|
||||
elseif(${CMAKE_SYSTEM} MATCHES "Linux")
|
||||
find_package(ALSA REQUIRED)
|
||||
if (ALSA_FOUND)
|
||||
include_directories(${ALSA_INCLUDE_DIRS})
|
||||
set(AUDIO_LIBS ${AUDIO_LIBS} ${ALSA_LIBRARIES})
|
||||
endif(ALSA_FOUND)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
set(AUDIO_LIBS ${AUDIO_LIBS} Threads::Threads) # PRIVATE
|
||||
endif()
|
||||
|
||||
|
||||
target_link_libraries(audio-grabber ${AUDIO_LIBS} ${QT_LIBRARIES})
|
||||
@@ -223,13 +223,11 @@ void EncoderThread::processImageMjpeg()
|
||||
{
|
||||
_xform->options = TJXOPT_CROP;
|
||||
_xform->r = tjregion {_cropLeft,_cropTop,transformedWidth,transformedHeight};
|
||||
//qDebug() << "processImageMjpeg() | _doTransform - Image cropped: transformedWidth: " << transformedWidth << " transformedHeight: " << transformedHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
_xform->options = 0;
|
||||
_xform->r = tjregion {0,0,_width,_height};
|
||||
//qDebug() << "processImageMjpeg() | _doTransform - Image not cropped: _width: " << _width << " _height: " << _height;
|
||||
}
|
||||
_xform->options |= TJXOPT_TRIM;
|
||||
|
||||
@@ -344,11 +342,9 @@ bool EncoderThread::onError(const QString context) const
|
||||
#if LIBJPEG_TURBO_VERSION_NUMBER > 2000000
|
||||
if (tjGetErrorCode(_tjInstance) == TJERR_FATAL)
|
||||
{
|
||||
//qDebug() << context << "Error: " << QString(tjGetErrorStr2(_tjInstance));
|
||||
treatAsError = true;
|
||||
}
|
||||
#else
|
||||
//qDebug() << context << "Error: " << QString(tjGetErrorStr());
|
||||
treatAsError = true;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -74,9 +74,6 @@ void VideoWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument
|
||||
// Device resolution
|
||||
_grabber.setWidthHeight(obj["width"].toInt(0), obj["height"].toInt(0));
|
||||
|
||||
// Device framerate
|
||||
_grabber.setFramerate(obj["fps"].toInt(15));
|
||||
|
||||
// Device encoding format
|
||||
_grabber.setEncoding(obj["encoding"].toString("NO_CHANGE"));
|
||||
|
||||
@@ -124,6 +121,11 @@ void VideoWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument
|
||||
obj["blueSignalThreshold"].toDouble(0.0)/100.0,
|
||||
obj["noSignalCounterThreshold"].toInt(50));
|
||||
|
||||
// Device framerate
|
||||
_grabber.setFramerate(obj["fps"].toInt(15));
|
||||
|
||||
updateTimer(_ggrabber->getUpdateInterval());
|
||||
|
||||
// Reload the Grabber if any settings have been changed that require it
|
||||
_grabber.reload(getV4lGrabberState());
|
||||
}
|
||||
|
||||
@@ -1209,7 +1209,7 @@ void V4L2Grabber::setCecDetectionEnable(bool enable)
|
||||
{
|
||||
_cecDetectionEnabled = enable;
|
||||
if(_initialized)
|
||||
Info(_log, QString("CEC detection is now %1").arg(enable ? "enabled" : "disabled").toLocal8Bit());
|
||||
Info(_log, "%s", QSTRING_CSTR(QString("CEC detection is now %1").arg(enable ? "enabled" : "disabled")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1501,19 +1501,19 @@ void V4L2Grabber::enumVideoCaptureDevices()
|
||||
|
||||
// Enumerate video control IDs
|
||||
QList<DeviceControls> deviceControlList;
|
||||
for (auto it = _controlIDPropertyMap->constBegin(); it != _controlIDPropertyMap->constEnd(); it++)
|
||||
for (auto itDeviceControls = _controlIDPropertyMap->constBegin(); itDeviceControls != _controlIDPropertyMap->constEnd(); itDeviceControls++)
|
||||
{
|
||||
struct v4l2_queryctrl queryctrl;
|
||||
CLEAR(queryctrl);
|
||||
|
||||
queryctrl.id = it.key();
|
||||
queryctrl.id = itDeviceControls.key();
|
||||
if (xioctl(fd, VIDIOC_QUERYCTRL, &queryctrl) < 0)
|
||||
break;
|
||||
if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
|
||||
break;
|
||||
|
||||
DeviceControls control;
|
||||
control.property = it.value();
|
||||
control.property = itDeviceControls.value();
|
||||
control.minValue = queryctrl.minimum;
|
||||
control.maxValue = queryctrl.maximum;
|
||||
control.step = queryctrl.step;
|
||||
@@ -1524,13 +1524,13 @@ void V4L2Grabber::enumVideoCaptureDevices()
|
||||
CLEAR(ctrl);
|
||||
CLEAR(ctrls);
|
||||
|
||||
ctrl.id = it.key();
|
||||
ctrl.id = itDeviceControls.key();
|
||||
ctrls.count = 1;
|
||||
ctrls.controls = &ctrl;
|
||||
if (xioctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls) == 0)
|
||||
{
|
||||
control.currentValue = ctrl.value;
|
||||
DebugIf(verbose, _log, "%s: min=%i, max=%i, step=%i, default=%i, current=%i", QSTRING_CSTR(it.value()), control.minValue, control.maxValue, control.step, control.defaultValue, control.currentValue);
|
||||
DebugIf(verbose, _log, "%s: min=%i, max=%i, step=%i, default=%i, current=%i", QSTRING_CSTR(itDeviceControls.value()), control.minValue, control.maxValue, control.step, control.defaultValue, control.currentValue);
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
||||
@@ -170,12 +170,12 @@ bool X11Grabber::setupDisplay()
|
||||
XShmQueryVersion(_x11Display, &dummy, &dummy, &pixmaps_supported);
|
||||
_XShmPixmapAvailable = pixmaps_supported && XShmPixmapFormat(_x11Display) == ZPixmap;
|
||||
|
||||
Info(_log, QString("XRandR=[%1] XRender=[%2] XShm=[%3] XPixmap=[%4]")
|
||||
.arg(_XRandRAvailable ? "available" : "unavailable")
|
||||
.arg(_XRenderAvailable ? "available" : "unavailable")
|
||||
.arg(_XShmAvailable ? "available" : "unavailable")
|
||||
.arg(_XShmPixmapAvailable ? "available" : "unavailable")
|
||||
.toStdString().c_str());
|
||||
Info(_log, "%s", QSTRING_CSTR(QString("XRandR=[%1] XRender=[%2] XShm=[%3] XPixmap=[%4]")
|
||||
.arg(_XRandRAvailable ? "available" : "unavailable",
|
||||
_XRenderAvailable ? "available" : "unavailable",
|
||||
_XShmAvailable ? "available" : "unavailable",
|
||||
_XShmPixmapAvailable ? "available" : "unavailable"))
|
||||
);
|
||||
|
||||
result = (updateScreenDimensions(true) >=0);
|
||||
ErrorIf(!result, _log, "X11 Grabber start failed");
|
||||
|
||||
@@ -242,12 +242,12 @@ bool XcbGrabber::setupDisplay()
|
||||
setupRender();
|
||||
setupShm();
|
||||
|
||||
Info(_log, QString("XcbRandR=[%1] XcbRender=[%2] XcbShm=[%3] XcbPixmap=[%4]")
|
||||
.arg(_XcbRandRAvailable ? "available" : "unavailable")
|
||||
.arg(_XcbRenderAvailable ? "available" : "unavailable")
|
||||
.arg(_XcbShmAvailable ? "available" : "unavailable")
|
||||
.arg(_XcbShmPixmapAvailable ? "available" : "unavailable")
|
||||
.toStdString().c_str());
|
||||
Info(_log, "%s", QSTRING_CSTR(QString("XcbRandR=[%1] XcbRender=[%2] XcbShm=[%3] XcbPixmap=[%4]")
|
||||
.arg(_XcbRandRAvailable ? "available" : "unavailable",
|
||||
_XcbRenderAvailable ? "available" : "unavailable",
|
||||
_XcbShmAvailable ? "available" : "unavailable",
|
||||
_XcbShmPixmapAvailable ? "available" : "unavailable"))
|
||||
);
|
||||
|
||||
result = (updateScreenDimensions(true) >= 0);
|
||||
ErrorIf(!result, _log, "XCB Grabber start failed");
|
||||
|
||||
@@ -261,26 +261,24 @@ void AuthManager::checkTimeout()
|
||||
void AuthManager::checkAuthBlockTimeout()
|
||||
{
|
||||
// handle user auth block
|
||||
for (auto it = _userAuthAttempts.begin(); it != _userAuthAttempts.end(); it++)
|
||||
{
|
||||
QMutableVectorIterator<uint64_t> itUserAuth(_userAuthAttempts);
|
||||
while (itUserAuth.hasNext()) {
|
||||
// after 10 minutes, we remove the entry
|
||||
if (*it < (uint64_t)QDateTime::currentMSecsSinceEpoch())
|
||||
{
|
||||
_userAuthAttempts.erase(it--);
|
||||
}
|
||||
if (itUserAuth.next() < static_cast<uint64_t>(QDateTime::currentMSecsSinceEpoch()))
|
||||
itUserAuth.remove();
|
||||
}
|
||||
|
||||
// handle token auth block
|
||||
for (auto it = _tokenAuthAttempts.begin(); it != _tokenAuthAttempts.end(); it++)
|
||||
{
|
||||
QMutableVectorIterator<uint64_t> itTokenAuth(_tokenAuthAttempts);
|
||||
while (itTokenAuth.hasNext()) {
|
||||
// after 10 minutes, we remove the entry
|
||||
if (*it < (uint64_t)QDateTime::currentMSecsSinceEpoch())
|
||||
{
|
||||
_tokenAuthAttempts.erase(it--);
|
||||
}
|
||||
if (itTokenAuth.next() < static_cast<uint64_t>(QDateTime::currentMSecsSinceEpoch()))
|
||||
itTokenAuth.remove();
|
||||
}
|
||||
|
||||
// if the lists are empty we stop
|
||||
if (_userAuthAttempts.empty() && _tokenAuthAttempts.empty())
|
||||
{
|
||||
_authBlockTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ CaptureCont::CaptureCont(Hyperion* hyperion)
|
||||
, _v4lCaptPrio(0)
|
||||
, _v4lCaptName()
|
||||
, _v4lInactiveTimer(new QTimer(this))
|
||||
, _audioCaptEnabled(false)
|
||||
, _audioCaptPrio(0)
|
||||
, _audioCaptName()
|
||||
, _audioInactiveTimer(new QTimer(this))
|
||||
{
|
||||
// settings changes
|
||||
connect(_hyperion, &Hyperion::settingsChanged, this, &CaptureCont::handleSettingsUpdate);
|
||||
@@ -37,6 +41,11 @@ CaptureCont::CaptureCont(Hyperion* hyperion)
|
||||
_v4lInactiveTimer->setSingleShot(true);
|
||||
_v4lInactiveTimer->setInterval(1000);
|
||||
|
||||
// inactive timer audio
|
||||
connect(_audioInactiveTimer, &QTimer::timeout, this, &CaptureCont::setAudioInactive);
|
||||
_audioInactiveTimer->setSingleShot(true);
|
||||
_audioInactiveTimer->setInterval(1000);
|
||||
|
||||
// init
|
||||
handleSettingsUpdate(settings::INSTCAPTURE, _hyperion->getSetting(settings::INSTCAPTURE));
|
||||
}
|
||||
@@ -65,6 +74,17 @@ void CaptureCont::handleSystemImage(const QString& name, const Image<ColorRgb>&
|
||||
_hyperion->setInputImage(_systemCaptPrio, image);
|
||||
}
|
||||
|
||||
void CaptureCont::handleAudioImage(const QString& name, const Image<ColorRgb>& image)
|
||||
{
|
||||
if (_audioCaptName != name)
|
||||
{
|
||||
_hyperion->registerInput(_audioCaptPrio, hyperion::COMP_AUDIO, "System", name);
|
||||
_audioCaptName = name;
|
||||
}
|
||||
_audioInactiveTimer->start();
|
||||
_hyperion->setInputImage(_audioCaptPrio, image);
|
||||
}
|
||||
|
||||
void CaptureCont::setSystemCaptureEnable(bool enable)
|
||||
{
|
||||
if(_systemCaptEnabled != enable)
|
||||
@@ -111,24 +131,56 @@ void CaptureCont::setV4LCaptureEnable(bool enable)
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureCont::setAudioCaptureEnable(bool enable)
|
||||
{
|
||||
if (_audioCaptEnabled != enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
_hyperion->registerInput(_audioCaptPrio, hyperion::COMP_AUDIO);
|
||||
connect(GlobalSignals::getInstance(), &GlobalSignals::setAudioImage, this, &CaptureCont::handleAudioImage);
|
||||
connect(GlobalSignals::getInstance(), &GlobalSignals::setAudioImage, _hyperion, &Hyperion::forwardAudioProtoMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnect(GlobalSignals::getInstance(), &GlobalSignals::setAudioImage, this, 0);
|
||||
_hyperion->clear(_audioCaptPrio);
|
||||
_audioInactiveTimer->stop();
|
||||
_audioCaptName = "";
|
||||
}
|
||||
_audioCaptEnabled = enable;
|
||||
_hyperion->setNewComponentState(hyperion::COMP_AUDIO, enable);
|
||||
emit GlobalSignals::getInstance()->requestSource(hyperion::COMP_AUDIO, int(_hyperion->getInstanceIndex()), enable);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureCont::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
|
||||
{
|
||||
if(type == settings::INSTCAPTURE)
|
||||
{
|
||||
const QJsonObject& obj = config.object();
|
||||
|
||||
if(_v4lCaptPrio != obj["v4lPriority"].toInt(240))
|
||||
{
|
||||
setV4LCaptureEnable(false); // clear prio
|
||||
_v4lCaptPrio = obj["v4lPriority"].toInt(240);
|
||||
}
|
||||
|
||||
if(_systemCaptPrio != obj["systemPriority"].toInt(250))
|
||||
{
|
||||
setSystemCaptureEnable(false); // clear prio
|
||||
_systemCaptPrio = obj["systemPriority"].toInt(250);
|
||||
}
|
||||
|
||||
if (_audioCaptPrio != obj["audioPriority"].toInt(230))
|
||||
{
|
||||
setAudioCaptureEnable(false); // clear prio
|
||||
_audioCaptPrio = obj["audioPriority"].toInt(230);
|
||||
}
|
||||
|
||||
setV4LCaptureEnable(obj["v4lEnable"].toBool(false));
|
||||
setSystemCaptureEnable(obj["systemEnable"].toBool(false));
|
||||
setAudioCaptureEnable(obj["audioEnable"].toBool(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +194,10 @@ void CaptureCont::handleCompStateChangeRequest(hyperion::Components component, b
|
||||
{
|
||||
setV4LCaptureEnable(enable);
|
||||
}
|
||||
else if (component == hyperion::COMP_AUDIO)
|
||||
{
|
||||
setAudioCaptureEnable(enable);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureCont::setV4lInactive()
|
||||
@@ -153,3 +209,8 @@ void CaptureCont::setSystemInactive()
|
||||
{
|
||||
_hyperion->setInputInactive(_systemCaptPrio);
|
||||
}
|
||||
|
||||
void CaptureCont::setAudioInactive()
|
||||
{
|
||||
_hyperion->setInputInactive(_audioCaptPrio);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ ComponentRegister::ComponentRegister(Hyperion* hyperion)
|
||||
|
||||
bool areScreenGrabberAvailable = !GrabberWrapper::availableGrabbers(GrabberTypeFilter::VIDEO).isEmpty();
|
||||
bool areVideoGrabberAvailable = !GrabberWrapper::availableGrabbers(GrabberTypeFilter::VIDEO).isEmpty();
|
||||
bool areAudioGrabberAvailable = !GrabberWrapper::availableGrabbers(GrabberTypeFilter::AUDIO).isEmpty();
|
||||
bool flatBufServerAvailable { false };
|
||||
bool protoBufServerAvailable{ false };
|
||||
|
||||
@@ -36,6 +37,11 @@ ComponentRegister::ComponentRegister(Hyperion* hyperion)
|
||||
vect << COMP_GRABBER;
|
||||
}
|
||||
|
||||
if (areAudioGrabberAvailable)
|
||||
{
|
||||
vect << COMP_AUDIO;
|
||||
}
|
||||
|
||||
if (areVideoGrabberAvailable)
|
||||
{
|
||||
vect << COMP_V4L;
|
||||
|
||||
@@ -149,6 +149,8 @@ bool Grabber::setWidthHeight(int width, int height)
|
||||
|
||||
bool Grabber::setFramerate(int fps)
|
||||
{
|
||||
Debug(_log,"Set new frames per second to: %i fps, current fps: %i", fps, _fps);
|
||||
|
||||
if((fps > 0) && (_fps != fps))
|
||||
{
|
||||
Info(_log,"Set new frames per second to: %i fps", fps);
|
||||
|
||||
@@ -18,8 +18,10 @@ const int GrabberWrapper::DEFAULT_PIXELDECIMATION = 8;
|
||||
/// Map of Hyperion instances with grabber name that requested screen capture
|
||||
QMap<int, QString> GrabberWrapper::GRABBER_SYS_CLIENTS = QMap<int, QString>();
|
||||
QMap<int, QString> GrabberWrapper::GRABBER_V4L_CLIENTS = QMap<int, QString>();
|
||||
QMap<int, QString> GrabberWrapper::GRABBER_AUDIO_CLIENTS = QMap<int, QString>();
|
||||
bool GrabberWrapper::GLOBAL_GRABBER_SYS_ENABLE = false;
|
||||
bool GrabberWrapper::GLOBAL_GRABBER_V4L_ENABLE = false;
|
||||
bool GrabberWrapper::GLOBAL_GRABBER_AUDIO_ENABLE = false;
|
||||
|
||||
GrabberWrapper::GrabberWrapper(const QString& grabberName, Grabber * ggrabber, int updateRate_Hz)
|
||||
: _grabberName(grabberName)
|
||||
@@ -38,9 +40,12 @@ GrabberWrapper::GrabberWrapper(const QString& grabberName, Grabber * ggrabber, i
|
||||
connect(_timer, &QTimer::timeout, this, &GrabberWrapper::action);
|
||||
|
||||
// connect the image forwarding
|
||||
(_grabberName.startsWith("V4L"))
|
||||
? connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setV4lImage)
|
||||
: connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setSystemImage);
|
||||
if (_grabberName.startsWith("V4L"))
|
||||
connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setV4lImage);
|
||||
else if (_grabberName.startsWith("Audio"))
|
||||
connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setAudioImage);
|
||||
else
|
||||
connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setSystemImage);
|
||||
|
||||
// listen for source requests
|
||||
connect(GlobalSignals::getInstance(), &GlobalSignals::requestSource, this, &GrabberWrapper::handleSourceRequest);
|
||||
@@ -99,6 +104,12 @@ QStringList GrabberWrapper::getActive(int inst, GrabberTypeFilter type) const
|
||||
result << GRABBER_V4L_CLIENTS.value(inst);
|
||||
}
|
||||
|
||||
if (type == GrabberTypeFilter::AUDIO || type == GrabberTypeFilter::ALL)
|
||||
{
|
||||
if (GRABBER_AUDIO_CLIENTS.contains(inst))
|
||||
result << GRABBER_AUDIO_CLIENTS.value(inst);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -148,6 +159,13 @@ QStringList GrabberWrapper::availableGrabbers(GrabberTypeFilter type)
|
||||
#endif
|
||||
}
|
||||
|
||||
if (type == GrabberTypeFilter::AUDIO || type == GrabberTypeFilter::ALL)
|
||||
{
|
||||
#ifdef ENABLE_AUDIO
|
||||
grabbers << "audio";
|
||||
#endif
|
||||
}
|
||||
|
||||
return grabbers;
|
||||
}
|
||||
|
||||
@@ -186,7 +204,10 @@ void GrabberWrapper::updateTimer(int interval)
|
||||
}
|
||||
|
||||
void GrabberWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
|
||||
{ if(type == settings::SYSTEMCAPTURE && !_grabberName.startsWith("V4L"))
|
||||
{
|
||||
if (type == settings::SYSTEMCAPTURE &&
|
||||
!_grabberName.startsWith("V4L") &&
|
||||
!_grabberName.startsWith("Audio"))
|
||||
{
|
||||
// extract settings
|
||||
const QJsonObject& obj = config.object();
|
||||
@@ -234,26 +255,42 @@ void GrabberWrapper::handleSettingsUpdate(settings::type type, const QJsonDocume
|
||||
|
||||
void GrabberWrapper::handleSourceRequest(hyperion::Components component, int hyperionInd, bool listen)
|
||||
{
|
||||
if(component == hyperion::Components::COMP_GRABBER && !_grabberName.startsWith("V4L"))
|
||||
if (component == hyperion::Components::COMP_GRABBER &&
|
||||
!_grabberName.startsWith("V4L") &&
|
||||
!_grabberName.startsWith("Audio"))
|
||||
{
|
||||
if(listen)
|
||||
if (listen)
|
||||
GRABBER_SYS_CLIENTS.insert(hyperionInd, _grabberName);
|
||||
else
|
||||
GRABBER_SYS_CLIENTS.remove(hyperionInd);
|
||||
|
||||
if(GRABBER_SYS_CLIENTS.empty() || !getSysGrabberState())
|
||||
if (GRABBER_SYS_CLIENTS.empty() || !getSysGrabberState())
|
||||
stop();
|
||||
else
|
||||
start();
|
||||
}
|
||||
else if(component == hyperion::Components::COMP_V4L && _grabberName.startsWith("V4L"))
|
||||
else if (component == hyperion::Components::COMP_V4L &&
|
||||
_grabberName.startsWith("V4L"))
|
||||
{
|
||||
if(listen)
|
||||
if (listen)
|
||||
GRABBER_V4L_CLIENTS.insert(hyperionInd, _grabberName);
|
||||
else
|
||||
GRABBER_V4L_CLIENTS.remove(hyperionInd);
|
||||
|
||||
if(GRABBER_V4L_CLIENTS.empty() || !getV4lGrabberState())
|
||||
if (GRABBER_V4L_CLIENTS.empty() || !getV4lGrabberState())
|
||||
stop();
|
||||
else
|
||||
start();
|
||||
}
|
||||
else if (component == hyperion::Components::COMP_AUDIO &&
|
||||
_grabberName.startsWith("Audio"))
|
||||
{
|
||||
if (listen)
|
||||
GRABBER_AUDIO_CLIENTS.insert(hyperionInd, _grabberName);
|
||||
else
|
||||
GRABBER_AUDIO_CLIENTS.remove(hyperionInd);
|
||||
|
||||
if (GRABBER_AUDIO_CLIENTS.empty())
|
||||
stop();
|
||||
else
|
||||
start();
|
||||
@@ -263,6 +300,11 @@ void GrabberWrapper::handleSourceRequest(hyperion::Components component, int hyp
|
||||
void GrabberWrapper::tryStart()
|
||||
{
|
||||
// verify start condition
|
||||
if(!_grabberName.startsWith("V4L") && !GRABBER_SYS_CLIENTS.empty() && getSysGrabberState())
|
||||
if (!_grabberName.startsWith("V4L") &&
|
||||
!_grabberName.startsWith("Audio") &&
|
||||
!GRABBER_SYS_CLIENTS.empty() &&
|
||||
getSysGrabberState())
|
||||
{
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,9 +243,6 @@ void Hyperion::freeObjects()
|
||||
|
||||
void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
|
||||
{
|
||||
// std::cout << "Hyperion::handleSettingsUpdate" << std::endl;
|
||||
// std::cout << config.toJson().toStdString() << std::endl;
|
||||
|
||||
if(type == settings::COLOR)
|
||||
{
|
||||
const QJsonObject obj = config.object();
|
||||
|
||||
@@ -4,26 +4,83 @@
|
||||
#include <hyperion/ImageProcessor.h>
|
||||
#include <hyperion/ImageToLedsMap.h>
|
||||
|
||||
// Blacborder includes
|
||||
// Blackborder includes
|
||||
#include <blackborder/BlackBorderProcessor.h>
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QRgb>
|
||||
|
||||
using namespace hyperion;
|
||||
|
||||
void ImageProcessor::registerProcessingUnit(
|
||||
int width,
|
||||
int height,
|
||||
int horizontalBorder,
|
||||
int verticalBorder)
|
||||
{
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
_imageToLedColors = QSharedPointer<ImageToLedsMap>(new ImageToLedsMap(
|
||||
_log,
|
||||
width,
|
||||
height,
|
||||
horizontalBorder,
|
||||
verticalBorder,
|
||||
_ledString.leds(),
|
||||
_reducedPixelSetFactorFactor,
|
||||
_accuraryLevel
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
_imageToLedColors = QSharedPointer<ImageToLedsMap>(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// global transform method
|
||||
int ImageProcessor::mappingTypeToInt(const QString& mappingType)
|
||||
{
|
||||
if (mappingType == "unicolor_mean" )
|
||||
{
|
||||
return 1;
|
||||
|
||||
}
|
||||
else if (mappingType == "multicolor_mean_squared" )
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else if (mappingType == "dominant_color" )
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
else if (mappingType == "dominant_color_advanced" )
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// global transform method
|
||||
QString ImageProcessor::mappingTypeToStr(int mappingType)
|
||||
{
|
||||
if (mappingType == 1 )
|
||||
return "unicolor_mean";
|
||||
QString typeText;
|
||||
switch (mappingType) {
|
||||
case 1:
|
||||
typeText = "unicolor_mean";
|
||||
break;
|
||||
case 2:
|
||||
typeText = "multicolor_mean_squared";
|
||||
break;
|
||||
case 3:
|
||||
typeText = "dominant_color";
|
||||
break;
|
||||
case 4:
|
||||
typeText = "dominant_color_advanced";
|
||||
break;
|
||||
default:
|
||||
typeText = "multicolor_mean";
|
||||
break;
|
||||
}
|
||||
|
||||
return "multicolor_mean";
|
||||
return typeText;
|
||||
}
|
||||
|
||||
ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion)
|
||||
@@ -31,10 +88,12 @@ ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion)
|
||||
, _log(nullptr)
|
||||
, _ledString(ledString)
|
||||
, _borderProcessor(new BlackBorderProcessor(hyperion, this))
|
||||
, _imageToLeds(nullptr)
|
||||
, _imageToLedColors(nullptr)
|
||||
, _mappingType(0)
|
||||
, _userMappingType(0)
|
||||
, _hardMappingType(0)
|
||||
, _hardMappingType(-1)
|
||||
, _accuraryLevel(0)
|
||||
, _reducedPixelSetFactorFactor(1)
|
||||
, _hyperion(hyperion)
|
||||
{
|
||||
QString subComponent = hyperion->property("instance").toString();
|
||||
@@ -48,7 +107,6 @@ ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion)
|
||||
|
||||
ImageProcessor::~ImageProcessor()
|
||||
{
|
||||
delete _imageToLeds;
|
||||
}
|
||||
|
||||
void ImageProcessor::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
|
||||
@@ -61,39 +119,40 @@ void ImageProcessor::handleSettingsUpdate(settings::type type, const QJsonDocume
|
||||
{
|
||||
setLedMappingType(newType);
|
||||
}
|
||||
|
||||
int reducedPixelSetFactorFactor = obj["reducedPixelSetFactorFactor"].toString().toInt();
|
||||
setReducedPixelSetFactorFactor(reducedPixelSetFactorFactor);
|
||||
|
||||
int accuracyLevel = obj["accuracyLevel"].toInt();
|
||||
setAccuracyLevel(accuracyLevel);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageProcessor::setSize(unsigned width, unsigned height)
|
||||
void ImageProcessor::setSize(int width, int height)
|
||||
{
|
||||
// Check if the existing buffer-image is already the correct dimensions
|
||||
if (_imageToLeds && _imageToLeds->width() == width && _imageToLeds->height() == height)
|
||||
if (!_imageToLedColors.isNull() && _imageToLedColors->width() == width && _imageToLedColors->height() == height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up the old buffer and mapping
|
||||
delete _imageToLeds;
|
||||
|
||||
// Construct a new buffer and mapping
|
||||
_imageToLeds = (width>0 && height>0) ? (new ImageToLedsMap(width, height, 0, 0, _ledString.leds())) : nullptr;
|
||||
registerProcessingUnit(width, height, 0, 0);
|
||||
}
|
||||
|
||||
void ImageProcessor::setLedString(const LedString& ledString)
|
||||
{
|
||||
if ( _imageToLeds != nullptr)
|
||||
Debug(_log,"");
|
||||
if ( !_imageToLedColors.isNull() )
|
||||
{
|
||||
_ledString = ledString;
|
||||
|
||||
// get current width/height
|
||||
unsigned width = _imageToLeds->width();
|
||||
unsigned height = _imageToLeds->height();
|
||||
|
||||
// Clean up the old buffer and mapping
|
||||
delete _imageToLeds;
|
||||
int width = _imageToLedColors->width();
|
||||
int height = _imageToLedColors->height();
|
||||
|
||||
// Construct a new buffer and mapping
|
||||
_imageToLeds = new ImageToLedsMap(width, height, 0, 0, _ledString.leds());
|
||||
registerProcessingUnit(width, height, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,15 +166,55 @@ bool ImageProcessor::blackBorderDetectorEnabled() const
|
||||
return _borderProcessor->enabled();
|
||||
}
|
||||
|
||||
void ImageProcessor::setReducedPixelSetFactorFactor(int count)
|
||||
{
|
||||
int currentReducedPixelSetFactor= _reducedPixelSetFactorFactor;
|
||||
|
||||
_reducedPixelSetFactorFactor = count;
|
||||
Debug(_log, "Set reduced pixel set factor to %d", _reducedPixelSetFactorFactor);
|
||||
|
||||
if (currentReducedPixelSetFactor != _reducedPixelSetFactorFactor && !_imageToLedColors.isNull())
|
||||
{
|
||||
int width = _imageToLedColors->width();
|
||||
int height = _imageToLedColors->height();
|
||||
|
||||
// Construct a new buffer and mapping
|
||||
registerProcessingUnit(width, height, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageProcessor::setAccuracyLevel(int level)
|
||||
{
|
||||
_accuraryLevel = level;
|
||||
Debug(_log, "Set processing accuracy level to %d", _accuraryLevel);
|
||||
|
||||
if (!_imageToLedColors.isNull())
|
||||
{
|
||||
_imageToLedColors->setAccuracyLevel(_accuraryLevel);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageProcessor::setLedMappingType(int mapType)
|
||||
{
|
||||
int currentMappingType = _mappingType;
|
||||
|
||||
// if the _hardMappingType is >-1 we aren't allowed to overwrite it
|
||||
_userMappingType = mapType;
|
||||
Debug(_log, "set user led mapping to %s", QSTRING_CSTR(mappingTypeToStr(mapType)));
|
||||
|
||||
Debug(_log, "Set user LED mapping to %s", QSTRING_CSTR(mappingTypeToStr(mapType)));
|
||||
|
||||
if(_hardMappingType == -1)
|
||||
{
|
||||
_mappingType = mapType;
|
||||
}
|
||||
|
||||
if (currentMappingType != _mappingType && !_imageToLedColors.isNull())
|
||||
{
|
||||
int width = _imageToLedColors->width();
|
||||
int height = _imageToLedColors->height();
|
||||
|
||||
registerProcessingUnit(width, height, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageProcessor::setHardLedMappingType(int mapType)
|
||||
|
||||
@@ -3,17 +3,26 @@
|
||||
using namespace hyperion;
|
||||
|
||||
ImageToLedsMap::ImageToLedsMap(
|
||||
unsigned width,
|
||||
unsigned height,
|
||||
unsigned horizontalBorder,
|
||||
unsigned verticalBorder,
|
||||
const std::vector<Led>& leds)
|
||||
: _width(width)
|
||||
Logger* log,
|
||||
int width,
|
||||
int height,
|
||||
int horizontalBorder,
|
||||
int verticalBorder,
|
||||
const std::vector<Led>& leds,
|
||||
int reducedPixelSetFactor,
|
||||
int accuracyLevel)
|
||||
: _log(log)
|
||||
, _width(width)
|
||||
, _height(height)
|
||||
, _horizontalBorder(horizontalBorder)
|
||||
, _verticalBorder(verticalBorder)
|
||||
, _nextPixelCount(reducedPixelSetFactor)
|
||||
, _clusterCount()
|
||||
, _colorsMap()
|
||||
{
|
||||
_nextPixelCount = reducedPixelSetFactor + 1;
|
||||
setAccuracyLevel(accuracyLevel);
|
||||
|
||||
// Sanity check of the size of the borders (and width and height)
|
||||
Q_ASSERT(_width > 2*_verticalBorder);
|
||||
Q_ASSERT(_height > 2*_horizontalBorder);
|
||||
@@ -23,10 +32,14 @@ ImageToLedsMap::ImageToLedsMap(
|
||||
// Reserve enough space in the map for the leds
|
||||
_colorsMap.reserve(leds.size());
|
||||
|
||||
const unsigned xOffset = _verticalBorder;
|
||||
const unsigned actualWidth = _width - 2 * _verticalBorder;
|
||||
const unsigned yOffset = _horizontalBorder;
|
||||
const unsigned actualHeight = _height - 2 * _horizontalBorder;
|
||||
const int xOffset = _verticalBorder;
|
||||
const int actualWidth = _width - 2 * _verticalBorder;
|
||||
const int yOffset = _horizontalBorder;
|
||||
const int actualHeight = _height - 2 * _horizontalBorder;
|
||||
|
||||
size_t totalCount = 0;
|
||||
size_t totalCapacity = 0;
|
||||
int ledCounter = 0;
|
||||
|
||||
for (const Led& led : leds)
|
||||
{
|
||||
@@ -38,10 +51,10 @@ ImageToLedsMap::ImageToLedsMap(
|
||||
}
|
||||
|
||||
// Compute the index boundaries for this led
|
||||
unsigned minX_idx = xOffset + unsigned(qRound(actualWidth * led.minX_frac));
|
||||
unsigned maxX_idx = xOffset + unsigned(qRound(actualWidth * led.maxX_frac));
|
||||
unsigned minY_idx = yOffset + unsigned(qRound(actualHeight * led.minY_frac));
|
||||
unsigned maxY_idx = yOffset + unsigned(qRound(actualHeight * led.maxY_frac));
|
||||
int minX_idx = xOffset + int32_t(qRound(actualWidth * led.minX_frac));
|
||||
int maxX_idx = xOffset + int32_t(qRound(actualWidth * led.maxX_frac));
|
||||
int minY_idx = yOffset + int32_t(qRound(actualHeight * led.minY_frac));
|
||||
int maxY_idx = yOffset + int32_t(qRound(actualHeight * led.maxY_frac));
|
||||
|
||||
// make sure that the area is at least a single led large
|
||||
minX_idx = qMin(minX_idx, xOffset + actualWidth - 1);
|
||||
@@ -56,31 +69,70 @@ ImageToLedsMap::ImageToLedsMap(
|
||||
}
|
||||
|
||||
// Add all the indices in the above defined rectangle to the indices for this led
|
||||
const auto maxYLedCount = qMin(maxY_idx, yOffset+actualHeight);
|
||||
const auto maxXLedCount = qMin(maxX_idx, xOffset+actualWidth);
|
||||
const int maxYLedCount = qMin(maxY_idx, yOffset+actualHeight);
|
||||
const int maxXLedCount = qMin(maxX_idx, xOffset+actualWidth);
|
||||
|
||||
std::vector<int32_t> ledColors;
|
||||
ledColors.reserve((size_t) maxXLedCount*maxYLedCount);
|
||||
const int realYLedCount = qAbs(maxYLedCount - minY_idx);
|
||||
const int realXLedCount = qAbs(maxXLedCount - minX_idx);
|
||||
|
||||
for (unsigned y = minY_idx; y < maxYLedCount; ++y)
|
||||
bool skipPixelProcessing {false};
|
||||
if (_nextPixelCount > 1)
|
||||
{
|
||||
for (unsigned x = minX_idx; x < maxXLedCount; ++x)
|
||||
skipPixelProcessing = true;
|
||||
}
|
||||
|
||||
size_t totalSize = static_cast<size_t>(realYLedCount * realXLedCount);
|
||||
|
||||
if (!skipPixelProcessing && totalSize > 1600)
|
||||
{
|
||||
skipPixelProcessing = true;
|
||||
_nextPixelCount = 2;
|
||||
Warning(_log, "Mapping LED/light [%d]. The current mapping area contains %d pixels which is huge. Therefore every %d pixels will be skipped. You can enable reduced processing to hide that warning.", ledCounter, totalSize, _nextPixelCount);
|
||||
}
|
||||
|
||||
std::vector<int> ledColors;
|
||||
ledColors.reserve(totalSize);
|
||||
|
||||
for (int y = minY_idx; y < maxYLedCount; y += _nextPixelCount)
|
||||
{
|
||||
for (int x = minX_idx; x < maxXLedCount; x += _nextPixelCount)
|
||||
{
|
||||
ledColors.push_back(y*width + x);
|
||||
ledColors.push_back( y * width + x);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the constructed vector to the map
|
||||
_colorsMap.push_back(ledColors);
|
||||
|
||||
totalCount += ledColors.size();
|
||||
totalCapacity += ledColors.capacity();
|
||||
|
||||
ledCounter++;
|
||||
}
|
||||
Debug(_log, "Total index number is: %d (memory: %d). Reduced pixel set factor: %d, Accuracy level: %d, Image size: %d x %d, LED areas: %d",
|
||||
totalCount, totalCapacity, reducedPixelSetFactor, accuracyLevel, width, height, leds.size());
|
||||
|
||||
}
|
||||
|
||||
unsigned ImageToLedsMap::width() const
|
||||
int ImageToLedsMap::width() const
|
||||
{
|
||||
return _width;
|
||||
}
|
||||
|
||||
unsigned ImageToLedsMap::height() const
|
||||
int ImageToLedsMap::height() const
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
void ImageToLedsMap::setAccuracyLevel (int accuracyLevel)
|
||||
{
|
||||
if (accuracyLevel > 4 )
|
||||
{
|
||||
Warning(_log, "Accuracy level %d is too high, it will be set to 4", accuracyLevel);
|
||||
accuracyLevel = 4;
|
||||
}
|
||||
//Set cluster number for dominant color advanced
|
||||
_clusterCount = accuracyLevel + 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument &config, Hyperion
|
||||
, _pause(false)
|
||||
, _currentConfigId(SmoothingConfigID::SYSTEM)
|
||||
, _enabled(false)
|
||||
, _enabledSystemCfg(false)
|
||||
, _smoothingType(SmoothingType::Linear)
|
||||
, tempValues(std::vector<uint64_t>(0, 0L))
|
||||
{
|
||||
@@ -114,11 +115,12 @@ void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJson
|
||||
QJsonObject obj = config.object();
|
||||
|
||||
setEnable(obj["enable"].toBool(_enabled));
|
||||
_enabledSystemCfg = _enabled;
|
||||
|
||||
SmoothingCfg cfg(false,
|
||||
static_cast<int64_t>(obj[SETTINGS_KEY_SETTLING_TIME].toInt(DEFAULT_SETTLINGTIME)),
|
||||
static_cast<int64_t>(MS_PER_MICRO / obj[SETTINGS_KEY_UPDATE_FREQUENCY].toDouble(DEFAULT_UPDATEFREQUENCY))
|
||||
);
|
||||
int64_t settlingTime_ms = static_cast<int64_t>(obj[SETTINGS_KEY_SETTLING_TIME].toInt(DEFAULT_SETTLINGTIME));
|
||||
int _updateInterval_ms =static_cast<int>(MS_PER_MICRO / obj[SETTINGS_KEY_UPDATE_FREQUENCY].toDouble(DEFAULT_UPDATEFREQUENCY));
|
||||
|
||||
SmoothingCfg cfg(false, settlingTime_ms, _updateInterval_ms);
|
||||
|
||||
const QString typeString = obj[SETTINGS_KEY_SMOOTHING_TYPE].toString();
|
||||
|
||||
@@ -162,7 +164,10 @@ int LinearColorSmoothing::write(const std::vector<ColorRgb> &ledValues)
|
||||
_previousValues = ledValues;
|
||||
_previousInterpolationTime = micros();
|
||||
|
||||
_timer->start(_updateInterval);
|
||||
if (!_pause)
|
||||
{
|
||||
_timer->start(_updateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -406,7 +411,6 @@ void LinearColorSmoothing::performDecay(const int64_t now) {
|
||||
|
||||
if(microsTillNextAction > SLEEP_RES_MICROS) {
|
||||
const int64_t wait = std::min(microsTillNextAction - SLEEP_RES_MICROS, SLEEP_MAX_MICROS);
|
||||
//usleep(wait);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(wait));
|
||||
}
|
||||
}
|
||||
@@ -511,6 +515,8 @@ void LinearColorSmoothing::clearRememberedFrames()
|
||||
|
||||
void LinearColorSmoothing::queueColors(const std::vector<ColorRgb> &ledColors)
|
||||
{
|
||||
assert (ledColors.size() > 0);
|
||||
|
||||
if (_outputDelay == 0)
|
||||
{
|
||||
// No output delay => immediate write
|
||||
@@ -542,7 +548,6 @@ void LinearColorSmoothing::queueColors(const std::vector<ColorRgb> &ledColors)
|
||||
void LinearColorSmoothing::clearQueuedColors()
|
||||
{
|
||||
_timer->stop();
|
||||
//QMetaObject::invokeMethod(_timer, "stop", Qt::QueuedConnection);
|
||||
_previousValues.clear();
|
||||
|
||||
_targetValues.clear();
|
||||
@@ -560,13 +565,16 @@ void LinearColorSmoothing::componentStateChange(hyperion::Components component,
|
||||
|
||||
void LinearColorSmoothing::setEnable(bool enable)
|
||||
{
|
||||
_enabled = enable;
|
||||
if (!_enabled)
|
||||
if ( _enabled != enable)
|
||||
{
|
||||
clearQueuedColors();
|
||||
_enabled = enable;
|
||||
if (!_enabled)
|
||||
{
|
||||
clearQueuedColors();
|
||||
}
|
||||
// update comp register
|
||||
_hyperion->setNewComponentState(hyperion::COMP_SMOOTHING, enable);
|
||||
}
|
||||
// update comp register
|
||||
_hyperion->setNewComponentState(hyperion::COMP_SMOOTHING, enable);
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::setPause(bool pause)
|
||||
@@ -605,7 +613,7 @@ unsigned LinearColorSmoothing::updateConfig(int cfgID, int settlingTime_ms, doub
|
||||
updateDelay
|
||||
};
|
||||
_cfgList[updatedCfgID] = cfg;
|
||||
DebugIf(verbose && _enabled, _log,"%s", QSTRING_CSTR(getConfig(updatedCfgID)));
|
||||
Debug(_log,"%s", QSTRING_CSTR(getConfig(updatedCfgID)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -662,6 +670,19 @@ bool LinearColorSmoothing::selectConfig(int cfgID, bool force)
|
||||
_interpolationCounter = 0;
|
||||
_interpolationStatCounter = 0;
|
||||
|
||||
//Enable smoothing for effects with smoothing
|
||||
if (cfgID >= SmoothingConfigID::EFFECT_DYNAMIC)
|
||||
{
|
||||
Debug(_log,"Run Effect with Smoothing enabled");
|
||||
_enabledSystemCfg = _enabled;
|
||||
setEnable(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore enabled state after running an effect with smoothing
|
||||
setEnable(_enabledSystemCfg);
|
||||
}
|
||||
|
||||
if (_cfgList[cfgID]._updateInterval != _updateInterval)
|
||||
{
|
||||
|
||||
@@ -669,7 +690,10 @@ bool LinearColorSmoothing::selectConfig(int cfgID, bool force)
|
||||
_updateInterval = _cfgList[cfgID]._updateInterval;
|
||||
if (this->enabled())
|
||||
{
|
||||
_timer->start(_updateInterval);
|
||||
if (!_pause && !_targetValues.empty())
|
||||
{
|
||||
_timer->start(_updateInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
_currentConfigId = cfgID;
|
||||
@@ -691,30 +715,36 @@ QString LinearColorSmoothing::getConfig(int cfgID)
|
||||
{
|
||||
SmoothingCfg cfg = _cfgList[cfgID];
|
||||
|
||||
configText = QString ("[%1] - type: %2, pause: %3, settlingTime: %4ms, interval: %5ms (%6Hz), delay: %7 frames")
|
||||
.arg(cfgID)
|
||||
.arg(SmoothingCfg::EnumToString(cfg._type),(cfg._pause) ? "true" : "false")
|
||||
.arg(cfg._settlingTime)
|
||||
.arg(cfg._updateInterval)
|
||||
.arg(int(MS_PER_MICRO/cfg._updateInterval))
|
||||
.arg(cfg._outputDelay);
|
||||
configText = QString ("[%1] - Type: %2, Pause: %3")
|
||||
.arg(cfgID)
|
||||
.arg(SmoothingCfg::EnumToString(cfg._type),(cfg._pause) ? "true" : "false") ;
|
||||
|
||||
switch (cfg._type) {
|
||||
case SmoothingType::Linear:
|
||||
break;
|
||||
|
||||
case SmoothingType::Decay:
|
||||
{
|
||||
const double thalf = (1.0-std::pow(1.0/2, 1.0/_decay))*_settlingTime;
|
||||
configText += QString (", interpolationRate: %1Hz, dithering: %2, decay: %3 -> halftime: %4ms")
|
||||
.arg(cfg._interpolationRate,0,'f',2)
|
||||
.arg((cfg._dithering) ? "true" : "false")
|
||||
.arg(cfg._decay,0,'f',2)
|
||||
.arg(thalf,0,'f',2);
|
||||
configText += QString (", Interpolation rate: %1Hz, Dithering: %2, decay: %3 -> Halftime: %4ms")
|
||||
.arg(cfg._interpolationRate,0,'f',2)
|
||||
.arg((cfg._dithering) ? "true" : "false")
|
||||
.arg(cfg._decay,0,'f',2)
|
||||
.arg(thalf,0,'f',2);
|
||||
[[fallthrough]];
|
||||
}
|
||||
|
||||
case SmoothingType::Linear:
|
||||
{
|
||||
configText += QString (", Settling time: %1ms, Interval: %2ms (%3Hz)")
|
||||
.arg(cfg._settlingTime)
|
||||
.arg(cfg._updateInterval)
|
||||
.arg(int(MS_PER_MICRO/cfg._updateInterval));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
configText += QString (", delay: %1 frames")
|
||||
.arg(cfg._outputDelay);
|
||||
}
|
||||
|
||||
return configText;
|
||||
}
|
||||
|
||||
@@ -738,7 +768,6 @@ LinearColorSmoothing::SmoothingCfg::SmoothingCfg(bool pause, int64_t settlingTim
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
QString LinearColorSmoothing::SmoothingCfg::EnumToString(SmoothingType type)
|
||||
{
|
||||
if (type == SmoothingType::Linear) {
|
||||
|
||||
@@ -42,11 +42,8 @@ void MultiColorAdjustment::setAdjustmentForLed(const QString& id, int startLed,
|
||||
|
||||
// Get the identified adjustment (don't care if is nullptr)
|
||||
ColorAdjustment * adjustment = getAdjustment(id);
|
||||
|
||||
//Debug(_log,"ColorAdjustment Profile [%s], startLed[%d], endLed[%d]", QSTRING_CSTR(id), startLed, endLed);
|
||||
for (int iLed=startLed; iLed<=endLed; ++iLed)
|
||||
{
|
||||
//Debug(_log,"_ledAdjustments [%d] -> [%p]", iLed, adjustment);
|
||||
_ledAdjustments[iLed] = adjustment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,6 +501,20 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
|
||||
Debug(_log, "GrabberV4L2 records migrated");
|
||||
}
|
||||
|
||||
if (config.contains("grabberAudio"))
|
||||
{
|
||||
QJsonObject newGrabberAudioConfig = config["grabberAudio"].toObject();
|
||||
|
||||
//Add new element enable
|
||||
if (!newGrabberAudioConfig.contains("enable"))
|
||||
{
|
||||
newGrabberAudioConfig["enable"] = false;
|
||||
migrated = true;
|
||||
}
|
||||
config["grabberAudio"] = newGrabberAudioConfig;
|
||||
Debug(_log, "GrabberAudio records migrated");
|
||||
}
|
||||
|
||||
if (config.contains("framegrabber"))
|
||||
{
|
||||
QJsonObject newFramegrabberConfig = config["framegrabber"].toObject();
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
{
|
||||
"$ref": "schema-grabberV4L2.json"
|
||||
},
|
||||
"grabberAudio" :
|
||||
{
|
||||
"$ref": "schema-grabberAudio.json"
|
||||
},
|
||||
"framegrabber" :
|
||||
{
|
||||
"$ref": "schema-framegrabber.json"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<file alias="schema-color.json">schema/schema-color.json</file>
|
||||
<file alias="schema-smoothing.json">schema/schema-smoothing.json</file>
|
||||
<file alias="schema-grabberV4L2.json">schema/schema-grabberV4L2.json</file>
|
||||
<file alias="schema-grabberAudio.json">schema/schema-grabberAudio.json</file>
|
||||
<file alias="schema-framegrabber.json">schema/schema-framegrabber.json</file>
|
||||
<file alias="schema-blackborderdetector.json">schema/schema-blackborderdetector.json</file>
|
||||
<file alias="schema-foregroundEffect.json">schema/schema-foregroundEffect.json</file>
|
||||
|
||||
@@ -9,20 +9,44 @@
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"title" : "edt_conf_color_imageToLedMappingType_title",
|
||||
"enum" : ["multicolor_mean", "unicolor_mean"],
|
||||
"enum" : ["multicolor_mean", "unicolor_mean", "multicolor_mean_squared", "dominant_color", "dominant_color_advanced"],
|
||||
"default" : "multicolor_mean",
|
||||
"options" : {
|
||||
"enum_titles" : ["edt_conf_enum_multicolor_mean", "edt_conf_enum_unicolor_mean"]
|
||||
"enum_titles" : ["edt_conf_enum_multicolor_mean", "edt_conf_enum_unicolor_mean", "edt_conf_enum_multicolor_mean_squared", "edt_conf_enum_dominant_color", "edt_conf_enum_dominant_color_advanced"]
|
||||
},
|
||||
"propertyOrder" : 1
|
||||
},
|
||||
"accuracyLevel": {
|
||||
"type": "integer",
|
||||
"title": "edt_conf_color_accuracyLevel_title",
|
||||
"minimum": 1,
|
||||
"maximum": 4,
|
||||
"default": 2,
|
||||
"propertyOrder": 2,
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"imageToLedMappingType": "dominant_color_advanced"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reducedPixelSetFactorFactor": {
|
||||
"type": "string",
|
||||
"title": "edt_conf_color_reducedPixelSetFactorFactor_title",
|
||||
"default": 0,
|
||||
"enum" : ["0", "1", "2", "3"],
|
||||
"default" : "0",
|
||||
"options" : {
|
||||
"enum_titles" : ["edt_conf_enum_disabled", "edt_conf_enum_low", "edt_conf_enum_medium", "edt_conf_enum_high"]
|
||||
},
|
||||
"propertyOrder": 3
|
||||
},
|
||||
"channelAdjustment" :
|
||||
{
|
||||
"type" : "array",
|
||||
"title" : "edt_conf_color_channelAdjustment_header_title",
|
||||
"minItems": 1,
|
||||
"required" : true,
|
||||
"propertyOrder" : 3,
|
||||
"propertyOrder" : 4,
|
||||
"items" :
|
||||
{
|
||||
"type" : "object",
|
||||
|
||||
151
libsrc/hyperion/schema/schema-grabberAudio.json
Normal file
151
libsrc/hyperion/schema/schema-grabberAudio.json
Normal file
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": true,
|
||||
"title": "edt_conf_audio_heading_title",
|
||||
"properties": {
|
||||
"enable": {
|
||||
"type": "boolean",
|
||||
"title": "edt_conf_general_enable_title",
|
||||
"required": true,
|
||||
"default": false,
|
||||
"propertyOrder": 1
|
||||
},
|
||||
"available_devices": {
|
||||
"type": "string",
|
||||
"title": "edt_conf_grabber_discovered_title",
|
||||
"default": "edt_conf_grabber_discovery_inprogress",
|
||||
"options": {
|
||||
"infoText": "edt_conf_grabber_discovered_title_info"
|
||||
},
|
||||
"propertyOrder": 2,
|
||||
"required": false
|
||||
},
|
||||
"device": {
|
||||
"type": "string",
|
||||
"title": "edt_conf_enum_custom",
|
||||
"default": "auto",
|
||||
"options": {
|
||||
"hidden": true
|
||||
},
|
||||
"required": true,
|
||||
"propertyOrder": 3,
|
||||
"comment": "The 'available_audio_devices' settings are dynamically inserted into the WebUI under PropertyOrder '1'."
|
||||
},
|
||||
"audioEffect": {
|
||||
"type": "string",
|
||||
"title": "edt_conf_audio_effects_title",
|
||||
"required": true,
|
||||
"enum": [ "vuMeter" ],
|
||||
"default": "vuMeter",
|
||||
"options": {
|
||||
"enum_titles": [ "edt_conf_audio_effect_enum_vumeter" ]
|
||||
},
|
||||
"propertyOrder": 4
|
||||
},
|
||||
"vuMeter": {
|
||||
"type": "object",
|
||||
"title": "",
|
||||
"required": true,
|
||||
"propertyOrder": 5,
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"audioEffect": "vuMeter"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"multiplier": {
|
||||
"type": "number",
|
||||
"title": "edt_conf_audio_effect_multiplier_title",
|
||||
"default": 1,
|
||||
"minimum": 0,
|
||||
"step": 0.01,
|
||||
"required": true,
|
||||
"propertyOrder": 1,
|
||||
"comment": "The multiplier is used to scale the audio input signal. Increase or decrease to achieve the desired effect. Set to 0 for auto"
|
||||
},
|
||||
"tolerance": {
|
||||
"type": "number",
|
||||
"title": "edt_conf_audio_effect_tolerance_title",
|
||||
"default": 5,
|
||||
"minimum": 0,
|
||||
"step": 1,
|
||||
"append": "edt_append_percent",
|
||||
"required": true,
|
||||
"propertyOrder": 2,
|
||||
"comment": "The tolerance is a percentage value from 0 - 100 used during auto multiplier calculation."
|
||||
},
|
||||
"hotColor": {
|
||||
"type": "array",
|
||||
"title": "edt_conf_audio_effect_hotcolor_title",
|
||||
"default": [ 255, 0, 0 ],
|
||||
"format": "colorpicker",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"required": true,
|
||||
"propertyOrder": 3,
|
||||
"comment": "Hot Color is the color the led's will reach when audio level exceeds the warn percentage"
|
||||
},
|
||||
"warnColor": {
|
||||
"type": "array",
|
||||
"title": "edt_conf_audio_effect_warncolor_title",
|
||||
"default": [ 255, 255, 0 ],
|
||||
"format": "colorpicker",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"required": true,
|
||||
"propertyOrder": 4,
|
||||
"comment": "Warn Color is the color the led's will reach when audio level exceeds the safe percentage"
|
||||
},
|
||||
"warnValue": {
|
||||
"type": "number",
|
||||
"title": "edt_conf_audio_effect_warnvalue_title",
|
||||
"default": 80,
|
||||
"minimum": 0,
|
||||
"step": 1,
|
||||
"append": "edt_append_percent",
|
||||
"required": true,
|
||||
"propertyOrder": 5,
|
||||
"comment": "Warn percentage is the percentage used to determine the maximum percentage of the audio warning level"
|
||||
},
|
||||
"safeColor": {
|
||||
"type": "array",
|
||||
"title": "edt_conf_audio_effect_safecolor_title",
|
||||
"default": [ 0, 255, 0 ],
|
||||
"format": "colorpicker",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"required": true,
|
||||
"propertyOrder": 6,
|
||||
"comment": "Safe Color is the color the led's will reach when audio level is below the warning percentage"
|
||||
},
|
||||
"safeValue": {
|
||||
"type": "number",
|
||||
"title": "edt_conf_audio_effect_safevalue_title",
|
||||
"default": 45,
|
||||
"minimum": 0,
|
||||
"step": 1,
|
||||
"append": "edt_append_percent",
|
||||
"required": true,
|
||||
"propertyOrder": 7,
|
||||
"comment": "Safe percentage is the percentage used to determine the maximum percentage of the audio safe level"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
@@ -48,6 +48,29 @@
|
||||
"maximum": 253,
|
||||
"default": 240,
|
||||
"propertyOrder": 6
|
||||
},
|
||||
"audioEnable": {
|
||||
"type": "boolean",
|
||||
"required": true,
|
||||
"title": "edt_conf_instC_audioEnable_title",
|
||||
"default": false,
|
||||
"propertyOrder": 7
|
||||
},
|
||||
"audioGrabberDevice": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"title": "edt_conf_instC_video_grabber_device_title",
|
||||
"default": "NONE",
|
||||
"propertyOrder": 7
|
||||
},
|
||||
"audioPriority": {
|
||||
"type": "integer",
|
||||
"required": true,
|
||||
"title": "edt_conf_general_priority_title",
|
||||
"minimum": 100,
|
||||
"maximum": 253,
|
||||
"default": 230,
|
||||
"propertyOrder": 9
|
||||
}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
"time_ms": {
|
||||
"type": "integer",
|
||||
"title": "edt_conf_smooth_time_ms_title",
|
||||
"minimum": 25,
|
||||
"minimum": 1,
|
||||
"maximum": 5000,
|
||||
"default": 200,
|
||||
"default": 150,
|
||||
"append": "edt_append_ms",
|
||||
"propertyOrder": 3
|
||||
},
|
||||
|
||||
@@ -118,7 +118,10 @@ if(ENABLE_DEV_NETWORK)
|
||||
string(REGEX MATCH "[0-9]+|-([A-Za-z0-9_.]+)" MBEDTLS_MAJOR ${MBEDTLS_VERSION})
|
||||
if (MBEDTLS_MAJOR EQUAL "3")
|
||||
target_compile_definitions(leddevice PRIVATE USE_MBEDTLS3)
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
target_compile_features(leddevice PRIVATE cxx_std_20)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_DEV_SERIAL)
|
||||
@@ -160,3 +163,4 @@ endif()
|
||||
if(ENABLE_MDNS)
|
||||
target_link_libraries(leddevice mdns)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -49,11 +49,13 @@ LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent)
|
||||
, _latchTime_ms(0)
|
||||
, _ledCount(0)
|
||||
, _isRestoreOrigState(false)
|
||||
, _isStayOnAfterStreaming(false)
|
||||
, _isEnabled(false)
|
||||
, _isDeviceInitialised(false)
|
||||
, _isDeviceReady(false)
|
||||
, _isOn(false)
|
||||
, _isDeviceInError(false)
|
||||
, _isDeviceRecoverable(false)
|
||||
, _lastWriteTime(QDateTime::currentDateTime())
|
||||
, _enableAttemptsTimer(nullptr)
|
||||
, _enableAttemptTimerInterval(DEFAULT_ENABLE_ATTEMPTS_INTERVAL)
|
||||
@@ -117,7 +119,7 @@ int LedDevice::close()
|
||||
return retval;
|
||||
}
|
||||
|
||||
void LedDevice::setInError(const QString& errorMsg)
|
||||
void LedDevice::setInError(const QString& errorMsg, bool isRecoverable)
|
||||
{
|
||||
_isOn = false;
|
||||
_isDeviceInError = true;
|
||||
@@ -125,6 +127,10 @@ void LedDevice::setInError(const QString& errorMsg)
|
||||
_isEnabled = false;
|
||||
this->stopRefreshTimer();
|
||||
|
||||
if (isRecoverable)
|
||||
{
|
||||
_isDeviceRecoverable = isRecoverable;
|
||||
}
|
||||
Error(_log, "Device disabled, device '%s' signals error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(errorMsg));
|
||||
emit enableStateChanged(_isEnabled);
|
||||
}
|
||||
@@ -170,7 +176,7 @@ void LedDevice::enable()
|
||||
{
|
||||
emit enableStateChanged(false);
|
||||
|
||||
if (_maxEnableAttempts > 0)
|
||||
if (_maxEnableAttempts > 0 && _isDeviceRecoverable)
|
||||
{
|
||||
Debug(_log, "Device's enablement failed - Start retry timer. Retried already done [%d], isEnabled: [%d]", _enableAttempts, _isEnabled);
|
||||
startEnableAttemptsTimer();
|
||||
@@ -234,8 +240,6 @@ void LedDevice::startRefreshTimer()
|
||||
connect(_refreshTimer, &QTimer::timeout, this, &LedDevice::rewriteLEDs);
|
||||
}
|
||||
_refreshTimer->setInterval(_refreshTimerInterval_ms);
|
||||
|
||||
//Debug(_log, "Start refresh timer with interval = %ims", _refreshTimer->interval());
|
||||
_refreshTimer->start();
|
||||
}
|
||||
else
|
||||
@@ -249,11 +253,9 @@ void LedDevice::stopRefreshTimer()
|
||||
{
|
||||
if (_refreshTimer != nullptr)
|
||||
{
|
||||
//Debug(_log, "Stopping refresh timer");
|
||||
_refreshTimer->stop();
|
||||
delete _refreshTimer;
|
||||
_refreshTimer = nullptr;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,27 +263,30 @@ void LedDevice::startEnableAttemptsTimer()
|
||||
{
|
||||
++_enableAttempts;
|
||||
|
||||
if (_enableAttempts <= _maxEnableAttempts)
|
||||
if (_isDeviceRecoverable)
|
||||
{
|
||||
if (_enableAttemptTimerInterval.count() > 0)
|
||||
if (_enableAttempts <= _maxEnableAttempts)
|
||||
{
|
||||
// setup enable retry timer
|
||||
if (_enableAttemptsTimer == nullptr)
|
||||
if (_enableAttemptTimerInterval.count() > 0)
|
||||
{
|
||||
_enableAttemptsTimer = new QTimer(this);
|
||||
_enableAttemptsTimer->setTimerType(Qt::PreciseTimer);
|
||||
connect(_enableAttemptsTimer, &QTimer::timeout, this, &LedDevice::enable);
|
||||
}
|
||||
_enableAttemptsTimer->setInterval(static_cast<int>(_enableAttemptTimerInterval.count() * 1000)); //NOLINT
|
||||
// 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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error(_log, "Device disabled. Maximum number of %d attempts enabling the device reached. Tried for %d seconds.", _maxEnableAttempts, _enableAttempts * _enableAttemptTimerInterval.count());
|
||||
_enableAttempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +307,7 @@ int LedDevice::updateLeds(std::vector<ColorRgb> ledValues)
|
||||
int retval = 0;
|
||||
if (!_isEnabled || !_isOn || !_isDeviceReady || _isDeviceInError)
|
||||
{
|
||||
//std::cout << "LedDevice::updateLeds(), LedDevice NOT ready! ";
|
||||
// LedDevice NOT ready!
|
||||
retval = -1;
|
||||
}
|
||||
else
|
||||
@@ -310,7 +315,6 @@ int LedDevice::updateLeds(std::vector<ColorRgb> ledValues)
|
||||
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;
|
||||
retval = write(ledValues);
|
||||
_lastWriteTime = QDateTime::currentDateTime();
|
||||
|
||||
@@ -323,7 +327,7 @@ int LedDevice::updateLeds(std::vector<ColorRgb> ledValues)
|
||||
}
|
||||
else
|
||||
{
|
||||
//std::cout << "LedDevice::updateLeds(), Skip write. elapsedTime (" << elapsedTimeMs << ") ms < _latchTime_ms (" << _latchTime_ms << ") ms" << std::endl;
|
||||
// Skip write as elapsedTime < latchTime
|
||||
if (_isRefreshEnabled)
|
||||
{
|
||||
//Stop timer to allow for next non-refresh update
|
||||
@@ -340,14 +344,6 @@ int LedDevice::rewriteLEDs()
|
||||
|
||||
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:
|
||||
|
||||
if (!_lastLedValues.empty())
|
||||
{
|
||||
retval = write(_lastLedValues);
|
||||
@@ -465,14 +461,16 @@ bool LedDevice::switchOff()
|
||||
|
||||
bool LedDevice::powerOff()
|
||||
{
|
||||
bool rc{ false };
|
||||
bool rc{ true };
|
||||
|
||||
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 (!_isStayOnAfterStreaming)
|
||||
{
|
||||
rc = true;
|
||||
Debug(_log, "Power Off: %s", QSTRING_CSTR(_activeDeviceType));
|
||||
// Simulate power-off by writing a final "Black" to have a defined outcome
|
||||
if (writeBlack() < 0)
|
||||
{
|
||||
rc = false;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -490,12 +488,15 @@ bool LedDevice::storeState()
|
||||
{
|
||||
bool rc{ true };
|
||||
|
||||
#if 0
|
||||
if (_isRestoreOrigState)
|
||||
{
|
||||
// Save device's original state
|
||||
// _originalStateValues = get device's state;
|
||||
// store original power on/off state, if available
|
||||
}
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -503,12 +504,14 @@ bool LedDevice::restoreState()
|
||||
{
|
||||
bool rc{ true };
|
||||
|
||||
#if 0
|
||||
if (_isRestoreOrigState)
|
||||
{
|
||||
// Restore device's original state
|
||||
// update device using _originalStateValues
|
||||
// update original power on/off state, if supported
|
||||
}
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -699,4 +702,3 @@ QString LedDevice::getColorOrder() const
|
||||
bool LedDevice::componentState() const {
|
||||
return _isEnabled;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,12 +69,14 @@ int LedDeviceTemplate::close()
|
||||
int retval = 0;
|
||||
_isDeviceReady = false;
|
||||
|
||||
#if 0
|
||||
// Test, if device requires closing
|
||||
if ( true /*If device is still open*/ )
|
||||
{
|
||||
// Close device
|
||||
// Everything is OK -> device is closed
|
||||
}
|
||||
#endif
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,9 @@ bool LedDeviceHyperionUsbasp::init(const QJsonObject &deviceConfig)
|
||||
else
|
||||
{
|
||||
Debug(_log, "USB context initialized");
|
||||
//libusb_set_debug(_libusbContext, 3);
|
||||
#if 0
|
||||
libusb_set_debug(_libusbContext, 3);
|
||||
#endif
|
||||
|
||||
// retrieve the list of USB devices
|
||||
libusb_device ** deviceList;
|
||||
|
||||
@@ -331,8 +331,6 @@ const QString &LedDeviceLightpack::getSerialNumber() const
|
||||
int LedDeviceLightpack::writeBytes(uint8_t *data, int size)
|
||||
{
|
||||
int rc = 0;
|
||||
//Debug( _log, "[%s]", QSTRING_CSTR(uint8_t_to_hex_string(data, size, 32)) );
|
||||
|
||||
int error = libusb_control_transfer(_deviceHandle,
|
||||
static_cast<uint8_t>( LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE ),
|
||||
0x09,
|
||||
|
||||
@@ -73,8 +73,8 @@ int ProviderHID::open()
|
||||
// Failed to open the device
|
||||
this->setInError( "Failed to open HID device. Maybe your PID/VID setting is wrong? Make sure to add a udev rule/use sudo." );
|
||||
|
||||
#if 0
|
||||
// http://www.signal11.us/oss/hidapi/
|
||||
/*
|
||||
std::cout << "Showing a list of all available HID devices:" << std::endl;
|
||||
auto devs = hid_enumerate(0x00, 0x00);
|
||||
auto cur_dev = devs;
|
||||
@@ -88,7 +88,8 @@ int ProviderHID::open()
|
||||
cur_dev = cur_dev->next;
|
||||
}
|
||||
hid_free_enumeration(devs);
|
||||
*/
|
||||
#endif
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -45,7 +45,6 @@ const char PANEL_NUM[] = "numPanels";
|
||||
const char PANEL_ID[] = "panelId";
|
||||
const char PANEL_POSITIONDATA[] = "positionData";
|
||||
const char PANEL_SHAPE_TYPE[] = "shapeType";
|
||||
//const char PANEL_ORIENTATION[] = "0";
|
||||
const char PANEL_POS_X[] = "x";
|
||||
const char PANEL_POS_Y[] = "y";
|
||||
|
||||
@@ -72,7 +71,6 @@ const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222;
|
||||
const int API_DEFAULT_PORT = 16021;
|
||||
const char API_BASE_PATH[] = "/api/v1/%1/";
|
||||
const char API_ROOT[] = "";
|
||||
//const char API_EXT_MODE_STRING_V1[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\"}}";
|
||||
const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}";
|
||||
const char API_STATE[] = "state";
|
||||
const char API_PANELLAYOUT[] = "panelLayout";
|
||||
@@ -243,7 +241,6 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
int panelX = panelObj[PANEL_POS_X].toInt();
|
||||
int panelY = panelObj[PANEL_POS_Y].toInt();
|
||||
int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt();
|
||||
//int panelOrientation = panelObj[PANEL_ORIENTATION].toInt();
|
||||
|
||||
DebugIf(verbose,_log, "Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType);
|
||||
|
||||
@@ -613,16 +610,16 @@ bool LedDeviceNanoleaf::storeState()
|
||||
// effect
|
||||
_restApi->setPath(API_EFFECT);
|
||||
|
||||
httpResponse response = _restApi->get();
|
||||
if ( response.error() )
|
||||
httpResponse responseEffects = _restApi->get();
|
||||
if ( responseEffects.error() )
|
||||
{
|
||||
QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason());
|
||||
QString errorReason = QString("Storing device state failed with error: '%1'").arg(responseEffects.getErrorReason());
|
||||
setInError(errorReason);
|
||||
rc = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
QJsonObject effects = response.getBody().object();
|
||||
QJsonObject effects = responseEffects.getBody().object();
|
||||
DebugIf(verbose, _log, "effects: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
|
||||
_originalEffect = effects[API_EFFECT_SELECT].toString();
|
||||
_originalIsDynEffect = _originalEffect == "*Dynamic*" || _originalEffect == "*Solid*";
|
||||
@@ -774,7 +771,7 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set panels not configured to black;
|
||||
// Set panels not configured to black
|
||||
color = ColorRgb::BLACK;
|
||||
DebugIf(verbose3, _log, "[%d] >= panelLedCount [%d] => Set to BLACK", panelCounter, _panelLedCount);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ const char CONFIG_VERBOSE[] = "verbose";
|
||||
const char DEV_DATA_BRIDGEID[] = "bridgeid";
|
||||
const char DEV_DATA_MODEL[] = "modelid";
|
||||
const char DEV_DATA_NAME[] = "name";
|
||||
//const char DEV_DATA_MANUFACTURER[] = "manufacturer";
|
||||
const char DEV_DATA_FIRMWAREVERSION[] = "swversion";
|
||||
const char DEV_DATA_APIVERSION[] = "apiversion";
|
||||
|
||||
@@ -65,7 +64,6 @@ const char API_STREAM_RESPONSE_FORMAT[] = "/%1/%2/%3/%4";
|
||||
// List of resources
|
||||
const char API_XY_COORDINATES[] = "xy";
|
||||
const char API_BRIGHTNESS[] = "bri";
|
||||
//const char API_SATURATION[] = "sat";
|
||||
const char API_TRANSITIONTIME[] = "transitiontime";
|
||||
const char API_MODEID[] = "modelid";
|
||||
|
||||
@@ -188,7 +186,6 @@ CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiCol
|
||||
}
|
||||
if (dBC < lowest)
|
||||
{
|
||||
//lowest = dBC;
|
||||
closestPoint = pBC;
|
||||
}
|
||||
// Change the xy value to a value which is within the reach of the lamp.
|
||||
@@ -1089,7 +1086,7 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig)
|
||||
|
||||
if( _groupId == 0 )
|
||||
{
|
||||
Error(_log, "Disabling usage of HueEntertainmentAPI: Group-ID is invalid", "%d", _groupId);
|
||||
Error(_log, "Disabling usage of HueEntertainmentAPI: Group-ID [%d] is invalid", _groupId);
|
||||
_useHueEntertainmentAPI = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,10 +141,6 @@ bool LedDeviceRazer::checkApiError(const httpResponse& response)
|
||||
else
|
||||
{
|
||||
QString errorReason;
|
||||
|
||||
QString strJson(response.getBody().toJson(QJsonDocument::Compact));
|
||||
//DebugIf(verbose, _log, "Reply: [%s]", strJson.toUtf8().constData());
|
||||
|
||||
QJsonObject jsonObj = response.getBody().object();
|
||||
|
||||
if (!jsonObj[API_RESULT].isNull())
|
||||
|
||||
@@ -20,15 +20,20 @@ const ushort E131_DEFAULT_PORT = 5568;
|
||||
|
||||
/* defined parameters from http://tsp.esta.org/tsp/documents/docs/BSR_E1-31-20xx_CP-2014-1009r2.pdf */
|
||||
const uint32_t VECTOR_ROOT_E131_DATA = 0x00000004;
|
||||
//#define VECTOR_ROOT_E131_EXTENDED 0x00000008
|
||||
|
||||
const uint8_t VECTOR_DMP_SET_PROPERTY = 0x02;
|
||||
const uint32_t VECTOR_E131_DATA_PACKET = 0x00000002;
|
||||
//#define VECTOR_E131_EXTENDED_SYNCHRONIZATION 0x00000001
|
||||
//#define VECTOR_E131_EXTENDED_DISCOVERY 0x00000002
|
||||
//#define VECTOR_UNIVERSE_DISCOVERY_UNIVERSE_LIST 0x00000001
|
||||
//#define E131_E131_UNIVERSE_DISCOVERY_INTERVAL 10 // seconds
|
||||
//#define E131_NETWORK_DATA_LOSS_TIMEOUT 2500 // milli econds
|
||||
//#define E131_DISCOVERY_UNIVERSE 64214
|
||||
|
||||
#if 0
|
||||
#define VECTOR_ROOT_E131_EXTENDED 0x00000008
|
||||
#define VECTOR_E131_EXTENDED_SYNCHRONIZATION 0x00000001
|
||||
#define VECTOR_E131_EXTENDED_DISCOVERY 0x00000002
|
||||
#define VECTOR_UNIVERSE_DISCOVERY_UNIVERSE_LIST 0x00000001
|
||||
#define E131_E131_UNIVERSE_DISCOVERY_INTERVAL 10 // seconds
|
||||
#define E131_NETWORK_DATA_LOSS_TIMEOUT 2500 // milli econds
|
||||
#define E131_DISCOVERY_UNIVERSE 64214
|
||||
#endif
|
||||
|
||||
const int DMX_MAX = 512; // 512 usable slots
|
||||
}
|
||||
|
||||
|
||||
@@ -20,28 +20,30 @@
|
||||
**/
|
||||
|
||||
/* E1.31 Packet Offsets */
|
||||
//#define E131_ROOT_PREAMBLE_SIZE 0
|
||||
//#define E131_ROOT_POSTAMBLE_SIZE 2
|
||||
//#define E131_ROOT_ID 4
|
||||
//#define E131_ROOT_FLENGTH 16
|
||||
//#define E131_ROOT_VECTOR 18
|
||||
//#define E131_ROOT_CID 22
|
||||
#if 0
|
||||
#define E131_ROOT_PREAMBLE_SIZE 0
|
||||
#define E131_ROOT_POSTAMBLE_SIZE 2
|
||||
#define E131_ROOT_ID 4
|
||||
#define E131_ROOT_FLENGTH 16
|
||||
#define E131_ROOT_VECTOR 18
|
||||
#define E131_ROOT_CID 22
|
||||
|
||||
//#define E131_FRAME_FLENGTH 38
|
||||
//#define E131_FRAME_VECTOR 40
|
||||
//#define E131_FRAME_SOURCE 44
|
||||
//#define E131_FRAME_PRIORITY 108
|
||||
//#define E131_FRAME_RESERVED 109
|
||||
//#define E131_FRAME_SEQ 111
|
||||
//#define E131_FRAME_OPT 112
|
||||
//#define E131_FRAME_UNIVERSE 113
|
||||
#define E131_FRAME_FLENGTH 38
|
||||
#define E131_FRAME_VECTOR 40
|
||||
#define E131_FRAME_SOURCE 44
|
||||
#define E131_FRAME_PRIORITY 108
|
||||
#define E131_FRAME_RESERVED 109
|
||||
#define E131_FRAME_SEQ 111
|
||||
#define E131_FRAME_OPT 112
|
||||
#define E131_FRAME_UNIVERSE 113
|
||||
|
||||
//#define E131_DMP_FLENGTH 115
|
||||
//#define E131_DMP_VECTOR 117
|
||||
//#define E131_DMP_TYPE 118
|
||||
//#define E131_DMP_ADDR_FIRST 119
|
||||
//#define E131_DMP_ADDR_INC 121
|
||||
//#define E131_DMP_COUNT 123
|
||||
#define E131_DMP_FLENGTH 115
|
||||
#define E131_DMP_VECTOR 117
|
||||
#define E131_DMP_TYPE 118
|
||||
#define E131_DMP_ADDR_FIRST 119
|
||||
#define E131_DMP_ADDR_INC 121
|
||||
#define E131_DMP_COUNT 123
|
||||
#endif
|
||||
const unsigned int E131_DMP_DATA=125;
|
||||
|
||||
/* E1.31 Packet Structure */
|
||||
|
||||
@@ -23,36 +23,61 @@ const bool verbose = false;
|
||||
const char CONFIG_HOST[] = "host";
|
||||
const char CONFIG_STREAM_PROTOCOL[] = "streamProtocol";
|
||||
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
|
||||
const char CONFIG_STAY_ON_AFTER_STREAMING[] = "stayOnAfterStreaming";
|
||||
|
||||
const char CONFIG_BRIGHTNESS[] = "brightness";
|
||||
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
|
||||
const char CONFIG_SYNC_OVERWRITE[] = "overwriteSync";
|
||||
|
||||
const char CONFIG_STREAM_SEGMENTS[] = "segments";
|
||||
const char CONFIG_STREAM_SEGMENT_ID[] = "streamSegmentId";
|
||||
const char CONFIG_SWITCH_OFF_OTHER_SEGMENTS[] = "switchOffOtherSegments";
|
||||
|
||||
const char DEFAULT_STREAM_PROTOCOL[] = "DDP";
|
||||
|
||||
// UDP-RAW
|
||||
const int UDP_STREAM_DEFAULT_PORT = 19446;
|
||||
const int UDP_MAX_LED_NUM = 490;
|
||||
|
||||
// DDP
|
||||
// Version constraints
|
||||
const char WLED_VERSION_DDP[] = "0.11.0";
|
||||
const char WLED_VERSION_SEGMENT_STREAMING[] = "0.13.3";
|
||||
|
||||
// WLED JSON-API elements
|
||||
const int API_DEFAULT_PORT = -1; //Use default port per communication scheme
|
||||
|
||||
const char API_BASE_PATH[] = "/json/";
|
||||
//const char API_PATH_INFO[] = "info";
|
||||
const char API_PATH_STATE[] = "state";
|
||||
const char API_PATH_INFO[] = "info";
|
||||
|
||||
// List of State Information
|
||||
// List of State keys
|
||||
const char STATE_ON[] = "on";
|
||||
const char STATE_VALUE_TRUE[] = "true";
|
||||
const char STATE_VALUE_FALSE[] = "false";
|
||||
const char STATE_BRI[] = "bri";
|
||||
const char STATE_LIVE[] = "live";
|
||||
const char STATE_LOR[] = "lor";
|
||||
const char STATE_SEG[] = "seg";
|
||||
const char STATE_SEG_ID[] = "id";
|
||||
const char STATE_SEG_LEN[] = "len";
|
||||
const char STATE_SEG_FX[] = "fx";
|
||||
const char STATE_SEG_SX[] = "sx";
|
||||
const char STATE_MAINSEG[] = "mainseg";
|
||||
const char STATE_UDPN[] = "udpn";
|
||||
const char STATE_UDPN_SEND[] = "send";
|
||||
const char STATE_UDPN_RECV[] = "recv";
|
||||
const char STATE_TRANSITIONTIME_CURRENTCALL[] = "tt";
|
||||
|
||||
// List of Info keys
|
||||
const char INFO_VER[] = "ver";
|
||||
const char INFO_LIVESEG[] = "liveseg";
|
||||
|
||||
//Default state values
|
||||
const bool DEFAULT_IS_RESTORE_STATE = false;
|
||||
const bool DEFAULT_IS_STAY_ON_AFTER_STREAMING = false;
|
||||
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
|
||||
const int BRI_MAX = 255;
|
||||
const bool DEFAULT_IS_SYNC_OVERWRITE = true;
|
||||
const int DEFAULT_SEGMENT_ID = -1;
|
||||
const bool DEFAULT_IS_SWITCH_OFF_OTHER_SEGMENTS = true;
|
||||
|
||||
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
|
||||
|
||||
@@ -62,12 +87,16 @@ LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig)
|
||||
: ProviderUdp(deviceConfig), LedDeviceUdpDdp(deviceConfig), LedDeviceUdpRaw(deviceConfig)
|
||||
,_restApi(nullptr)
|
||||
,_apiPort(API_DEFAULT_PORT)
|
||||
,_currentVersion("")
|
||||
,_isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE)
|
||||
,_brightness (BRI_MAX)
|
||||
,_isSyncOverwrite(DEFAULT_IS_SYNC_OVERWRITE)
|
||||
,_originalStateUdpnSend(false)
|
||||
,_originalStateUdpnRecv(true)
|
||||
,_isStreamDDP(true)
|
||||
,_streamSegmentId(DEFAULT_SEGMENT_ID)
|
||||
,_isSwitchOffOtherSegments(DEFAULT_IS_SWITCH_OFF_OTHER_SEGMENTS)
|
||||
,_isStreamToSegment(false)
|
||||
{
|
||||
#ifdef ENABLE_MDNS
|
||||
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
|
||||
@@ -113,6 +142,7 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
_apiPort = API_DEFAULT_PORT;
|
||||
_isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE);
|
||||
_isStayOnAfterStreaming = _devConfig[CONFIG_STAY_ON_AFTER_STREAMING].toBool(DEFAULT_IS_STAY_ON_AFTER_STREAMING);
|
||||
_isSyncOverwrite = _devConfig[CONFIG_SYNC_OVERWRITE].toBool(DEFAULT_IS_SYNC_OVERWRITE);
|
||||
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
|
||||
_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX);
|
||||
@@ -122,6 +152,23 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
|
||||
Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
|
||||
Debug(_log, "Set Brightness to : %d", _brightness);
|
||||
|
||||
|
||||
QJsonObject segments = _devConfig[CONFIG_STREAM_SEGMENTS].toObject();
|
||||
_streamSegmentId = segments[CONFIG_STREAM_SEGMENT_ID].toInt(DEFAULT_SEGMENT_ID);
|
||||
|
||||
if (_streamSegmentId > DEFAULT_SEGMENT_ID)
|
||||
{
|
||||
_isStreamToSegment = true;
|
||||
}
|
||||
_isSwitchOffOtherSegments = segments[CONFIG_SWITCH_OFF_OTHER_SEGMENTS].toBool(DEFAULT_IS_SWITCH_OFF_OTHER_SEGMENTS);
|
||||
|
||||
Debug(_log, "Stream to one segment: %s", _isStreamToSegment ? "Yes" : "No");
|
||||
if (_isStreamToSegment )
|
||||
{
|
||||
Debug(_log, "Stream to segment [%d]", _streamSegmentId);
|
||||
Debug(_log, "Switch-off other segments: %s", _isSwitchOffOtherSegments ? "Yes" : "No");
|
||||
}
|
||||
|
||||
isInitOK = true;
|
||||
}
|
||||
|
||||
@@ -194,79 +241,187 @@ int LedDeviceWled::close()
|
||||
return retval;
|
||||
}
|
||||
|
||||
QString LedDeviceWled::getOnOffRequest(bool isOn) const
|
||||
QJsonObject LedDeviceWled::getUdpnObject(bool isSendOn, bool isRecvOn) const
|
||||
{
|
||||
QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
|
||||
return QString( "\"%1\":%2,\"%3\":%4" ).arg( STATE_ON, state).arg( STATE_LIVE, state);
|
||||
QJsonObject udpnObj
|
||||
{
|
||||
{STATE_UDPN_SEND, isSendOn},
|
||||
{STATE_UDPN_RECV, isRecvOn}
|
||||
};
|
||||
return udpnObj;
|
||||
}
|
||||
|
||||
QString LedDeviceWled::getBrightnessRequest(int bri) const
|
||||
QJsonObject LedDeviceWled::getSegmentObject(int segmentId, bool isOn, int brightness) const
|
||||
{
|
||||
return QString( "\"bri\":%1" ).arg(bri);
|
||||
QJsonObject segmentObj
|
||||
{
|
||||
{STATE_SEG_ID, segmentId},
|
||||
{STATE_ON, isOn}
|
||||
};
|
||||
|
||||
if ( brightness > -1)
|
||||
{
|
||||
segmentObj.insert(STATE_BRI, brightness);
|
||||
}
|
||||
return segmentObj;
|
||||
}
|
||||
|
||||
QString LedDeviceWled::getEffectRequest(int effect, int speed) const
|
||||
{
|
||||
return QString( "\"seg\":{\"fx\":%1,\"sx\":%2}" ).arg(effect).arg(speed);
|
||||
}
|
||||
|
||||
QString LedDeviceWled::getLorRequest(int lor) const
|
||||
{
|
||||
return QString( "\"lor\":%1" ).arg(lor);
|
||||
}
|
||||
|
||||
QString LedDeviceWled::getUdpnRequest(bool isSendOn, bool isRecvOn) const
|
||||
{
|
||||
QString send = isSendOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
|
||||
QString recv = isRecvOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
|
||||
return QString( "\"udpn\":{\"send\":%1,\"recv\":%2}" ).arg(send, recv);
|
||||
}
|
||||
|
||||
bool LedDeviceWled::sendStateUpdateRequest(const QString &request)
|
||||
bool LedDeviceWled::sendStateUpdateRequest(const QJsonObject &request, const QString requestType)
|
||||
{
|
||||
bool rc = true;
|
||||
|
||||
_restApi->setPath(API_PATH_STATE);
|
||||
|
||||
httpResponse response1 = _restApi->put(QString("{%1}").arg(request));
|
||||
if ( response1.error() )
|
||||
httpResponse response = _restApi->put(request);
|
||||
if ( response.error() )
|
||||
{
|
||||
QString errorReason = QString("%1 request failed with error: '%2'").arg(requestType, response.getErrorReason());
|
||||
this->setInError ( errorReason );
|
||||
rc = false;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool LedDeviceWled::isReadyForSegmentStreaming(semver::version& version) const
|
||||
{
|
||||
bool isReady{false};
|
||||
|
||||
if (version.isValid())
|
||||
{
|
||||
semver::version segmentStreamingVersion{WLED_VERSION_SEGMENT_STREAMING};
|
||||
if (version < segmentStreamingVersion)
|
||||
{
|
||||
Warning(_log, "Segment streaming not supported by your WLED device version [%s], minimum version expected [%s].", _currentVersion.getVersion().c_str(), segmentStreamingVersion.getVersion().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug(_log, "Segment streaming is supported by your WLED device version [%s].", _currentVersion.getVersion().c_str());
|
||||
isReady = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error(_log, "Version provided to test for streaming readiness is not valid ");
|
||||
}
|
||||
return isReady;
|
||||
}
|
||||
|
||||
bool LedDeviceWled::isReadyForDDPStreaming(semver::version& version) const
|
||||
{
|
||||
bool isReady{false};
|
||||
|
||||
if (version.isValid())
|
||||
{
|
||||
semver::version ddpVersion{WLED_VERSION_DDP};
|
||||
if (version < 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug(_log, "DDP streaming is supported by your WLED device version [%s]. No limitation in number of LEDs.", _currentVersion.getVersion().c_str());
|
||||
isReady = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error(_log, "Version provided to test for streaming readiness is not valid ");
|
||||
}
|
||||
return isReady;
|
||||
}
|
||||
|
||||
bool LedDeviceWled::powerOn()
|
||||
{
|
||||
bool on = false;
|
||||
if ( _isDeviceReady)
|
||||
{
|
||||
//Power-on WLED device
|
||||
_restApi->setPath(API_PATH_STATE);
|
||||
|
||||
QString cmd = getOnOffRequest(true);
|
||||
|
||||
if ( _isBrightnessOverwrite)
|
||||
QJsonObject cmd;
|
||||
if (_isStreamToSegment)
|
||||
{
|
||||
cmd += "," + getBrightnessRequest(_brightness);
|
||||
if (!isReadyForSegmentStreaming(_currentVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_wledInfo[INFO_LIVESEG].toInt() == -1)
|
||||
{
|
||||
stopEnableAttemptsTimer();
|
||||
this->setInError( "Segment streaming configured, but \"Use main segment only\" in WLED Sync Interface configuration is not enabled!", false);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
QJsonArray propertiesSegments = _originalStateProperties[STATE_SEG].toArray();
|
||||
|
||||
bool isStreamSegmentIdFound { false };
|
||||
|
||||
QJsonArray segments;
|
||||
for (const auto& segmentItem : qAsConst(propertiesSegments))
|
||||
{
|
||||
QJsonObject segmentObj = segmentItem.toObject();
|
||||
|
||||
int segmentID = segmentObj.value(STATE_SEG_ID).toInt();
|
||||
if (segmentID == _streamSegmentId)
|
||||
{
|
||||
isStreamSegmentIdFound = true;
|
||||
int len = segmentObj.value(STATE_SEG_LEN).toInt();
|
||||
if (getLedCount() > len)
|
||||
{
|
||||
QString errorReason = QString("Too many LEDs [%1] configured for segment [%2], which supports maximum [%3] LEDs. Check your WLED setup!").arg(getLedCount()).arg(_streamSegmentId).arg(len);
|
||||
this->setInError(errorReason, false);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int brightness{ -1 };
|
||||
if (_isBrightnessOverwrite)
|
||||
{
|
||||
brightness = _brightness;
|
||||
}
|
||||
segments.append(getSegmentObject(segmentID, true, brightness));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isSwitchOffOtherSegments)
|
||||
{
|
||||
segments.append(getSegmentObject(segmentID, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStreamSegmentIdFound)
|
||||
{
|
||||
QString errorReason = QString("Segment streaming to segment [%1] configured, but segment does not exist on WLED. Check your WLED setup!").arg(_streamSegmentId);
|
||||
this->setInError(errorReason, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
cmd.insert(STATE_SEG, segments);
|
||||
|
||||
//Set segment to be streamed to
|
||||
cmd.insert(STATE_MAINSEG, _streamSegmentId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isBrightnessOverwrite)
|
||||
{
|
||||
cmd.insert(STATE_BRI, _brightness);
|
||||
}
|
||||
}
|
||||
|
||||
cmd.insert(STATE_LIVE, true);
|
||||
cmd.insert(STATE_ON, true);
|
||||
|
||||
if (_isSyncOverwrite)
|
||||
{
|
||||
Debug( _log, "Disable synchronisation with other WLED devices");
|
||||
cmd += "," + getUdpnRequest(false, false);
|
||||
cmd.insert(STATE_UDPN, getUdpnObject(false, false));
|
||||
}
|
||||
|
||||
httpResponse response = _restApi->put(QString("{%1}").arg(cmd));
|
||||
if ( response.error() )
|
||||
{
|
||||
QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
|
||||
this->setInError ( errorReason );
|
||||
on = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
on = true;
|
||||
}
|
||||
on = sendStateUpdateRequest(cmd,"Power-on");
|
||||
}
|
||||
return on;
|
||||
}
|
||||
@@ -280,23 +435,25 @@ bool LedDeviceWled::powerOff()
|
||||
writeBlack();
|
||||
|
||||
//Power-off the WLED device physically
|
||||
_restApi->setPath(API_PATH_STATE);
|
||||
QJsonObject cmd;
|
||||
if (_isStreamToSegment)
|
||||
{
|
||||
QJsonArray segments;
|
||||
segments.append(getSegmentObject(_streamSegmentId, _isStayOnAfterStreaming));
|
||||
cmd.insert(STATE_SEG, segments);
|
||||
}
|
||||
|
||||
QString cmd = getOnOffRequest(false);
|
||||
cmd.insert(STATE_LIVE, false);
|
||||
cmd.insert(STATE_TRANSITIONTIME_CURRENTCALL, 0);
|
||||
cmd.insert(STATE_ON, _isStayOnAfterStreaming);
|
||||
|
||||
if (_isSyncOverwrite)
|
||||
{
|
||||
Debug( _log, "Restore synchronisation with other WLED devices");
|
||||
cmd += "," + getUdpnRequest(_originalStateUdpnSend, _originalStateUdpnRecv);
|
||||
cmd.insert(STATE_UDPN, getUdpnObject(_originalStateUdpnSend, _originalStateUdpnRecv));
|
||||
}
|
||||
|
||||
httpResponse response = _restApi->put(QString("{%1}").arg(cmd));
|
||||
if ( response.error() )
|
||||
{
|
||||
QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
|
||||
this->setInError ( errorReason );
|
||||
off = false;
|
||||
}
|
||||
off = sendStateUpdateRequest(cmd,"Power-off");
|
||||
}
|
||||
return off;
|
||||
}
|
||||
@@ -305,28 +462,33 @@ bool LedDeviceWled::storeState()
|
||||
{
|
||||
bool rc = true;
|
||||
|
||||
if ( _isRestoreOrigState || _isSyncOverwrite )
|
||||
if ( _isRestoreOrigState || _isSyncOverwrite || _isStreamToSegment)
|
||||
{
|
||||
_restApi->setPath(API_PATH_STATE);
|
||||
_restApi->setPath("");
|
||||
|
||||
httpResponse response = _restApi->get();
|
||||
if ( response.error() )
|
||||
{
|
||||
QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason());
|
||||
QString errorReason = QString("Retrieving device properties failed with error: '%1'").arg(response.getErrorReason());
|
||||
setInError(errorReason);
|
||||
rc = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_originalStateProperties = response.getBody().object();
|
||||
_originalStateProperties = response.getBody().object().value(API_PATH_STATE).toObject();
|
||||
DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
|
||||
|
||||
QJsonObject udpn = _originalStateProperties.value("udpn").toObject();
|
||||
QJsonObject udpn = _originalStateProperties.value(STATE_UDPN).toObject();
|
||||
if (!udpn.isEmpty())
|
||||
{
|
||||
_originalStateUdpnSend = udpn["send"].toBool(false);
|
||||
_originalStateUdpnRecv = udpn["recv"].toBool(true);
|
||||
_originalStateUdpnSend = udpn[STATE_UDPN_SEND].toBool(false);
|
||||
_originalStateUdpnRecv = udpn[STATE_UDPN_RECV].toBool(true);
|
||||
}
|
||||
|
||||
_wledInfo = response.getBody().object().value(API_PATH_INFO).toObject();
|
||||
DebugIf(verbose, _log, "info: [%s]", QString(QJsonDocument(_wledInfo).toJson(QJsonDocument::Compact)).toUtf8().constData() );
|
||||
|
||||
_currentVersion.setVersion(_wledInfo.value(INFO_VER).toString().toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,10 +503,32 @@ bool LedDeviceWled::restoreState()
|
||||
{
|
||||
_restApi->setPath(API_PATH_STATE);
|
||||
|
||||
if (_isStreamToSegment)
|
||||
{
|
||||
QJsonArray propertiesSegments = _originalStateProperties[STATE_SEG].toArray();
|
||||
QJsonArray segments;
|
||||
for (const auto& segmentItem : qAsConst(propertiesSegments))
|
||||
{
|
||||
QJsonObject segmentObj = segmentItem.toObject();
|
||||
|
||||
int segmentID = segmentObj.value(STATE_SEG_ID).toInt();
|
||||
if (segmentID == _streamSegmentId)
|
||||
{
|
||||
segmentObj[STATE_ON] = _isStayOnAfterStreaming;
|
||||
}
|
||||
segments.append(segmentObj);
|
||||
}
|
||||
_originalStateProperties[STATE_SEG] = segments;
|
||||
}
|
||||
|
||||
_originalStateProperties[STATE_LIVE] = false;
|
||||
_originalStateProperties[STATE_TRANSITIONTIME_CURRENTCALL] = 0;
|
||||
if (_isStayOnAfterStreaming)
|
||||
{
|
||||
_originalStateProperties[STATE_ON] = true;
|
||||
}
|
||||
|
||||
httpResponse response = _restApi->put(QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
||||
|
||||
httpResponse response = _restApi->put(_originalStateProperties);
|
||||
if ( response.error() )
|
||||
{
|
||||
Warning (_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
|
||||
@@ -364,10 +548,10 @@ QJsonObject LedDeviceWled::discover(const QJsonObject& /*params*/)
|
||||
#ifdef ENABLE_MDNS
|
||||
QString discoveryMethod("mDNS");
|
||||
deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(
|
||||
MdnsServiceRegister::getServiceType(_activeDeviceType),
|
||||
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
|
||||
DEFAULT_DISCOVER_TIMEOUT
|
||||
);
|
||||
MdnsServiceRegister::getServiceType(_activeDeviceType),
|
||||
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
|
||||
DEFAULT_DISCOVER_TIMEOUT
|
||||
);
|
||||
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
|
||||
#endif
|
||||
devicesDiscovered.insert("devices", deviceList);
|
||||
@@ -398,27 +582,21 @@ QJsonObject LedDeviceWled::getProperties(const QJsonObject& params)
|
||||
{
|
||||
Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
|
||||
}
|
||||
|
||||
QJsonObject propertiesDetails = response.getBody().object();
|
||||
|
||||
semver::version currentVersion {""};
|
||||
if (currentVersion.setVersion(propertiesDetails.value("ver").toString().toStdString()))
|
||||
else
|
||||
{
|
||||
semver::version ddpVersion{WLED_VERSION_DDP};
|
||||
if (currentVersion < ddpVersion)
|
||||
QJsonObject propertiesDetails = response.getBody().object();
|
||||
|
||||
_wledInfo = propertiesDetails.value(API_PATH_INFO).toObject();
|
||||
_currentVersion.setVersion(_wledInfo.value(INFO_VER).toString().toStdString());
|
||||
if (!isReadyForDDPStreaming(_currentVersion))
|
||||
{
|
||||
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() );
|
||||
@@ -442,8 +620,23 @@ void LedDeviceWled::identify(const QJsonObject& params)
|
||||
_isRestoreOrigState = true;
|
||||
storeState();
|
||||
|
||||
QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25);
|
||||
sendStateUpdateRequest(request);
|
||||
QJsonObject cmd;
|
||||
|
||||
cmd.insert(STATE_ON, true);
|
||||
cmd.insert(STATE_LOR, 1);
|
||||
|
||||
_streamSegmentId = params[CONFIG_STREAM_SEGMENT_ID].toInt(0);
|
||||
|
||||
QJsonObject segment;
|
||||
segment = getSegmentObject(_streamSegmentId, true, BRI_MAX);
|
||||
segment.insert(STATE_SEG_FX, 25);
|
||||
segment.insert(STATE_SEG_SX, 128);
|
||||
|
||||
QJsonArray segments;
|
||||
segments.append(segment);
|
||||
cmd.insert(STATE_SEG, segments);
|
||||
|
||||
sendStateUpdateRequest(cmd,"Identify");
|
||||
|
||||
wait(DEFAULT_IDENTIFY_TIME);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "LedDeviceUdpDdp.h"
|
||||
#include "LedDeviceUdpRaw.h"
|
||||
|
||||
#include <utils/version.hpp>
|
||||
///
|
||||
/// Implementation of a WLED-device
|
||||
///
|
||||
@@ -146,20 +147,13 @@ private:
|
||||
///
|
||||
bool openRestAPI();
|
||||
|
||||
///
|
||||
/// @brief Get command to power WLED-device on or off
|
||||
///
|
||||
/// @param isOn True, if to switch on device
|
||||
/// @return Command to switch device on/off
|
||||
///
|
||||
QString getOnOffRequest (bool isOn ) const;
|
||||
QJsonObject getUdpnObject(bool send, bool recv) const;
|
||||
QJsonObject getSegmentObject(int segmentId, bool isOn, int brightness=-1) const;
|
||||
|
||||
QString getBrightnessRequest (int bri ) const;
|
||||
QString getEffectRequest(int effect, int speed=128) const;
|
||||
QString getLorRequest(int lor) const;
|
||||
QString getUdpnRequest(bool send, bool recv) const;
|
||||
bool sendStateUpdateRequest(const QJsonObject &request, const QString requestType = "");
|
||||
|
||||
bool sendStateUpdateRequest(const QString &request);
|
||||
bool isReadyForSegmentStreaming(semver::version& version) const;
|
||||
bool isReadyForDDPStreaming(semver::version& version) const;
|
||||
|
||||
QString resolveAddress (const QString& hostName);
|
||||
|
||||
@@ -169,8 +163,11 @@ private:
|
||||
QString _hostAddress;
|
||||
int _apiPort;
|
||||
|
||||
QJsonObject _wledInfo;
|
||||
QJsonObject _originalStateProperties;
|
||||
|
||||
semver::version _currentVersion;
|
||||
|
||||
bool _isBrightnessOverwrite;
|
||||
int _brightness;
|
||||
|
||||
@@ -179,6 +176,10 @@ private:
|
||||
bool _originalStateUdpnRecv;
|
||||
|
||||
bool _isStreamDDP;
|
||||
|
||||
int _streamSegmentId;
|
||||
bool _isSwitchOffOtherSegments;
|
||||
bool _isStreamToSegment;
|
||||
};
|
||||
|
||||
#endif // LEDDEVICEWLED_H
|
||||
|
||||
@@ -82,7 +82,6 @@ const char API_PROP_BRIGHT[] = "bright";
|
||||
// List of Result Information
|
||||
const char API_RESULT_ID[] = "id";
|
||||
const char API_RESULT[] = "result";
|
||||
//const char API_RESULT_OK[] = "OK";
|
||||
|
||||
// List of Error Information
|
||||
const char API_ERROR[] = "error";
|
||||
@@ -383,8 +382,6 @@ bool YeelightLight::streamCommand( const QJsonDocument &command )
|
||||
{
|
||||
log ( 2, "Info:", "Skip write. Device is in error");
|
||||
}
|
||||
|
||||
//log (2,"streamCommand() rc","%d, isON[%d], isInMusicMode[%d]", rc, _isOn, _isInMusicMode );
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -392,8 +389,6 @@ YeelightResponse YeelightLight::handleResponse(int correlationID, QByteArray con
|
||||
{
|
||||
log (3,"handleResponse()","" );
|
||||
|
||||
//std::cout << _name.toStdString() <<"| Response: [" << response.toStdString() << "]" << std::endl << std::flush;
|
||||
|
||||
YeelightResponse yeeResponse;
|
||||
QString errorReason;
|
||||
|
||||
@@ -446,8 +441,6 @@ YeelightResponse YeelightLight::handleResponse(int correlationID, QByteArray con
|
||||
else
|
||||
{
|
||||
int id = jsonObj[API_RESULT_ID].toInt();
|
||||
//log ( 3, "Correlation ID:", "%d", id );
|
||||
|
||||
if ( id != correlationID && TEST_CORRELATION_IDS)
|
||||
{
|
||||
errorReason = QString ("%1| API is out of sync, received ID [%2], expected [%3]").
|
||||
@@ -528,9 +521,6 @@ QJsonObject YeelightLight::getProperties()
|
||||
log (3,"getProperties()","" );
|
||||
QJsonObject properties;
|
||||
|
||||
//Selected properties
|
||||
//QJsonArray propertyList = { API_PROP_NAME, API_PROP_MODEL, API_PROP_POWER, API_PROP_RGB, API_PROP_BRIGHT, API_PROP_CT, API_PROP_FWVER };
|
||||
|
||||
//All properties
|
||||
QJsonArray propertyList = {"power","bright","ct","rgb","hue","sat","color_mode","flowing","delayoff","music_on","name","bg_power","bg_flowing","bg_ct","bg_bright","bg_hue","bg_sat","bg_rgb","nl_br","active_mode" };
|
||||
|
||||
@@ -579,9 +569,6 @@ bool YeelightLight::identify()
|
||||
*/
|
||||
QJsonArray colorflowParams = { API_PROP_COLORFLOW, 6, 0, "500,1,100,100,500,1,16711696,10"};
|
||||
|
||||
//Blink White
|
||||
//QJsonArray colorflowParams = { API_PROP_COLORFLOW, 6, 0, "500,2,4000,1,500,2,4000,50"};
|
||||
|
||||
QJsonDocument command = getCommand( API_METHOD_SETSCENE, colorflowParams );
|
||||
|
||||
if ( writeCommand( command ) < 0 )
|
||||
@@ -819,7 +806,6 @@ bool YeelightLight::setColorRGB(const ColorRgb &color)
|
||||
rc = false;
|
||||
}
|
||||
}
|
||||
//log (2,"setColorRGB() rc","%d, isON[%d], isInMusicMode[%d]", rc, _isOn, _isInMusicMode );
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -914,7 +900,7 @@ bool YeelightLight::setColorHSV(const ColorRgb &colorRGB)
|
||||
}
|
||||
else
|
||||
{
|
||||
//log ( 3, "setColorHSV", "Skip update. Same Color as before");
|
||||
// Skip update. Same Color as before
|
||||
}
|
||||
log( 3,
|
||||
"setColorHSV() rc",
|
||||
@@ -1471,7 +1457,6 @@ void LedDeviceYeelight::identify(const QJsonObject& params)
|
||||
|
||||
int LedDeviceYeelight::write(const std::vector<ColorRgb> & ledValues)
|
||||
{
|
||||
//DebugIf(verbose, _log, "enabled [%d], _isDeviceReady [%d]", _isEnabled, _isDeviceReady);
|
||||
int rc = -1;
|
||||
|
||||
//Update on all Yeelights by iterating through lights and set colors.
|
||||
@@ -1545,8 +1530,5 @@ int LedDeviceYeelight::write(const std::vector<ColorRgb> & ledValues)
|
||||
// Minimum one Yeelight device is working, continue updating devices
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
//DebugIf(verbose, _log, "rc [%d]", rc );
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ const char API_METHOD_MUSIC_MODE[] = "set_music";
|
||||
const int API_METHOD_MUSIC_MODE_ON = 1;
|
||||
const int API_METHOD_MUSIC_MODE_OFF = 0;
|
||||
|
||||
const char API_METHOD_SETRGB[] = "set_rgb";
|
||||
const char API_METHOD_SETSCENE[] = "set_scene";
|
||||
const char API_METHOD_GETPROP[] = "get_prop";
|
||||
|
||||
|
||||
@@ -16,27 +16,38 @@ namespace {
|
||||
|
||||
const QChar ONE_SLASH = '/';
|
||||
|
||||
const int HTTP_STATUS_NO_CONTENT = 204;
|
||||
const int HTTP_STATUS_BAD_REQUEST = 400;
|
||||
const int HTTP_STATUS_UNAUTHORIZED = 401;
|
||||
const int HTTP_STATUS_NOT_FOUND = 404;
|
||||
|
||||
constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 400 };
|
||||
enum HttpStatusCode {
|
||||
NoContent = 204,
|
||||
BadRequest = 400,
|
||||
UnAuthorized = 401,
|
||||
Forbidden = 403,
|
||||
NotFound = 404
|
||||
};
|
||||
|
||||
} //End of constants
|
||||
|
||||
ProviderRestApi::ProviderRestApi(const QString& host, int port, const QString& basePath)
|
||||
:_log(Logger::getInstance("LEDDEVICE"))
|
||||
, _networkManager(nullptr)
|
||||
ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int port, const QString& basePath)
|
||||
: _log(Logger::getInstance("LEDDEVICE"))
|
||||
, _networkManager(nullptr)
|
||||
, _requestTimeout(DEFAULT_REST_TIMEOUT)
|
||||
{
|
||||
_networkManager = new QNetworkAccessManager();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||
_networkManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
#endif
|
||||
|
||||
_apiUrl.setScheme("http");
|
||||
_apiUrl.setScheme(scheme);
|
||||
_apiUrl.setHost(host);
|
||||
_apiUrl.setPort(port);
|
||||
_basePath = basePath;
|
||||
}
|
||||
|
||||
ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int port)
|
||||
: ProviderRestApi(scheme, host, port, "") {}
|
||||
|
||||
ProviderRestApi::ProviderRestApi(const QString& host, int port, const QString& basePath)
|
||||
: ProviderRestApi("http", host, port, basePath) {}
|
||||
|
||||
ProviderRestApi::ProviderRestApi(const QString& host, int port)
|
||||
: ProviderRestApi(host, port, "") {}
|
||||
|
||||
@@ -60,6 +71,12 @@ void ProviderRestApi::setBasePath(const QString& basePath)
|
||||
appendPath(_basePath, basePath);
|
||||
}
|
||||
|
||||
void ProviderRestApi::setPath(const QStringList& pathElements)
|
||||
{
|
||||
_path.clear();
|
||||
appendPath(_path, pathElements.join(ONE_SLASH));
|
||||
}
|
||||
|
||||
void ProviderRestApi::setPath(const QString& path)
|
||||
{
|
||||
_path.clear();
|
||||
@@ -71,6 +88,11 @@ void ProviderRestApi::appendPath(const QString& path)
|
||||
appendPath(_path, path);
|
||||
}
|
||||
|
||||
void ProviderRestApi::appendPath(const QStringList& pathElements)
|
||||
{
|
||||
appendPath(_path, pathElements.join(ONE_SLASH));
|
||||
}
|
||||
|
||||
void ProviderRestApi::appendPath ( QString& path, const QString &appendPath)
|
||||
{
|
||||
if (!appendPath.isEmpty() && appendPath != ONE_SLASH)
|
||||
@@ -130,40 +152,7 @@ httpResponse ProviderRestApi::get()
|
||||
|
||||
httpResponse ProviderRestApi::get(const QUrl& url)
|
||||
{
|
||||
// Perform request
|
||||
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();
|
||||
|
||||
httpResponse response;
|
||||
if (reply->operation() == QNetworkAccessManager::GetOperation)
|
||||
{
|
||||
if(reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
Debug(_log, "GET: [%s]", QSTRING_CSTR( url.toString() ));
|
||||
}
|
||||
response = getResponse(reply );
|
||||
}
|
||||
// Free space.
|
||||
reply->deleteLater();
|
||||
// Return response
|
||||
return response;
|
||||
return executeOperation(QNetworkAccessManager::GetOperation, url);
|
||||
}
|
||||
|
||||
httpResponse ProviderRestApi::put(const QJsonObject &body)
|
||||
@@ -178,40 +167,7 @@ httpResponse ProviderRestApi::put(const QString &body)
|
||||
|
||||
httpResponse ProviderRestApi::put(const QUrl &url, const QByteArray &body)
|
||||
{
|
||||
// Perform request
|
||||
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();
|
||||
|
||||
httpResponse response;
|
||||
if (reply->operation() == QNetworkAccessManager::PutOperation)
|
||||
{
|
||||
if(reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
Debug(_log, "PUT: [%s] [%s]", QSTRING_CSTR( url.toString() ),body.constData() );
|
||||
}
|
||||
response = getResponse(reply);
|
||||
}
|
||||
// Free space.
|
||||
reply->deleteLater();
|
||||
|
||||
// Return response
|
||||
return response;
|
||||
return executeOperation(QNetworkAccessManager::PutOperation, url, body);
|
||||
}
|
||||
|
||||
httpResponse ProviderRestApi::post(const QJsonObject& body)
|
||||
@@ -226,76 +182,69 @@ httpResponse ProviderRestApi::post(const QString& body)
|
||||
|
||||
httpResponse ProviderRestApi::post(const QUrl& url, const QByteArray& body)
|
||||
{
|
||||
// Perform request
|
||||
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();
|
||||
|
||||
httpResponse response;
|
||||
if (reply->operation() == QNetworkAccessManager::PostOperation)
|
||||
{
|
||||
if(reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
Debug(_log, "POST: [%s] [%s]", QSTRING_CSTR( url.toString() ),body.constData() );
|
||||
}
|
||||
response = getResponse(reply);
|
||||
}
|
||||
// Free space.
|
||||
reply->deleteLater();
|
||||
|
||||
// Return response
|
||||
return response;
|
||||
return executeOperation(QNetworkAccessManager::PostOperation, url, body);
|
||||
}
|
||||
|
||||
httpResponse ProviderRestApi::deleteResource(const QUrl& url)
|
||||
{
|
||||
return executeOperation(QNetworkAccessManager::DeleteOperation, url);
|
||||
}
|
||||
|
||||
httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation operation, const QUrl& url, const QByteArray& body)
|
||||
{
|
||||
// Perform request
|
||||
QNetworkRequest request(_networkRequestHeaders);
|
||||
request.setUrl(url);
|
||||
request.setOriginatingObject(this);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
_networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count());
|
||||
_networkManager->setTransferTimeout(_requestTimeout.count());
|
||||
#endif
|
||||
|
||||
QNetworkReply* reply = _networkManager->deleteResource(request);
|
||||
QDateTime start = QDateTime::currentDateTime();
|
||||
QString opCode;
|
||||
QNetworkReply* reply;
|
||||
switch (operation) {
|
||||
case QNetworkAccessManager::GetOperation:
|
||||
opCode = "GET";
|
||||
reply = _networkManager->get(request);
|
||||
break;
|
||||
case QNetworkAccessManager::PutOperation:
|
||||
opCode = "PUT";
|
||||
reply = _networkManager->put(request, body);
|
||||
break;
|
||||
case QNetworkAccessManager::PostOperation:
|
||||
opCode = "POST";
|
||||
reply = _networkManager->post(request, body);
|
||||
break;
|
||||
case QNetworkAccessManager::DeleteOperation:
|
||||
opCode = "DELETE";
|
||||
reply = _networkManager->deleteResource(request);
|
||||
break;
|
||||
default:
|
||||
Error(_log, "Unsupported operation");
|
||||
return httpResponse();
|
||||
}
|
||||
|
||||
// Connect requestFinished signal to quit slot of the loop.
|
||||
QEventLoop loop;
|
||||
QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
// 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());
|
||||
ReplyTimeout* timeout = ReplyTimeout::set(reply, _requestTimeout.count());
|
||||
#endif
|
||||
|
||||
httpResponse response;
|
||||
if (reply->operation() == QNetworkAccessManager::DeleteOperation)
|
||||
{
|
||||
if(reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
Debug(_log, "DELETE: [%s]", QSTRING_CSTR(url.toString()));
|
||||
}
|
||||
response = getResponse(reply);
|
||||
}
|
||||
// Go into the loop until the request is finished.
|
||||
loop.exec();
|
||||
QDateTime end = QDateTime::currentDateTime();
|
||||
|
||||
httpResponse response = (reply->operation() == operation) ? getResponse(reply) : httpResponse();
|
||||
|
||||
Debug(_log, "%s took %lldms, HTTP %d: [%s] [%s]", QSTRING_CSTR(opCode), start.msecsTo(end), response.getHttpStatusCode(), QSTRING_CSTR(url.toString()), body.constData());
|
||||
|
||||
// Free space.
|
||||
reply->deleteLater();
|
||||
|
||||
// Return response
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -303,72 +252,74 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
|
||||
{
|
||||
httpResponse response;
|
||||
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
HttpStatusCode httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
response.setHttpStatusCode(httpStatusCode);
|
||||
response.setNetworkReplyError(reply->error());
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError)
|
||||
{
|
||||
if ( httpStatusCode != HTTP_STATUS_NO_CONTENT ){
|
||||
QByteArray replyData = reply->readAll();
|
||||
QByteArray replyData = reply->readAll();
|
||||
|
||||
if (!replyData.isEmpty())
|
||||
if (!replyData.isEmpty())
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(replyData, &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(replyData, &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
//Received not valid JSON response
|
||||
//std::cout << "Response: [" << replyData.toStdString() << "]" << std::endl;
|
||||
response.setError(true);
|
||||
response.setErrorReason(error.errorString());
|
||||
}
|
||||
else
|
||||
{
|
||||
//std::cout << "Response: [" << QString(jsonDoc.toJson(QJsonDocument::Compact)).toStdString() << "]" << std::endl;
|
||||
response.setBody(jsonDoc);
|
||||
}
|
||||
//Received not valid JSON response
|
||||
response.setError(true);
|
||||
response.setErrorReason(error.errorString());
|
||||
}
|
||||
else
|
||||
{ // Create valid body which is empty
|
||||
response.setBody(QJsonDocument());
|
||||
{
|
||||
response.setBody(jsonDoc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // Create valid body which is empty
|
||||
response.setBody(QJsonDocument());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug(_log, "Reply.httpStatusCode [%d]", httpStatusCode );
|
||||
QString errorReason;
|
||||
if (httpStatusCode > 0) {
|
||||
QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
QString advise;
|
||||
switch ( httpStatusCode ) {
|
||||
case HTTP_STATUS_BAD_REQUEST:
|
||||
case HttpStatusCode::BadRequest:
|
||||
advise = "Check Request Body";
|
||||
break;
|
||||
case HTTP_STATUS_UNAUTHORIZED:
|
||||
case HttpStatusCode::UnAuthorized:
|
||||
advise = "Check Authentication Token (API Key)";
|
||||
break;
|
||||
case HTTP_STATUS_NOT_FOUND:
|
||||
case HttpStatusCode::Forbidden:
|
||||
advise = "No permission to access the given resource";
|
||||
break;
|
||||
case HttpStatusCode::NotFound:
|
||||
advise = "Check Resource given";
|
||||
break;
|
||||
default:
|
||||
advise = httpReason;
|
||||
break;
|
||||
}
|
||||
errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise);
|
||||
}
|
||||
else
|
||||
{
|
||||
errorReason = reply->errorString();
|
||||
if (reply->error() == QNetworkReply::OperationCanceledError)
|
||||
{
|
||||
response.setError(true);
|
||||
response.setErrorReason(errorReason);
|
||||
errorReason = "Network request timeout error";
|
||||
}
|
||||
else
|
||||
{
|
||||
errorReason = reply->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
// Create valid body which is empty
|
||||
response.setBody(QJsonDocument());
|
||||
}
|
||||
response.setError(true);
|
||||
response.setErrorReason(errorReason);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@@ -388,3 +339,8 @@ void ProviderRestApi::setHeader(QNetworkRequest::KnownHeaders header, const QVar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProviderRestApi::setHeader(const QByteArray &headerName, const QByteArray &headerValue)
|
||||
{
|
||||
_networkRequestHeaders.setRawHeader(headerName, headerValue);
|
||||
}
|
||||
|
||||
@@ -13,15 +13,22 @@
|
||||
#include <QBasicTimer>
|
||||
#include <QTimerEvent>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 1000 };
|
||||
|
||||
//Set QNetworkReply timeout without external timer
|
||||
//https://stackoverflow.com/questions/37444539/how-to-set-qnetworkreply-timeout-without-external-timer
|
||||
|
||||
class ReplyTimeout : public QObject {
|
||||
class ReplyTimeout : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum HandleMethod { Abort, Close };
|
||||
|
||||
ReplyTimeout(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) :
|
||||
QObject(reply), m_method(method)
|
||||
QObject(reply), m_method(method), m_timedout(false)
|
||||
{
|
||||
Q_ASSERT(reply);
|
||||
if (reply && reply->isRunning()) {
|
||||
@@ -29,20 +36,30 @@ public:
|
||||
connect(reply, &QNetworkReply::finished, this, &QObject::deleteLater);
|
||||
}
|
||||
}
|
||||
static void set(QNetworkReply* reply, const int timeout, HandleMethod method = Abort)
|
||||
|
||||
bool isTimedout() const
|
||||
{
|
||||
new ReplyTimeout(reply, timeout, method);
|
||||
return m_timedout;
|
||||
}
|
||||
|
||||
static ReplyTimeout * set(QNetworkReply* reply, const int timeout, HandleMethod method = Abort)
|
||||
{
|
||||
return new ReplyTimeout(reply, timeout, method);
|
||||
}
|
||||
|
||||
signals:
|
||||
void timedout();
|
||||
|
||||
protected:
|
||||
QBasicTimer m_timer;
|
||||
HandleMethod m_method;
|
||||
|
||||
void timerEvent(QTimerEvent * ev) override {
|
||||
if (!m_timer.isActive() || ev->timerId() != m_timer.timerId())
|
||||
return;
|
||||
auto reply = static_cast<QNetworkReply*>(parent());
|
||||
if (reply->isRunning())
|
||||
{
|
||||
m_timedout = true;
|
||||
emit timedout();
|
||||
if (m_method == Close)
|
||||
reply->close();
|
||||
else if (m_method == Abort)
|
||||
@@ -50,6 +67,10 @@ protected:
|
||||
m_timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
QBasicTimer m_timer;
|
||||
HandleMethod m_method;
|
||||
bool m_timedout;
|
||||
};
|
||||
|
||||
///
|
||||
@@ -104,11 +125,12 @@ private:
|
||||
///
|
||||
///@endcode
|
||||
///
|
||||
class ProviderRestApi
|
||||
class ProviderRestApi : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
///
|
||||
/// @brief Constructor of the REST-API wrapper
|
||||
///
|
||||
ProviderRestApi();
|
||||
@@ -121,6 +143,15 @@ public:
|
||||
///
|
||||
explicit ProviderRestApi(const QString& host, int port);
|
||||
|
||||
///
|
||||
/// @brief Constructor of the REST-API wrapper
|
||||
///
|
||||
/// @param[in] scheme
|
||||
/// @param[in] host
|
||||
/// @param[in] port
|
||||
///
|
||||
explicit ProviderRestApi(const QString& scheme, const QString& host, int port);
|
||||
|
||||
///
|
||||
/// @brief Constructor of the REST-API wrapper
|
||||
///
|
||||
@@ -130,10 +161,20 @@ public:
|
||||
///
|
||||
explicit ProviderRestApi(const QString& host, int port, const QString& basePath);
|
||||
|
||||
///
|
||||
/// @brief Constructor of the REST-API wrapper
|
||||
///
|
||||
/// @param[in] scheme
|
||||
/// @param[in] host
|
||||
/// @param[in] port
|
||||
/// @param[in] API base-path
|
||||
///
|
||||
explicit ProviderRestApi(const QString& scheme, const QString& host, int port, const QString& basePath);
|
||||
|
||||
///
|
||||
/// @brief Destructor of the REST-API wrapper
|
||||
///
|
||||
virtual ~ProviderRestApi();
|
||||
virtual ~ProviderRestApi() override;
|
||||
|
||||
///
|
||||
/// @brief Set an API's host
|
||||
@@ -177,6 +218,12 @@ public:
|
||||
///
|
||||
void setPath(const QString& path);
|
||||
|
||||
/// @brief Set an API's path to address resources
|
||||
///
|
||||
/// @param[in] pathElements to form a path, e.g. (lights,1,state) results in "/lights/1/state/"
|
||||
///
|
||||
void setPath(const QStringList& pathElements);
|
||||
|
||||
///
|
||||
/// @brief Append an API's path element to path set before
|
||||
///
|
||||
@@ -184,6 +231,13 @@ public:
|
||||
///
|
||||
void appendPath(const QString& appendPath);
|
||||
|
||||
///
|
||||
/// @brief Append API's path elements to path set before
|
||||
///
|
||||
/// @param[in] pathElements
|
||||
///
|
||||
void appendPath(const QStringList& pathElements);
|
||||
|
||||
///
|
||||
/// @brief Set an API's fragment
|
||||
///
|
||||
@@ -283,14 +337,28 @@ public:
|
||||
/// @param[in] The type of the header field.
|
||||
/// @param[in] The value of the header field.
|
||||
/// If the header field exists, the value will be combined as comma separated string.
|
||||
|
||||
void setHeader(QNetworkRequest::KnownHeaders header, const QVariant& value);
|
||||
|
||||
///
|
||||
/// Set a header field.
|
||||
///
|
||||
/// @param[in] The type of the header field.
|
||||
/// @param[in] The value of the header field.
|
||||
/// If the header field exists, the value will override the previous setting.
|
||||
void setHeader(const QByteArray &headerName, const QByteArray &headerValue);
|
||||
|
||||
///
|
||||
/// Remove all header fields.
|
||||
///
|
||||
void removeAllHeaders() { _networkRequestHeaders = QNetworkRequest(); }
|
||||
|
||||
///
|
||||
/// Sets the timeout time frame after a request is aborted
|
||||
/// Zero means no timer is set.
|
||||
///
|
||||
/// @param[in] timeout in milliseconds.
|
||||
void setTransferTimeout(std::chrono::milliseconds timeout = DEFAULT_REST_TIMEOUT) { _requestTimeout = timeout; }
|
||||
|
||||
///
|
||||
/// @brief Set the common logger for LED-devices.
|
||||
///
|
||||
@@ -308,10 +376,14 @@ private:
|
||||
///
|
||||
static void appendPath (QString &path, const QString &appendPath) ;
|
||||
|
||||
|
||||
httpResponse executeOperation(QNetworkAccessManager::Operation op, const QUrl& url, const QByteArray& body = {});
|
||||
|
||||
Logger* _log;
|
||||
|
||||
// QNetworkAccessManager object for sending REST-requests.
|
||||
QNetworkAccessManager* _networkManager;
|
||||
std::chrono::milliseconds _requestTimeout;
|
||||
|
||||
QUrl _apiUrl;
|
||||
|
||||
|
||||
@@ -92,9 +92,6 @@ int LedDeviceFile::close()
|
||||
int LedDeviceFile::write(const std::vector<ColorRgb> & ledValues)
|
||||
{
|
||||
QTextStream out(_file);
|
||||
|
||||
//printLedValues (ledValues);
|
||||
|
||||
if ( _printTimeStamp )
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
@@ -179,8 +179,6 @@ int LedDevicePiBlaster::write(const std::vector<ColorRgb> & ledValues)
|
||||
continue;
|
||||
}
|
||||
|
||||
// fprintf(_fid, "%i=%f\n", iPins[iPin], pwmDutyCycle);
|
||||
|
||||
if ( (fprintf(_fid, "%i=%f\n", i, pwmDutyCycle) < 0) || (fflush(_fid) < 0))
|
||||
{
|
||||
if (_fid != nullptr)
|
||||
|
||||
@@ -94,7 +94,7 @@ void LedDeviceAdalight::prepareHeader()
|
||||
break;
|
||||
|
||||
case Adalight::AWA:
|
||||
_bufferLength += 7;
|
||||
_bufferLength += 8;
|
||||
[[fallthrough]];
|
||||
case Adalight::ADA:
|
||||
[[fallthrough]];
|
||||
@@ -162,14 +162,20 @@ int LedDeviceAdalight::write(const std::vector<ColorRgb> & ledValues)
|
||||
{
|
||||
whiteChannelExtension(writer);
|
||||
|
||||
uint16_t fletcher1 = 0, fletcher2 = 0;
|
||||
uint16_t fletcher1 = 0;
|
||||
uint16_t fletcher2 = 0;
|
||||
uint16_t fletcherExt = 0;
|
||||
uint8_t position = 0;
|
||||
|
||||
while (hasher < writer)
|
||||
{
|
||||
fletcherExt = (fletcherExt + (*(hasher) ^ (position++))) % 255;
|
||||
fletcher1 = (fletcher1 + *(hasher++)) % 255;
|
||||
fletcher2 = (fletcher2 + fletcher1) % 255;
|
||||
}
|
||||
*(writer++) = static_cast<uint8_t>(fletcher1);
|
||||
*(writer++) = static_cast<uint8_t>(fletcher2);
|
||||
*(writer++) = static_cast<uint8_t>((fletcherExt != 0x41) ? fletcherExt : 0xaa);
|
||||
}
|
||||
_bufferLength = writer - _ledBuffer.data();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ bool LedDeviceDMX::init(const QJsonObject &deviceConfig)
|
||||
}
|
||||
else
|
||||
{
|
||||
//Error(_log, "unknown dmx device type %s", QSTRING_CSTR(dmxString));
|
||||
QString errortext = QString ("unknown dmx device type: %1").arg(dmxTypeString);
|
||||
this->setInError(errortext);
|
||||
return false;
|
||||
|
||||
@@ -24,7 +24,6 @@ bool LedDeviceKarate::init(const QJsonObject& deviceConfig)
|
||||
{
|
||||
if (_ledCount != 8 && _ledCount != 16)
|
||||
{
|
||||
//Error( _log, "%d channels configured. This should always be 16!", _ledCount);
|
||||
QString errortext = QString("%1 channels configured. This should always be 8 or 16!").arg(_ledCount);
|
||||
this->setInError(errortext);
|
||||
isInitOK = false;
|
||||
|
||||
@@ -40,7 +40,6 @@ bool LedDeviceSedu::init(const QJsonObject &deviceConfig)
|
||||
|
||||
if (_ledBuffer.empty())
|
||||
{
|
||||
//Warning(_log, "More rgb-channels required then available");
|
||||
QString errortext = "More rgb-channels required then available";
|
||||
this->setInError(errortext);
|
||||
}
|
||||
|
||||
@@ -207,12 +207,12 @@ bool ProviderRs232::tryOpen(int delayAfterConnect_ms)
|
||||
return _rs232Port.isOpen();
|
||||
}
|
||||
|
||||
void ProviderRs232::setInError(const QString& errorMsg)
|
||||
void ProviderRs232::setInError(const QString& errorMsg, bool isRecoverable)
|
||||
{
|
||||
_rs232Port.clearError();
|
||||
this->close();
|
||||
|
||||
LedDevice::setInError( errorMsg );
|
||||
LedDevice::setInError( errorMsg, isRecoverable );
|
||||
}
|
||||
|
||||
int ProviderRs232::writeBytes(const qint64 size, const uint8_t *data)
|
||||
@@ -273,13 +273,6 @@ void ProviderRs232::readFeedback()
|
||||
{
|
||||
//Output as received
|
||||
std::cout << readData.toStdString();
|
||||
|
||||
//Output as Hex
|
||||
//#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||
// std::cout << readData.toHex(':').toStdString();
|
||||
//#else
|
||||
// std::cout << readData.toHex().toStdString();
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,9 +119,10 @@ protected slots:
|
||||
///
|
||||
/// @brief Set device in error state
|
||||
///
|
||||
/// @param errorMsg The error message to be logged
|
||||
/// @param[in] errorMsg The error message to be logged
|
||||
/// @param[in] isRecoverable If False, no further retries will be done
|
||||
///
|
||||
void setInError( const QString& errorMsg) override;
|
||||
void setInError( const QString& errorMsg, bool isRecoverable=true) override;
|
||||
|
||||
///
|
||||
/// @brief Handle any feedback provided by the device
|
||||
|
||||
@@ -117,10 +117,6 @@ void LedDeviceSK9822::bufferWithAdjustedCurrent(std::vector<uint8_t> &txBuf, con
|
||||
txBuf[b + 1] = red;
|
||||
txBuf[b + 2] = green;
|
||||
txBuf[b + 3] = blue;
|
||||
|
||||
//if(iLed == 0) {
|
||||
// std::cout << std::to_string((int)rgb.red) << "," << std::to_string((int)rgb.green) << "," << std::to_string((int)rgb.blue) << ": " << std::to_string(maxValue) << (maxValue >= threshold ? " >= " : " < ") << std::to_string(threshold) << " -> " << std::to_string((int)(level&SK9822_GBC_MAX_LEVEL))<< "@" << std::to_string((int)red) << "," << std::to_string((int)green) << "," << std::to_string((int)blue) << std::endl;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,6 @@ bool LedDeviceSk6822SPI::init(const QJsonObject &deviceConfig)
|
||||
WarningIf(( _baudRate_Hz < 2000000 || _baudRate_Hz > 2460000 ), _log, "SPI rate %d outside recommended range (2000000 -> 2460000)", _baudRate_Hz);
|
||||
|
||||
_ledBuffer.resize( (_ledRGBCount * SPI_BYTES_PER_COLOUR) + (_ledCount * SPI_BYTES_WAIT_TIME ) + SPI_FRAME_END_LATCH_BYTES, 0x00);
|
||||
// Debug(_log, "_ledBuffer.resize(_ledRGBCount:%d * SPI_BYTES_PER_COLOUR:%d) + ( _ledCount:%d * SPI_BYTES_WAIT_TIME:%d ) + SPI_FRAME_END_LATCH_BYTES:%d, 0x00)", _ledRGBCount, SPI_BYTES_PER_COLOUR, _ledCount, SPI_BYTES_WAIT_TIME, SPI_FRAME_END_LATCH_BYTES);
|
||||
|
||||
isInitOK = true;
|
||||
}
|
||||
|
||||
@@ -95,7 +93,7 @@ int LedDeviceSk6822SPI::write(const std::vector<ColorRgb> &ledValues)
|
||||
spi_ptr += SPI_BYTES_WAIT_TIME; // the wait between led time is all zeros
|
||||
}
|
||||
|
||||
/*
|
||||
#if 0
|
||||
// debug the whole SPI packet
|
||||
char debug_line[2048];
|
||||
int ptr=0;
|
||||
@@ -114,7 +112,7 @@ int LedDeviceSk6822SPI::write(const std::vector<ColorRgb> &ledValues)
|
||||
ptr = 0;
|
||||
}
|
||||
}
|
||||
*/
|
||||
#endif
|
||||
|
||||
return writeBytes(_ledBuffer.size(), _ledBuffer.data());
|
||||
}
|
||||
|
||||
@@ -35,6 +35,46 @@
|
||||
"access": "expert",
|
||||
"propertyOrder": 3
|
||||
},
|
||||
"segments": {
|
||||
"type": "object",
|
||||
"title": "Segment streaming",
|
||||
"required": false,
|
||||
"properties": {
|
||||
"segmentList": {
|
||||
"type": "string",
|
||||
"title": "edt_dev_spec_segments_title",
|
||||
"enum": [ "-1" ],
|
||||
"default": "-1",
|
||||
"options": {
|
||||
"enum_titles": [ "edt_dev_spec_segments_disabled_title" ]
|
||||
},
|
||||
"propertyOrder": 1
|
||||
},
|
||||
"streamSegmentId": {
|
||||
"type": "integer",
|
||||
"title": "edt_dev_spec_segmentId_title",
|
||||
"default": -1,
|
||||
"minimum": -1,
|
||||
"maximum": 16,
|
||||
"options": {
|
||||
"hidden": true
|
||||
},
|
||||
"access": "expert",
|
||||
"propertyOrder": 2
|
||||
},
|
||||
"switchOffOtherSegments": {
|
||||
"type": "boolean",
|
||||
"format": "checkbox",
|
||||
"title": "edt_dev_spec_segmentsSwitchOffOthers_title",
|
||||
"default": true,
|
||||
"required": true,
|
||||
"access": "advanced",
|
||||
"propertyOrder": 3
|
||||
}
|
||||
},
|
||||
"propertyOrder": 4,
|
||||
"additionalProperties": false
|
||||
},
|
||||
"restoreOriginalState": {
|
||||
"type": "boolean",
|
||||
"format": "checkbox",
|
||||
@@ -44,7 +84,18 @@
|
||||
"options": {
|
||||
"infoText": "edt_dev_spec_restoreOriginalState_title_info"
|
||||
},
|
||||
"propertyOrder": 4
|
||||
"propertyOrder": 7
|
||||
},
|
||||
"stayOnAfterStreaming": {
|
||||
"type": "boolean",
|
||||
"format": "checkbox",
|
||||
"title": "edt_dev_spec_stayOnAfterStreaming_title",
|
||||
"default": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"infoText": "edt_dev_spec_stayOnAfterStreaming_title_info"
|
||||
},
|
||||
"propertyOrder": 8
|
||||
},
|
||||
"overwriteSync": {
|
||||
"type": "boolean",
|
||||
@@ -53,7 +104,7 @@
|
||||
"default": true,
|
||||
"required": true,
|
||||
"access": "advanced",
|
||||
"propertyOrder": 5
|
||||
"propertyOrder": 9
|
||||
},
|
||||
"overwriteBrightness": {
|
||||
"type": "boolean",
|
||||
@@ -62,7 +113,7 @@
|
||||
"default": true,
|
||||
"required": true,
|
||||
"access": "advanced",
|
||||
"propertyOrder": 6
|
||||
"propertyOrder": 10
|
||||
},
|
||||
"brightness": {
|
||||
"type": "integer",
|
||||
@@ -76,7 +127,7 @@
|
||||
}
|
||||
},
|
||||
"access": "advanced",
|
||||
"propertyOrder": 7
|
||||
"propertyOrder": 11
|
||||
},
|
||||
"latchTime": {
|
||||
"type": "integer",
|
||||
@@ -89,8 +140,8 @@
|
||||
"options": {
|
||||
"infoText": "edt_dev_spec_latchtime_title_info"
|
||||
},
|
||||
"propertyOrder": 8
|
||||
"propertyOrder": 12
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
"additionalProperties": true
|
||||
}
|
||||
|
||||
@@ -429,7 +429,7 @@ QJsonArray MdnsBrowser::getServicesDiscoveredJson(const QByteArray& serviceType,
|
||||
|
||||
void MdnsBrowser::printCache(const QByteArray& name, quint16 type) const
|
||||
{
|
||||
DebugIf(verboseBrowser,_log, "for type: ", QSTRING_CSTR(QMdnsEngine::typeName(type)));
|
||||
DebugIf(verboseBrowser,_log, "for type: %s", QSTRING_CSTR(QMdnsEngine::typeName(type)));
|
||||
QList<QMdnsEngine::Record> records;
|
||||
if (_cache.lookupRecords(name, type, records))
|
||||
{
|
||||
@@ -466,6 +466,6 @@ void MdnsBrowser::printCache(const QByteArray& name, quint16 type) const
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugIf(verboseBrowser,_log, "Cash is empty for type: ", QSTRING_CSTR(QMdnsEngine::typeName(type)));
|
||||
DebugIf(verboseBrowser,_log, "Cash is empty for type: %s", QSTRING_CSTR(QMdnsEngine::typeName(type)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,6 @@ QString SSDPDiscover::getFirstService(const searchType& type, const QString& st,
|
||||
|
||||
QString data(datagram);
|
||||
|
||||
//Debug(_log, "_data: [%s]", QSTRING_CSTR(data));
|
||||
|
||||
QMap<QString,QString> headers;
|
||||
QString address;
|
||||
// parse request
|
||||
@@ -107,7 +105,7 @@ QString SSDPDiscover::getFirstService(const searchType& type, const QString& st,
|
||||
{
|
||||
_usnList << headers.value("usn");
|
||||
QUrl url(headers.value("location"));
|
||||
//Debug(_log, "Received msearch response from '%s:%d'. Search target: %s",QSTRING_CSTR(sender.toString()), senderPort, QSTRING_CSTR(headers.value("st")));
|
||||
|
||||
if(type == searchType::STY_WEBSERVER)
|
||||
{
|
||||
Debug(_log, "Found service [%s] at: %s:%d", QSTRING_CSTR(st), QSTRING_CSTR(url.host()), url.port());
|
||||
@@ -191,7 +189,6 @@ void SSDPDiscover::readPendingDatagrams()
|
||||
if (headers.value("st") == _searchTarget)
|
||||
{
|
||||
_usnList << headers.value("usn");
|
||||
//Debug(_log, "Received msearch response from '%s:%d'. Search target: %s",QSTRING_CSTR(sender.toString()), senderPort, QSTRING_CSTR(headers.value("st")));
|
||||
QUrl url(headers.value("location"));
|
||||
emit newService(url.host() + ":" + QString::number(url.port()));
|
||||
}
|
||||
@@ -226,8 +223,6 @@ int SSDPDiscover::discoverServices(const QString& searchTarget, const QString& k
|
||||
|
||||
QString data(datagram);
|
||||
|
||||
//Debug(_log, "_data: [%s]", QSTRING_CSTR(data));
|
||||
|
||||
QMap<QString,QString> headers;
|
||||
// parse request
|
||||
QStringList entries = QStringUtils::split(data,"\n", QStringUtils::SplitBehavior::SkipEmptyParts);
|
||||
@@ -250,7 +245,6 @@ int SSDPDiscover::discoverServices(const QString& searchTarget, const QString& k
|
||||
if ( match.hasMatch() )
|
||||
{
|
||||
Debug(_log,"Found target [%s], plus record [%s] matches [%s:%s]", QSTRING_CSTR(_searchTarget), QSTRING_CSTR(headers[_filterHeader]), QSTRING_CSTR(_filterHeader), QSTRING_CSTR(_filter) );
|
||||
//Debug(_log, "_data: [%s]", QSTRING_CSTR(data));
|
||||
|
||||
QString mapKey = headers[key];
|
||||
|
||||
@@ -303,8 +297,6 @@ QJsonArray SSDPDiscover::getServicesDiscoveredJson() const
|
||||
QMultiMap<QString, SSDPService>::const_iterator i;
|
||||
for (i = _services.begin(); i != _services.end(); ++i)
|
||||
{
|
||||
//Debug(_log, "Device discovered at [%s]", QSTRING_CSTR( i.key() ));
|
||||
|
||||
QJsonObject obj;
|
||||
|
||||
obj.insert("id", i.key());
|
||||
@@ -363,8 +355,6 @@ QJsonArray SSDPDiscover::getServicesDiscoveredJson() const
|
||||
|
||||
result << obj;
|
||||
}
|
||||
|
||||
//Debug(_log, "result: [%s]", QString(QJsonDocument(result).toJson(QJsonDocument::Compact)).toUtf8().constData() );
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -372,7 +362,6 @@ void SSDPDiscover::sendSearch(const QString& st)
|
||||
{
|
||||
const QString msg = QString(UPNP_DISCOVER_MESSAGE).arg(_ssdpAddr.toString()).arg(_ssdpPort).arg(_ssdpMaxWaitResponseTime).arg(st);
|
||||
|
||||
//Debug(_log,"Search request: [%s]", QSTRING_CSTR(msg));
|
||||
_udpSocket->writeDatagram(msg.toUtf8(), _ssdpAddr, _ssdpPort);
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,6 @@ void SSDPServer::readPendingDatagrams()
|
||||
|
||||
if (headers.value("man") == "\"ssdp:discover\"")
|
||||
{
|
||||
//Debug(_log, "Received msearch from '%s:%d'. Search target: %s",QSTRING_CSTR(sender.toString()), senderPort, QSTRING_CSTR(headers.value("st")));
|
||||
emit msearchRequestReceived(headers.value("st"), headers.value("mx"), sender.toString(), senderPort);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ void print_trace()
|
||||
* handler and print_trace functions. */
|
||||
for (int i = 2; i < size; ++i)
|
||||
{
|
||||
std::string line = "\t" + decipher_trace(symbols[i]);
|
||||
Error(log, line.c_str());
|
||||
const std::string line = "\t" + decipher_trace(symbols[i]);
|
||||
Error(log, "%s", line.c_str());
|
||||
}
|
||||
|
||||
free(symbols);
|
||||
@@ -149,8 +149,6 @@ void signal_handler(int signum, siginfo_t * /*info*/, void * /*context*/)
|
||||
default:
|
||||
/* If the signal_handler is hit before the event loop is started,
|
||||
* following call will do nothing. So we queue the call. */
|
||||
|
||||
// QCoreApplication::quit();
|
||||
QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection);
|
||||
|
||||
// Reset signal handler to default (in case this handler is not capable of stopping)
|
||||
|
||||
@@ -59,8 +59,6 @@ namespace JsonUtils {
|
||||
{
|
||||
//remove Comments in data
|
||||
QString cleanData = data;
|
||||
//cleanData .remove(QRegularExpression("([^:]?\\/\\/.*)"));
|
||||
|
||||
QJsonParseError error;
|
||||
doc = QJsonDocument::fromJson(cleanData.toUtf8(), &error);
|
||||
|
||||
@@ -145,7 +143,6 @@ namespace JsonUtils {
|
||||
obj.insert(attribute, resolveRefs(attributeValue.toObject(), obj, log));
|
||||
else
|
||||
{
|
||||
//qDebug() <<"ADD ATTR:VALUE"<<attribute<<attributeValue;
|
||||
obj.insert(attribute, attributeValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,9 +125,6 @@ Logger::Logger (const QString & name, const QString & subName, LogLevel minLevel
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
//Debug(this, "logger '%s' destroyed", QSTRING_CSTR(_name) );
|
||||
|
||||
|
||||
if (LoggerCount.fetchAndSubOrdered(1) == 0)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
|
||||
@@ -17,7 +17,6 @@ RgbChannelAdjustment::RgbChannelAdjustment(uint8_t adjustR, uint8_t adjustG, uin
|
||||
|
||||
void RgbChannelAdjustment::resetInitialized()
|
||||
{
|
||||
//Debug(_log, "initialize mapping with %d,%d,%d", _adjust[RED], _adjust[GREEN], _adjust[BLUE]);
|
||||
memset(_initialized, false, sizeof(_initialized));
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ int RgbTransform::getBacklightThreshold() const
|
||||
return _backlightThreshold;
|
||||
}
|
||||
|
||||
void RgbTransform::setBacklightThreshold(int backlightThreshold)
|
||||
void RgbTransform::setBacklightThreshold(double backlightThreshold)
|
||||
{
|
||||
_backlightThreshold = backlightThreshold;
|
||||
_sumBrightnessLow = 765.0 * ((qPow(2.0,(_backlightThreshold/100)*2)-1) / 3.0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// stdlib includes
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
|
||||
// Utils-Jsonschema includes
|
||||
#include <utils/jsonschema/QJsonSchemaChecker.h>
|
||||
@@ -64,7 +64,7 @@ QJsonObject QJsonSchemaChecker::getAutoCorrectedConfig(const QJsonObject& value,
|
||||
_messages.clear();
|
||||
_autoCorrected = value;
|
||||
|
||||
for (const QString& correct : sequence)
|
||||
for (const QString& correct : qAsConst(sequence))
|
||||
{
|
||||
_correct = correct;
|
||||
_currentPath.clear();
|
||||
@@ -162,7 +162,8 @@ void QJsonSchemaChecker::validate(const QJsonValue& value, const QJsonObject& sc
|
||||
; // references have already been collected
|
||||
else if (attribute == "title" || attribute == "description" || attribute == "default" || attribute == "format"
|
||||
|| attribute == "defaultProperties" || attribute == "propertyOrder" || attribute == "append" || attribute == "step"
|
||||
|| attribute == "access" || attribute == "options" || attribute == "script" || attribute == "allowEmptyArray" || attribute == "comment")
|
||||
|| attribute == "access" || attribute == "options" || attribute == "script" || attribute == "allowEmptyArray" || attribute == "comment"
|
||||
|| attribute == "watch" || attribute == "template")
|
||||
; // nothing to do.
|
||||
else
|
||||
{
|
||||
@@ -186,7 +187,14 @@ void QJsonSchemaChecker::checkType(const QJsonValue& value, const QJsonValue& sc
|
||||
else if (type == "integer")
|
||||
{
|
||||
if (value.isDouble()) //check if value type not boolean (true = 1 && false = 0)
|
||||
wrongType = (rint(value.toDouble()) != value.toDouble());
|
||||
{
|
||||
double valueIntegratlPart;
|
||||
double valueFractionalPart = std::modf(value.toDouble(), &valueIntegratlPart);
|
||||
if (valueFractionalPart > std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
wrongType = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
wrongType = true;
|
||||
}
|
||||
@@ -227,7 +235,6 @@ void QJsonSchemaChecker::checkProperties(const QJsonObject& value, const QJsonOb
|
||||
const QJsonValue& propertyValue = *i;
|
||||
|
||||
_currentPath.append("." + property);
|
||||
QJsonObject::const_iterator required = propertyValue.toObject().find("required");
|
||||
|
||||
if (value.contains(property))
|
||||
{
|
||||
@@ -235,7 +242,8 @@ void QJsonSchemaChecker::checkProperties(const QJsonObject& value, const QJsonOb
|
||||
}
|
||||
else if (!verifyDeps(property, value, schema))
|
||||
{
|
||||
if (required != propertyValue.toObject().end() && propertyValue.toObject().find("required").value().toBool() && !_ignoreRequired)
|
||||
bool isRequired = propertyValue.toObject().value("required").toBool(false);
|
||||
if (isRequired && !_ignoreRequired)
|
||||
{
|
||||
_error = true;
|
||||
|
||||
@@ -266,9 +274,10 @@ bool QJsonSchemaChecker::verifyDeps(const QString& property, const QJsonObject&
|
||||
{
|
||||
const QJsonObject& depends = ((schema[property].toObject())["options"].toObject())["dependencies"].toObject();
|
||||
|
||||
if (depends.keys().size() > 0)
|
||||
const QStringList dependsKeys = depends.keys();
|
||||
if (!dependsKeys.isEmpty())
|
||||
{
|
||||
QString firstName = depends.keys().first();
|
||||
const QString firstName = dependsKeys.constFirst();
|
||||
if (value.contains(firstName))
|
||||
{
|
||||
if (value[firstName] != depends[firstName])
|
||||
|
||||
@@ -77,13 +77,13 @@ void QtHttpClientWrapper::onClientDataReceived (void)
|
||||
else
|
||||
{
|
||||
m_parsingStatus = ParsingError;
|
||||
//qWarning () << "Error : unhandled HTTP version :" << version;
|
||||
// Error : unhandled HTTP version
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_parsingStatus = ParsingError;
|
||||
//qWarning () << "Error : incorrect HTTP command line :" << line;
|
||||
// Error : incorrect HTTP command line
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
#include "StaticFileServing.h"
|
||||
|
||||
#include "QtHttpHeader.h"
|
||||
#include <utils/QStringUtils.h>
|
||||
|
||||
#include <QStringBuilder>
|
||||
@@ -9,6 +11,7 @@
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QResource>
|
||||
|
||||
#include <exception>
|
||||
|
||||
StaticFileServing::StaticFileServing (QObject * parent)
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
#include <QMimeDatabase>
|
||||
|
||||
//#include "QtHttpServer.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpHeader.h"
|
||||
#include "CgiHandler.h"
|
||||
|
||||
#include <utils/Logger.h>
|
||||
|
||||
@@ -58,14 +58,12 @@ void WebSocketClient::handleWebSocketFrame()
|
||||
|
||||
if(_socket->bytesAvailable() < (qint64)_wsh.payloadLength)
|
||||
{
|
||||
//printf("not enough data %llu %llu\n", _socket->bytesAvailable(), _wsh.payloadLength);
|
||||
_notEnoughData=true;
|
||||
return;
|
||||
}
|
||||
_notEnoughData = false;
|
||||
|
||||
QByteArray buf = _socket->read(_wsh.payloadLength);
|
||||
//printf("opcode %x payload bytes %llu avail: %llu\n", _wsh.opCode, _wsh.payloadLength, _socket->bytesAvailable());
|
||||
|
||||
if (OPCODE::invalid((OPCODE::value)_wsh.opCode))
|
||||
{
|
||||
@@ -210,10 +208,10 @@ void WebSocketClient::getWsFrameHeader(WebSocketHeader* header)
|
||||
}
|
||||
|
||||
/// See http://tools.ietf.org/html/rfc6455#section-5.2 for more information
|
||||
void WebSocketClient::sendClose(int status, QString reason)
|
||||
void WebSocketClient::sendClose(int status, const QString& reason)
|
||||
{
|
||||
Debug(_log, "Send close to %s: %d %s", QSTRING_CSTR(_socket->peerAddress().toString()), status, QSTRING_CSTR(reason));
|
||||
ErrorIf(!reason.isEmpty(), _log, QSTRING_CSTR(reason));
|
||||
ErrorIf(!reason.isEmpty(), _log, "%s", QSTRING_CSTR(reason));
|
||||
_receiveBuffer.clear();
|
||||
QByteArray sendBuffer;
|
||||
|
||||
@@ -244,8 +242,6 @@ void WebSocketClient::sendClose(int status, QString reason)
|
||||
|
||||
void WebSocketClient::handleBinaryMessage(QByteArray &data)
|
||||
{
|
||||
//uint8_t priority = data.at(0);
|
||||
//unsigned duration_s = data.at(1);
|
||||
unsigned imgSize = data.size() - 4;
|
||||
unsigned width = ((data.at(2) << 8) & 0xFF00) | (data.at(3) & 0xFF);
|
||||
unsigned height = imgSize / width;
|
||||
@@ -260,8 +256,6 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data)
|
||||
image.resize(width, height);
|
||||
|
||||
memcpy(image.memptr(), data.data()+4, imgSize);
|
||||
//_hyperion->registerInput();
|
||||
//_hyperion->setInputImage(priority, image, duration_s*1000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ private:
|
||||
JsonAPI* _jsonAPI;
|
||||
|
||||
void getWsFrameHeader(WebSocketHeader* header);
|
||||
void sendClose(int status, QString reason = "");
|
||||
void sendClose(int status, const QString& reason = "");
|
||||
void handleBinaryMessage(QByteArray &data);
|
||||
qint64 sendMessage_Raw(const char* data, quint64 size);
|
||||
qint64 sendMessage_Raw(QByteArray &data);
|
||||
|
||||
Reference in New Issue
Block a user