2013-11-08 22:18:10 +01:00
|
|
|
// system includes
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <cassert>
|
|
|
|
#include <iomanip>
|
|
|
|
#include <cstdio>
|
2020-10-18 17:16:27 +02:00
|
|
|
#include <cmath>
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
// stl includes
|
|
|
|
#include <iostream>
|
|
|
|
#include <sstream>
|
|
|
|
#include <iterator>
|
2020-10-18 17:16:27 +02:00
|
|
|
#include <locale>
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
// Qt includes
|
|
|
|
#include <QResource>
|
|
|
|
#include <QDateTime>
|
2017-03-01 15:23:53 +01:00
|
|
|
#include <QHostInfo>
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
// hyperion util includes
|
2018-12-28 18:12:45 +01:00
|
|
|
#include <hyperion/ImageProcessor.h>
|
2016-06-12 22:27:24 +02:00
|
|
|
#include "HyperionConfig.h"
|
2018-12-28 18:12:45 +01:00
|
|
|
#include <hyperion/Hyperion.h>
|
2020-07-12 20:27:56 +02:00
|
|
|
#include <utils/QStringUtils.h>
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
// project includes
|
|
|
|
#include "BoblightClientConnection.h"
|
|
|
|
|
2020-08-08 13:09:15 +02:00
|
|
|
BoblightClientConnection::BoblightClientConnection(Hyperion* hyperion, QTcpSocket *socket, int priority)
|
2016-06-27 22:43:43 +02:00
|
|
|
: QObject()
|
|
|
|
, _locale(QLocale::C)
|
|
|
|
, _socket(socket)
|
2018-12-28 18:12:45 +01:00
|
|
|
, _imageProcessor(hyperion->getImageProcessor())
|
|
|
|
, _hyperion(hyperion)
|
2016-06-27 22:43:43 +02:00
|
|
|
, _receiveBuffer()
|
|
|
|
, _priority(priority)
|
2018-12-28 18:12:45 +01:00
|
|
|
, _ledColors(hyperion->getLedCount(), ColorRgb::BLACK)
|
2016-06-27 22:43:43 +02:00
|
|
|
, _log(Logger::getInstance("BOBLIGHT"))
|
2017-03-01 15:23:53 +01:00
|
|
|
, _clientAddress(QHostInfo::fromName(socket->peerAddress().toString()).hostName())
|
2013-11-08 22:18:10 +01:00
|
|
|
{
|
|
|
|
// initalize the locale. Start with the default C-locale
|
|
|
|
_locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
|
|
|
|
|
|
|
|
// connect internal signals and slots
|
2020-08-02 22:32:00 +02:00
|
|
|
connect(_socket, &QTcpSocket::disconnected, this, &BoblightClientConnection::socketClosed);
|
|
|
|
connect(_socket, &QTcpSocket::readyRead, this, &BoblightClientConnection::readData);
|
2013-11-08 22:18:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
BoblightClientConnection::~BoblightClientConnection()
|
|
|
|
{
|
2019-08-02 21:12:13 +02:00
|
|
|
// clear the current channel
|
|
|
|
if (_priority != 0 && _priority >= 128 && _priority < 254)
|
|
|
|
_hyperion->clear(_priority);
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
delete _socket;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BoblightClientConnection::readData()
|
|
|
|
{
|
2020-10-18 17:16:27 +02:00
|
|
|
_receiveBuffer.append(_socket->readAll());
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
|
|
|
while(bytes > 0)
|
|
|
|
{
|
|
|
|
// create message string (strip the newline)
|
2020-10-18 17:16:27 +02:00
|
|
|
const QString message = readMessage(_receiveBuffer.data(), bytes);
|
2013-11-08 22:18:10 +01:00
|
|
|
|
2015-12-14 00:15:15 +01:00
|
|
|
// handle trimmed message
|
2020-10-18 17:16:27 +02:00
|
|
|
handleMessage(message);
|
|
|
|
|
|
|
|
// remove message data from buffer
|
|
|
|
_receiveBuffer.remove(0, bytes);
|
2013-11-08 22:18:10 +01:00
|
|
|
|
2013-11-09 10:33:16 +01:00
|
|
|
// drop messages if the buffer is too full
|
|
|
|
if (_receiveBuffer.size() > 100*1024)
|
|
|
|
{
|
2016-06-27 22:43:43 +02:00
|
|
|
Debug(_log, "server drops messages (buffer full)");
|
2013-11-09 10:33:16 +01:00
|
|
|
_receiveBuffer.clear();
|
|
|
|
}
|
|
|
|
|
2013-11-08 22:18:10 +01:00
|
|
|
// try too look up '\n' again
|
|
|
|
bytes = _receiveBuffer.indexOf('\n') + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-18 17:16:27 +02:00
|
|
|
QString BoblightClientConnection::readMessage(const char *data, const size_t size) const
|
|
|
|
{
|
|
|
|
char *end = (char *)data + size - 1;
|
|
|
|
|
|
|
|
// Trim left
|
|
|
|
while (data < end && std::isspace(*data))
|
|
|
|
{
|
|
|
|
++data;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trim right
|
|
|
|
while (end > data && std::isspace(*end))
|
|
|
|
{
|
|
|
|
--end;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create message string (strip the newline)
|
|
|
|
const int len = end - data + 1;
|
|
|
|
const QString message = QString::fromLatin1(data, len);
|
|
|
|
|
|
|
|
//std::cout << bytes << ": \"" << message.toUtf8().constData() << "\"" << std::endl;
|
|
|
|
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
2013-11-08 22:18:10 +01:00
|
|
|
void BoblightClientConnection::socketClosed()
|
|
|
|
{
|
2019-08-02 21:12:13 +02:00
|
|
|
// clear the current channel
|
2020-07-12 20:27:56 +02:00
|
|
|
if (_priority >= 128 && _priority < 254)
|
2019-08-02 21:12:13 +02:00
|
|
|
_hyperion->clear(_priority);
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
emit connectionClosed(this);
|
|
|
|
}
|
|
|
|
|
2020-10-18 17:16:27 +02:00
|
|
|
|
2013-11-08 22:18:10 +01:00
|
|
|
void BoblightClientConnection::handleMessage(const QString & message)
|
|
|
|
{
|
|
|
|
//std::cout << "boblight message: " << message.toStdString() << std::endl;
|
2020-10-18 17:16:27 +02:00
|
|
|
const QVector<QStringRef> messageParts = QStringUtils::splitRef(message, ' ', QStringUtils::SplitBehavior::SkipEmptyParts);
|
2013-11-08 22:18:10 +01:00
|
|
|
if (messageParts.size() > 0)
|
|
|
|
{
|
|
|
|
if (messageParts[0] == "hello")
|
|
|
|
{
|
|
|
|
sendMessage("hello\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (messageParts[0] == "ping")
|
|
|
|
{
|
|
|
|
sendMessage("ping 1\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (messageParts[0] == "get" && messageParts.size() > 1)
|
|
|
|
{
|
|
|
|
if (messageParts[1] == "version")
|
|
|
|
{
|
|
|
|
sendMessage("version 5\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (messageParts[1] == "lights")
|
|
|
|
{
|
|
|
|
sendLightMessage();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (messageParts[0] == "set" && messageParts.size() > 2)
|
|
|
|
{
|
|
|
|
if (messageParts.size() > 3 && messageParts[1] == "light")
|
|
|
|
{
|
|
|
|
bool rc;
|
2020-10-18 17:16:27 +02:00
|
|
|
const unsigned ledIndex = parseUInt(messageParts[2], &rc);
|
2013-11-08 22:18:10 +01:00
|
|
|
if (rc && ledIndex < _ledColors.size())
|
|
|
|
{
|
|
|
|
if (messageParts[3] == "rgb" && messageParts.size() == 7)
|
|
|
|
{
|
2020-10-18 17:16:27 +02:00
|
|
|
// custom parseByte accepts both ',' and '.' as decimal separator
|
|
|
|
// no need to replace decimal comma with decimal point
|
2015-12-14 00:23:53 +01:00
|
|
|
|
2013-11-08 22:18:10 +01:00
|
|
|
bool rc1, rc2, rc3;
|
2020-10-18 17:16:27 +02:00
|
|
|
const uint8_t red = parseByte(messageParts[4], &rc1);
|
|
|
|
const uint8_t green = parseByte(messageParts[5], &rc2);
|
|
|
|
const uint8_t blue = parseByte(messageParts[6], &rc3);
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
if (rc1 && rc2 && rc3)
|
|
|
|
{
|
2013-11-11 10:00:37 +01:00
|
|
|
ColorRgb & rgb = _ledColors[ledIndex];
|
2013-11-08 22:18:10 +01:00
|
|
|
rgb.red = red;
|
|
|
|
rgb.green = green;
|
|
|
|
rgb.blue = blue;
|
2013-12-12 23:12:22 +01:00
|
|
|
|
2019-08-02 21:12:13 +02:00
|
|
|
if (_priority == 0 || _priority < 128 || _priority >= 254)
|
|
|
|
return;
|
|
|
|
|
2013-12-12 23:12:22 +01:00
|
|
|
// send current color values to hyperion if this is the last led assuming leds values are send in order of id
|
2019-08-01 19:10:15 +02:00
|
|
|
if (ledIndex == _ledColors.size() -1)
|
2013-12-12 23:12:22 +01:00
|
|
|
{
|
2018-12-27 23:11:32 +01:00
|
|
|
_hyperion->setInput(_priority, _ledColors);
|
2013-12-12 23:12:22 +01:00
|
|
|
}
|
|
|
|
|
2013-11-08 22:18:10 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(messageParts[3] == "speed" ||
|
2017-03-01 15:23:53 +01:00
|
|
|
messageParts[3] == "interpolation" ||
|
|
|
|
messageParts[3] == "use" ||
|
|
|
|
messageParts[3] == "singlechange")
|
2013-11-08 22:18:10 +01:00
|
|
|
{
|
|
|
|
// these message are ignored by Hyperion
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (messageParts.size() == 3 && messageParts[1] == "priority")
|
|
|
|
{
|
|
|
|
bool rc;
|
2020-10-18 17:16:27 +02:00
|
|
|
const int prio = static_cast<int>(parseUInt(messageParts[2], &rc));
|
2013-11-08 22:18:10 +01:00
|
|
|
if (rc && prio != _priority)
|
|
|
|
{
|
2019-08-02 21:12:13 +02:00
|
|
|
if (_priority != 0 && _hyperion->getPriorityInfo(_priority).componentId == hyperion::COMP_BOBLIGHTSERVER)
|
|
|
|
_hyperion->clear(_priority);
|
2019-05-26 14:25:37 +02:00
|
|
|
|
2019-08-02 21:12:13 +02:00
|
|
|
if (prio < 128 || prio >= 254)
|
|
|
|
{
|
|
|
|
_priority = 128;
|
|
|
|
while (_hyperion->getActivePriorities().contains(_priority))
|
|
|
|
{
|
|
|
|
_priority += 1;
|
|
|
|
}
|
|
|
|
|
2019-08-03 10:59:08 +02:00
|
|
|
// warn against invalid priority
|
2019-08-02 21:12:13 +02:00
|
|
|
Warning(_log, "The priority %i is not in the priority range between 128 and 253. Priority %i is used instead.", prio, _priority);
|
2019-08-03 10:59:08 +02:00
|
|
|
// register new priority (previously modified)
|
|
|
|
_hyperion->registerInput(_priority, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_socket->peerAddress().toString()));
|
2019-08-02 21:12:13 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// register new priority
|
|
|
|
_hyperion->registerInput(prio, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_socket->peerAddress().toString()));
|
|
|
|
_priority = prio;
|
|
|
|
}
|
2013-11-08 22:18:10 +01:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (messageParts[0] == "sync")
|
|
|
|
{
|
2020-07-12 20:27:56 +02:00
|
|
|
if ( _priority >= 128 && _priority < 254)
|
2019-08-02 21:12:13 +02:00
|
|
|
_hyperion->setInput(_priority, _ledColors); // send current color values to hyperion
|
|
|
|
|
2013-11-09 10:33:16 +01:00
|
|
|
return;
|
2013-11-08 22:18:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-01 15:23:53 +01:00
|
|
|
Debug(_log, "unknown boblight message: %s", QSTRING_CSTR(message));
|
2013-11-08 22:18:10 +01:00
|
|
|
}
|
|
|
|
|
2020-10-18 17:16:27 +02:00
|
|
|
/// Float values 10 to the power of -p for p in 0 .. 8.
|
|
|
|
const float ipows[] = {
|
|
|
|
1,
|
|
|
|
1.0f / 10.0f,
|
|
|
|
1.0f / 100.0f,
|
|
|
|
1.0f / 1000.0f,
|
|
|
|
1.0f / 10000.0f,
|
|
|
|
1.0f / 100000.0f,
|
|
|
|
1.0f / 1000000.0f,
|
|
|
|
1.0f / 10000000.0f,
|
|
|
|
1.0f / 100000000.0f};
|
|
|
|
|
|
|
|
float BoblightClientConnection::parseFloat(const QStringRef& s, bool *ok) const
|
|
|
|
{
|
|
|
|
// We parse radix 10
|
|
|
|
const char MIN_DIGIT = '0';
|
|
|
|
const char MAX_DIGIT = '9';
|
|
|
|
const char SEP_POINT = '.';
|
|
|
|
const char SEP_COMMA = ',';
|
|
|
|
const int NUM_POWS = 9;
|
|
|
|
|
|
|
|
/// The maximum number of characters we want to process
|
|
|
|
const int MAX_LEN = 18; // Chosen randomly
|
|
|
|
|
|
|
|
/// The index of the current character
|
|
|
|
int q = 0;
|
|
|
|
|
|
|
|
/// The integer part of the number
|
|
|
|
int64_t n = 0;
|
|
|
|
|
|
|
|
auto it = s.begin();
|
|
|
|
|
|
|
|
#define STEP ((it != s.end()) && (q++ < MAX_LEN))
|
|
|
|
|
|
|
|
// parse the integer-part
|
|
|
|
while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && STEP)
|
|
|
|
{
|
|
|
|
n = (n * 10) + (it->unicode() - MIN_DIGIT);
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The resulting float value
|
|
|
|
float f = static_cast<float>(n);
|
|
|
|
|
|
|
|
// parse decimal part
|
|
|
|
if ((it->unicode() == SEP_POINT || it->unicode() == SEP_COMMA) && STEP)
|
|
|
|
{
|
|
|
|
/// The decimal part of the number
|
|
|
|
int64_t d = 0;
|
|
|
|
|
|
|
|
/// The exponent for the scale-factor 10 to the power -e
|
|
|
|
int e = 0;
|
|
|
|
|
|
|
|
++it;
|
|
|
|
while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && STEP)
|
|
|
|
{
|
|
|
|
d = (d * 10) + (it->unicode() - MIN_DIGIT);
|
|
|
|
++e;
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
|
|
|
|
const float h = static_cast<float>(d);
|
|
|
|
|
|
|
|
// We want to use pre-calculated power whenever possible
|
|
|
|
if (e < NUM_POWS)
|
|
|
|
{
|
|
|
|
f += h * ipows[e];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
f += h / std::pow(10.0f, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (q >= MAX_LEN || q < s.length())
|
|
|
|
{
|
|
|
|
if (ok)
|
|
|
|
{
|
|
|
|
//std::cout << "FAIL L " << q << ": " << s.toUtf8().constData() << std::endl;
|
|
|
|
*ok = false;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
{
|
|
|
|
//std::cout << "OK " << d << ": " << s.toUtf8().constData() << std::endl;
|
|
|
|
*ok = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned BoblightClientConnection::parseUInt(const QStringRef& s, bool *ok) const
|
|
|
|
{
|
|
|
|
// We parse radix 10
|
|
|
|
const char MIN_DIGIT = '0';
|
|
|
|
const char MAX_DIGIT = '9';
|
|
|
|
|
|
|
|
/// The maximum number of characters we want to process
|
|
|
|
const int MAX_LEN = 10;
|
|
|
|
|
|
|
|
/// The index of the current character
|
|
|
|
int q = 0;
|
|
|
|
|
|
|
|
/// The integer part of the number
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
auto it = s.begin();
|
|
|
|
|
|
|
|
// parse the integer-part
|
|
|
|
while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && ((it != s.end()) && (q++ < MAX_LEN)))
|
|
|
|
{
|
|
|
|
n = (n * 10) + (it->unicode() - MIN_DIGIT);
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
{
|
|
|
|
*ok = !(q >= MAX_LEN || q < s.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t BoblightClientConnection::parseByte(const QStringRef& s, bool *ok) const
|
|
|
|
{
|
|
|
|
const int LO = 0;
|
|
|
|
const int HI = 255;
|
|
|
|
|
|
|
|
#if defined(FAST_FLOAT_PARSE)
|
|
|
|
const float d = parseFloat(s, ok);
|
|
|
|
#else
|
|
|
|
const float d = s.toFloat(ok);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Clamp to byte range 0 to 255
|
|
|
|
return static_cast<uint8_t>(qBound(LO, int(HI * d), HI)); // qBound args are in order min, value, max; see: https://doc.qt.io/qt-5/qtglobal.html#qBound
|
|
|
|
}
|
|
|
|
|
2013-11-08 22:18:10 +01:00
|
|
|
void BoblightClientConnection::sendLightMessage()
|
|
|
|
{
|
|
|
|
char buffer[256];
|
2017-03-01 15:23:53 +01:00
|
|
|
|
2013-11-08 22:18:10 +01:00
|
|
|
int n = snprintf(buffer, sizeof(buffer), "lights %d\n", _hyperion->getLedCount());
|
2017-03-01 15:23:53 +01:00
|
|
|
sendMessage(QByteArray(buffer, n));
|
2013-11-08 22:18:10 +01:00
|
|
|
|
2018-12-28 18:12:45 +01:00
|
|
|
double h0, h1, v0, v1;
|
2013-11-08 22:18:10 +01:00
|
|
|
for (unsigned i = 0; i < _hyperion->getLedCount(); ++i)
|
|
|
|
{
|
2018-12-28 18:12:45 +01:00
|
|
|
_imageProcessor->getScanParameters(i, h0, h1, v0, v1);
|
|
|
|
n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100*v0, 100*v1, 100*h0, 100*h1);
|
|
|
|
sendMessage(QByteArray(buffer, n));
|
2013-11-08 22:18:10 +01:00
|
|
|
}
|
|
|
|
}
|