BoblightClientConnection.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. // system includes
  2. #include <stdexcept>
  3. #include <cassert>
  4. #include <iomanip>
  5. #include <cstdio>
  6. #include <cmath>
  7. // stl includes
  8. #include <iostream>
  9. #include <sstream>
  10. #include <iterator>
  11. #include <locale>
  12. // Qt includes
  13. #include <QResource>
  14. #include <QDateTime>
  15. #include <QHostInfo>
  16. // hyperion util includes
  17. #include <hyperion/ImageProcessor.h>
  18. #include "HyperionConfig.h"
  19. #include <hyperion/Hyperion.h>
  20. #include <utils/QStringUtils.h>
  21. #include <hyperion/PriorityMuxer.h>
  22. // project includes
  23. #include "BoblightClientConnection.h"
  24. // Constants
  25. namespace {
  26. const int BOBLIGHT_DEFAULT_PRIORITY = 128;
  27. const int BOBLIGHT_MIN_PRIORITY = PriorityMuxer::FG_PRIORITY+1;
  28. const int BOBLIGHT_MAX_PRIORITY = PriorityMuxer::BG_PRIORITY-1;
  29. } //End of constants
  30. BoblightClientConnection::BoblightClientConnection(Hyperion* hyperion, QTcpSocket* socket, int priority)
  31. : QObject()
  32. , _locale(QLocale::C)
  33. , _socket(socket)
  34. , _imageProcessor(hyperion->getImageProcessor())
  35. , _hyperion(hyperion)
  36. , _receiveBuffer()
  37. , _priority(priority)
  38. , _ledColors(hyperion->getLedCount(), ColorRgb::BLACK)
  39. , _log(Logger::getInstance("BOBLIGHT"))
  40. , _clientAddress(QHostInfo::fromName(socket->peerAddress().toString()).hostName())
  41. {
  42. // initalize the locale. Start with the default C-locale
  43. _locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
  44. // connect internal signals and slots
  45. connect(_socket, &QTcpSocket::disconnected, this, &BoblightClientConnection::socketClosed);
  46. connect(_socket, &QTcpSocket::readyRead, this, &BoblightClientConnection::readData);
  47. }
  48. BoblightClientConnection::~BoblightClientConnection()
  49. {
  50. _socket->deleteLater();
  51. }
  52. void BoblightClientConnection::readData()
  53. {
  54. _receiveBuffer.append(_socket->readAll());
  55. int bytes = _receiveBuffer.indexOf('\n') + 1;
  56. while (bytes > 0)
  57. {
  58. // create message string (strip the newline)
  59. const QString message = readMessage(_receiveBuffer.data(), bytes);
  60. // handle trimmed message
  61. handleMessage(message);
  62. // remove message data from buffer
  63. _receiveBuffer.remove(0, bytes);
  64. // drop messages if the buffer is too full
  65. if (_receiveBuffer.size() > 100 * 1024)
  66. {
  67. Debug(_log, "server drops messages (buffer full)");
  68. _receiveBuffer.clear();
  69. }
  70. // try too look up '\n' again
  71. bytes = _receiveBuffer.indexOf('\n') + 1;
  72. }
  73. }
  74. QString BoblightClientConnection::readMessage(const char* data, const size_t size) const
  75. {
  76. char* end = (char*)data + size - 1;
  77. // Trim left
  78. while (data < end && std::isspace(*data))
  79. {
  80. ++data;
  81. }
  82. // Trim right
  83. while (end > data && std::isspace(*end))
  84. {
  85. --end;
  86. }
  87. // create message string (strip the newline)
  88. const int len = end - data + 1;
  89. const QString message = QString::fromLatin1(data, len);
  90. return message;
  91. }
  92. void BoblightClientConnection::socketClosed()
  93. {
  94. if (_priority >= BOBLIGHT_MIN_PRIORITY && _priority <= BOBLIGHT_MAX_PRIORITY)
  95. {
  96. _hyperion->clear(_priority);
  97. }
  98. emit connectionClosed(this);
  99. }
  100. void BoblightClientConnection::handleMessage(const QString& message)
  101. {
  102. QStringList messageParts = QStringUtils::split(message, ' ', QStringUtils::SplitBehavior::SkipEmptyParts);
  103. if (!messageParts.isEmpty())
  104. {
  105. if (messageParts[0] == "hello")
  106. {
  107. sendMessage("hello\n");
  108. return;
  109. }
  110. else if (messageParts[0] == "ping")
  111. {
  112. sendMessage("ping 1\n");
  113. return;
  114. }
  115. else if (messageParts[0] == "get" && messageParts.size() > 1)
  116. {
  117. if (messageParts[1] == "version")
  118. {
  119. sendMessage("version 5\n");
  120. return;
  121. }
  122. else if (messageParts[1] == "lights")
  123. {
  124. sendLightMessage();
  125. return;
  126. }
  127. }
  128. else if (messageParts[0] == "set" && messageParts.size() > 2)
  129. {
  130. if (messageParts.size() > 3 && messageParts[1] == "light")
  131. {
  132. bool rc;
  133. const unsigned ledIndex = parseUInt(messageParts[2], &rc);
  134. if (rc && ledIndex < _ledColors.size())
  135. {
  136. if (messageParts[3] == "rgb" && messageParts.size() == 7)
  137. {
  138. // custom parseByte accepts both ',' and '.' as decimal separator
  139. // no need to replace decimal comma with decimal point
  140. bool rc1, rc2, rc3;
  141. const uint8_t red = parseByte(messageParts[4], &rc1);
  142. const uint8_t green = parseByte(messageParts[5], &rc2);
  143. const uint8_t blue = parseByte(messageParts[6], &rc3);
  144. if (rc1 && rc2 && rc3)
  145. {
  146. ColorRgb& rgb = _ledColors[ledIndex];
  147. rgb.red = red;
  148. rgb.green = green;
  149. rgb.blue = blue;
  150. if (_priority == 0 || _priority < BOBLIGHT_MIN_PRIORITY || _priority > BOBLIGHT_MAX_PRIORITY)
  151. return;
  152. // send current color values to hyperion if this is the last led assuming leds values are send in order of id
  153. if (ledIndex == _ledColors.size() - 1)
  154. {
  155. _hyperion->setInput(_priority, _ledColors);
  156. }
  157. return;
  158. }
  159. }
  160. else if (messageParts[3] == "speed" ||
  161. messageParts[3] == "interpolation" ||
  162. messageParts[3] == "use" ||
  163. messageParts[3] == "singlechange")
  164. {
  165. // these message are ignored by Hyperion
  166. return;
  167. }
  168. }
  169. }
  170. else if (messageParts.size() == 3 && messageParts[1] == "priority")
  171. {
  172. bool rc;
  173. const int prio = static_cast<int>(parseUInt(messageParts[2], &rc));
  174. if (rc)
  175. {
  176. int currentPriority = _hyperion->getCurrentPriority();
  177. if (prio == currentPriority)
  178. {
  179. Error(_log, "The priority %i is already in use onther component of type [%s]", prio, componentToString(_hyperion->getPriorityInfo(currentPriority).componentId));
  180. _socket->close();
  181. }
  182. else
  183. {
  184. if (prio < BOBLIGHT_MIN_PRIORITY || prio > BOBLIGHT_MAX_PRIORITY)
  185. {
  186. _priority = BOBLIGHT_DEFAULT_PRIORITY;
  187. while (_hyperion->getActivePriorities().contains(_priority))
  188. {
  189. _priority += 1;
  190. }
  191. // warn against invalid priority
  192. Warning(_log, "The priority %i is not in the priority range of [%d-%d]. Priority %i is used instead.",
  193. prio, BOBLIGHT_MIN_PRIORITY, BOBLIGHT_MAX_PRIORITY, _priority);
  194. // register new priority (previously modified)
  195. _hyperion->registerInput(_priority, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_clientAddress));
  196. }
  197. else
  198. {
  199. // register new priority
  200. _hyperion->registerInput(prio, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_clientAddress));
  201. _priority = prio;
  202. }
  203. }
  204. }
  205. return;
  206. }
  207. }
  208. else if (messageParts[0] == "sync")
  209. {
  210. if (_priority >= BOBLIGHT_MIN_PRIORITY && _priority <= BOBLIGHT_MAX_PRIORITY)
  211. {
  212. int currentPriority = _hyperion->getCurrentPriority();
  213. if ( _priority != currentPriority)
  214. {
  215. // register this connection's priority
  216. _hyperion->registerInput(_priority, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_clientAddress));
  217. }
  218. if (_priority >= BOBLIGHT_MIN_PRIORITY && _priority <= BOBLIGHT_MAX_PRIORITY)
  219. {
  220. _hyperion->setInput(_priority, _ledColors); // send current color values to hyperion
  221. }
  222. }
  223. return;
  224. }
  225. }
  226. Debug(_log, "unknown boblight message: %s", QSTRING_CSTR(message));
  227. }
  228. /// Float values 10 to the power of -p for p in 0 .. 8.
  229. const float ipows[] = {
  230. 1,
  231. 1.0f / 10.0f,
  232. 1.0f / 100.0f,
  233. 1.0f / 1000.0f,
  234. 1.0f / 10000.0f,
  235. 1.0f / 100000.0f,
  236. 1.0f / 1000000.0f,
  237. 1.0f / 10000000.0f,
  238. 1.0f / 100000000.0f };
  239. float BoblightClientConnection::parseFloat(const QString& s, bool *ok) const
  240. {
  241. // We parse radix 10
  242. const char MIN_DIGIT = '0';
  243. const char MAX_DIGIT = '9';
  244. const char SEP_POINT = '.';
  245. const char SEP_COMMA = ',';
  246. const int NUM_POWS = 9;
  247. /// The maximum number of characters we want to process
  248. const int MAX_LEN = 18; // Chosen randomly
  249. /// The index of the current character
  250. int q = 0;
  251. /// The integer part of the number
  252. int64_t n = 0;
  253. auto it = s.begin();
  254. #define STEP ((it != s.end()) && (q++ < MAX_LEN))
  255. // parse the integer-part
  256. while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && STEP)
  257. {
  258. n = (n * 10) + (it->unicode() - MIN_DIGIT);
  259. ++it;
  260. }
  261. /// The resulting float value
  262. float f = static_cast<float>(n);
  263. // parse decimal part
  264. if ((it->unicode() == SEP_POINT || it->unicode() == SEP_COMMA) && STEP)
  265. {
  266. /// The decimal part of the number
  267. int64_t d = 0;
  268. /// The exponent for the scale-factor 10 to the power -e
  269. int e = 0;
  270. ++it;
  271. while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && STEP)
  272. {
  273. d = (d * 10) + (it->unicode() - MIN_DIGIT);
  274. ++e;
  275. ++it;
  276. }
  277. const float h = static_cast<float>(d);
  278. // We want to use pre-calculated power whenever possible
  279. if (e < NUM_POWS)
  280. {
  281. f += h * ipows[e];
  282. }
  283. else
  284. {
  285. f += h / std::pow(10.0f, e);
  286. }
  287. }
  288. if (q >= MAX_LEN || q < s.length())
  289. {
  290. if (ok)
  291. {
  292. *ok = false;
  293. }
  294. return 0;
  295. }
  296. if (ok)
  297. {
  298. *ok = true;
  299. }
  300. return f;
  301. }
  302. unsigned BoblightClientConnection::parseUInt(const QString& s, bool *ok) const
  303. {
  304. // We parse radix 10
  305. const char MIN_DIGIT = '0';
  306. const char MAX_DIGIT = '9';
  307. /// The maximum number of characters we want to process
  308. const int MAX_LEN = 10;
  309. /// The index of the current character
  310. int q = 0;
  311. /// The integer part of the number
  312. int n = 0;
  313. auto it = s.begin();
  314. // parse the integer-part
  315. while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && ((it != s.end()) && (q++ < MAX_LEN)))
  316. {
  317. n = (n * 10) + (it->unicode() - MIN_DIGIT);
  318. ++it;
  319. }
  320. if (ok)
  321. {
  322. *ok = !(q >= MAX_LEN || q < s.length());
  323. }
  324. return n;
  325. }
  326. uint8_t BoblightClientConnection::parseByte(const QString& s, bool *ok) const
  327. {
  328. const int LO = 0;
  329. const int HI = 255;
  330. #if defined(FAST_FLOAT_PARSE)
  331. const float d = parseFloat(s, ok);
  332. #else
  333. const float d = s.toFloat(ok);
  334. #endif
  335. // Clamp to byte range 0 to 255
  336. 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
  337. }
  338. void BoblightClientConnection::sendMessage(const QByteArray &message)
  339. {
  340. if (_socket->isOpen())
  341. {
  342. _socket->write(message);
  343. }
  344. }
  345. void BoblightClientConnection::sendLightMessage()
  346. {
  347. char buffer[256];
  348. int n = snprintf(buffer, sizeof(buffer), "lights %d\n", _hyperion->getLedCount());
  349. sendMessage(QByteArray(buffer, n));
  350. double h0, h1, v0, v1;
  351. for (int i = 0; i < _hyperion->getLedCount(); ++i)
  352. {
  353. _imageProcessor->getScanParameters(i, h0, h1, v0, v1);
  354. n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100 * v0, 100 * v1, 100 * h0, 100 * h1);
  355. sendMessage(QByteArray(buffer, n));
  356. }
  357. }