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>
2021-09-15 10:32:19 +02:00
# include <hyperion/PriorityMuxer.h>
2013-11-08 22:18:10 +01:00
// project includes
# include "BoblightClientConnection.h"
2021-09-15 10:32:19 +02:00
// Constants
namespace {
const int BOBLIGHT_DEFAULT_PRIORITY = 128 ;
const int BOBLIGHT_MIN_PRIORITY = PriorityMuxer : : FG_PRIORITY + 1 ;
const int BOBLIGHT_MAX_PRIORITY = PriorityMuxer : : BG_PRIORITY - 1 ;
} //End of constants
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 ( )
{
2022-02-22 20:58:59 +01:00
_socket - > deleteLater ( ) ;
2013-11-08 22:18:10 +01:00
}
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 ;
2021-09-15 10:32:19 +02:00
while ( bytes > 0 )
2013-11-08 22:18:10 +01:00
{
// 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
2021-09-15 10:32:19 +02:00
if ( _receiveBuffer . size ( ) > 100 * 1024 )
2013-11-09 10:33:16 +01:00
{
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 ;
}
}
2021-09-15 10:32:19 +02:00
QString BoblightClientConnection : : readMessage ( const char * data , const size_t size ) const
2020-10-18 17:16:27 +02:00
{
2021-09-15 10:32:19 +02:00
char * end = ( char * ) data + size - 1 ;
2020-10-18 17:16:27 +02:00
// 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 ;
2021-09-15 10:32:19 +02:00
const QString message = QString : : fromLatin1 ( data , len ) ;
2020-10-18 17:16:27 +02:00
return message ;
}
2013-11-08 22:18:10 +01:00
void BoblightClientConnection : : socketClosed ( )
{
2021-09-15 10:32:19 +02:00
if ( _priority > = BOBLIGHT_MIN_PRIORITY & & _priority < = BOBLIGHT_MAX_PRIORITY )
2022-02-22 20:58:59 +01:00
{
2019-08-02 21:12:13 +02:00
_hyperion - > clear ( _priority ) ;
2022-02-22 20:58:59 +01:00
}
2013-11-08 22:18:10 +01:00
emit connectionClosed ( this ) ;
}
2020-10-18 17:16:27 +02:00
2021-09-15 10:32:19 +02:00
void BoblightClientConnection : : handleMessage ( const QString & message )
2013-11-08 22:18:10 +01:00
{
2021-11-16 18:12:56 +01:00
QStringList messageParts = QStringUtils : : split ( message , ' ' , QStringUtils : : SplitBehavior : : SkipEmptyParts ) ;
if ( ! messageParts . isEmpty ( ) )
2013-11-08 22:18:10 +01:00
{
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 )
{
2021-09-15 10:32:19 +02: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
2021-09-15 10:32:19 +02:00
if ( _priority = = 0 | | _priority < BOBLIGHT_MIN_PRIORITY | | _priority > BOBLIGHT_MAX_PRIORITY )
2019-08-02 21:12:13 +02:00
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
2021-09-15 10:32:19 +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 ;
}
}
2021-09-15 10:32:19 +02:00
else if ( messageParts [ 3 ] = = " speed " | |
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 ) ) ;
2022-02-22 20:58:59 +01:00
if ( rc )
2013-11-08 22:18:10 +01:00
{
2022-02-22 20:58:59 +01:00
int currentPriority = _hyperion - > getCurrentPriority ( ) ;
2019-05-26 14:25:37 +02:00
2022-02-22 20:58:59 +01:00
if ( prio = = currentPriority )
2019-08-02 21:12:13 +02:00
{
2022-02-22 20:58:59 +01:00
Error ( _log , " The priority %i is already in use onther component of type [%s] " , prio , componentToString ( _hyperion - > getPriorityInfo ( currentPriority ) . componentId ) ) ;
_socket - > close ( ) ;
2019-08-02 21:12:13 +02:00
}
else
{
2022-02-22 20:58:59 +01:00
if ( prio < BOBLIGHT_MIN_PRIORITY | | prio > BOBLIGHT_MAX_PRIORITY )
{
_priority = BOBLIGHT_DEFAULT_PRIORITY ;
while ( _hyperion - > getActivePriorities ( ) . contains ( _priority ) )
{
_priority + = 1 ;
}
2013-11-08 22:18:10 +01:00
2022-02-22 20:58:59 +01:00
// warn against invalid priority
Warning ( _log , " The priority %i is not in the priority range of [%d-%d]. Priority %i is used instead. " ,
prio , BOBLIGHT_MIN_PRIORITY , BOBLIGHT_MAX_PRIORITY , _priority ) ;
// register new priority (previously modified)
_hyperion - > registerInput ( _priority , hyperion : : COMP_BOBLIGHTSERVER , QString ( " Boblight@%1 " ) . arg ( _clientAddress ) ) ;
}
else
{
// register new priority
_hyperion - > registerInput ( prio , hyperion : : COMP_BOBLIGHTSERVER , QString ( " Boblight@%1 " ) . arg ( _clientAddress ) ) ;
_priority = prio ;
}
}
2013-11-08 22:18:10 +01:00
}
2022-02-22 20:58:59 +01:00
return ;
2013-11-08 22:18:10 +01:00
}
}
else if ( messageParts [ 0 ] = = " sync " )
{
2021-09-15 10:32:19 +02:00
if ( _priority > = BOBLIGHT_MIN_PRIORITY & & _priority < = BOBLIGHT_MAX_PRIORITY )
2022-02-22 20:58:59 +01:00
{
int currentPriority = _hyperion - > getCurrentPriority ( ) ;
if ( _priority ! = currentPriority )
{
// register this connection's priority
_hyperion - > registerInput ( _priority , hyperion : : COMP_BOBLIGHTSERVER , QString ( " Boblight@%1 " ) . arg ( _clientAddress ) ) ;
}
if ( _priority > = BOBLIGHT_MIN_PRIORITY & & _priority < = BOBLIGHT_MAX_PRIORITY )
{
_hyperion - > setInput ( _priority , _ledColors ) ; // send current color values to hyperion
}
}
2019-08-02 21:12:13 +02:00
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 ,
2021-09-15 10:32:19 +02:00
1.0f / 100000000.0f } ;
2020-10-18 17:16:27 +02:00
2021-11-16 18:12:56 +01:00
float BoblightClientConnection : : parseFloat ( const QString & s , bool * ok ) const
2020-10-18 17:16:27 +02:00
{
// 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 )
{
* ok = false ;
}
return 0 ;
}
if ( ok )
{
* ok = true ;
}
return f ;
}
2021-11-16 18:12:56 +01:00
unsigned BoblightClientConnection : : parseUInt ( const QString & s , bool * ok ) const
2020-10-18 17:16:27 +02:00
{
// 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 ;
}
2021-11-16 18:12:56 +01:00
uint8_t BoblightClientConnection : : parseByte ( const QString & s , bool * ok ) const
2020-10-18 17:16:27 +02:00
{
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
}
2022-02-22 20:58:59 +01:00
void BoblightClientConnection : : sendMessage ( const QByteArray & message )
{
if ( _socket - > isOpen ( ) )
{
_socket - > write ( message ) ;
}
}
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 ;
2020-11-14 17:58:56 +01:00
for ( int i = 0 ; i < _hyperion - > getLedCount ( ) ; + + i )
2013-11-08 22:18:10 +01:00
{
2018-12-28 18:12:45 +01:00
_imageProcessor - > getScanParameters ( i , h0 , h1 , v0 , v1 ) ;
2021-09-15 10:32:19 +02:00
n = snprintf ( buffer , sizeof ( buffer ) , " light %03d scan %f %f %f %f \n " , i , 100 * v0 , 100 * v1 , 100 * h0 , 100 * h1 ) ;
2018-12-28 18:12:45 +01:00
sendMessage ( QByteArray ( buffer , n ) ) ;
2013-11-08 22:18:10 +01:00
}
}