Orvibo S20 socket manager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

socket.cpp 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /*************************************************************************
  2. * Copyright (C) 2015 by Andrius Štikonas <andrius@stikonas.eu> *
  3. * *
  4. * This program is free software; you can redistribute it and/or modify *
  5. * it under the terms of the GNU General Public License as published by *
  6. * the Free Software Foundation; either version 3 of the License, or *
  7. * (at your option) any later version. *
  8. * *
  9. * This program is distributed in the hope that it will be useful, *
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  12. * GNU General Public License for more details. *
  13. * *
  14. * You should have received a copy of the GNU General Public License *
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.*
  16. *************************************************************************/
  17. #include "socket.h"
  18. #include <algorithm>
  19. #include <iostream>
  20. #include <QDataStream>
  21. #include <QMutex>
  22. #include <QThread>
  23. #include <QUdpSocket>
  24. Socket::Socket(QHostAddress IPaddress, QByteArray reply)
  25. {
  26. ip = IPaddress;
  27. mac = reply.mid(7, 6);
  28. rmac = mac;
  29. std::reverse(rmac.begin(), rmac.end());
  30. powered = reply.right(1) == one;
  31. commandID[QueryAll] = QStringLiteral("qa").toLatin1();
  32. commandID[Discover] = QStringLiteral("qg").toLatin1(); // qg
  33. commandID[Subscribe] = QStringLiteral("cl").toLatin1(); // Claim
  34. commandID[PowerOn] = QStringLiteral("sf").toLatin1(); // State Flip (change of power state)
  35. commandID[PowerOff] = commandID[PowerOn];
  36. commandID[ReadTable] = QStringLiteral("rt").toLatin1();
  37. commandID[SocketData] = commandID[ReadTable];
  38. commandID[TimingData] = commandID[ReadTable];
  39. commandID[TableModify] = QStringLiteral("tm").toLatin1();
  40. QByteArray commandIDPower = QStringLiteral("dc").toLatin1(); // Socket change responce
  41. // 2 hex bytes are the total length of the message
  42. datagram[Discover] = commandID[Discover] + mac + twenties;
  43. datagram[Subscribe] = commandID[Subscribe] + mac + twenties + rmac + twenties;
  44. datagram[PowerOn] = commandIDPower + mac + twenties + zeros + one;
  45. datagram[PowerOff] = commandIDPower + mac + twenties + zeros + zero;
  46. datagram[ReadTable] = commandID[ReadTable] + mac + twenties + /*zeros*/QByteArray::fromHex("00 00 00 00") /*cookie*/ + QByteArray::fromHex("01 00") + zero + zeros;
  47. udpSocket = new QUdpSocket(this);
  48. udpSocket->connectToHost(ip, 10000);
  49. connect(this, &Socket::datagramQueued, this, &Socket::processQueue);
  50. subscribeTimer = new QTimer(this);
  51. subscribeTimer->setInterval(2 * 60 * 1000); // 2 min
  52. subscribeTimer->setSingleShot(false);
  53. connect(subscribeTimer, &QTimer::timeout, this, &Socket::subscribe);
  54. subscribeTimer->start();
  55. subscribe();
  56. tableData();
  57. }
  58. void Socket::sendDatagram(Datagram d)
  59. {
  60. commands.enqueue(d);
  61. Q_EMIT datagramQueued();
  62. }
  63. void Socket::run()
  64. {
  65. QMutex mutex;
  66. if (!mutex.tryLock()) {
  67. return;
  68. }
  69. unsigned short retryCount = 0;
  70. QByteArray currentDatagram, previousDatagram = 0, recordLength;
  71. while (commands.size() > 0) {
  72. currentDatagram = datagram[commands.head()];
  73. if (previousDatagram == currentDatagram) {
  74. ++retryCount;
  75. }
  76. if (retryCount == 5) {
  77. std::cout << "Stop retrying: " << currentDatagram.toHex().toStdString() << std::endl;
  78. commands.dequeue();
  79. retryCount = 0;
  80. }
  81. uint16_t length = currentDatagram.length() + 4; // +4 for magicKey and total message length
  82. recordLength = intToHex(length, 2, false);
  83. udpSocket->write(magicKey + recordLength + currentDatagram);
  84. previousDatagram = currentDatagram;
  85. QThread::msleep(100);
  86. }
  87. mutex.unlock();
  88. }
  89. void Socket::subscribe()
  90. {
  91. sendDatagram(Subscribe);
  92. }
  93. void Socket::discover()
  94. {
  95. sendDatagram(Discover);
  96. }
  97. void Socket::toggle()
  98. {
  99. sendDatagram(powered ? PowerOff : PowerOn);
  100. }
  101. void Socket::powerOff()
  102. {
  103. if (powered)
  104. sendDatagram(PowerOff);
  105. Q_EMIT stateChanged();
  106. }
  107. void Socket::powerOn()
  108. {
  109. if (!powered)
  110. sendDatagram(PowerOn);
  111. Q_EMIT stateChanged();
  112. }
  113. void Socket::changeSocketName(QString newName)
  114. {
  115. QByteArray name = newName.toLatin1().leftJustified(16, ' ', true);
  116. writeSocketData(name, remotePassword, timezone, offTime);
  117. }
  118. void Socket::changeSocketPassword(QString newPassword)
  119. {
  120. QByteArray password = newPassword.toLatin1().leftJustified(12, ' ', true);
  121. writeSocketData(socketName, password, timezone, offTime);
  122. }
  123. void Socket::changeTimezone(int8_t newTimezone)
  124. {
  125. writeSocketData(socketName, remotePassword, newTimezone, offTime);
  126. }
  127. void Socket::setOffTimer(uint16_t offTime)
  128. {
  129. writeSocketData(socketName, remotePassword, timezone, offTime);
  130. }
  131. void Socket::toggleOffTimer()
  132. {
  133. offTimerEnabled=!offTimerEnabled;
  134. writeSocketData(socketName, remotePassword, timezone, offTime);
  135. }
  136. void Socket::writeSocketData(QByteArray socketName, QByteArray remotePassword, int8_t timezone, uint16_t offTime)
  137. {
  138. QByteArray record = QByteArray::fromHex("01:00") /* record number = 1*/ + versionID + mac + twenties + rmac + twenties + remotePassword + socketName + icon + hardwareVersion + firmwareVersion + wifiFirmwareVersion + port + staticServerIP + port + domainServerName + localIP + localGatewayIP + localNetMask + DST_TimezoneOffset + discoverable + timeZoneSet + intToHex(timezone, 1) + (offTimerEnabled ? QByteArray::fromHex("01:00") : QByteArray::fromHex("00:ff")) + intToHex(offTime, 2) + zeros + zeros + zeros + QStringLiteral("000000000000000000000000000000").toLocal8Bit();
  139. QByteArray recordLength = intToHex(record.length(), 2); // 2 bytes
  140. datagram[TableModify] = commandID[TableModify] + mac + twenties + zeros + QByteArray::fromHex("04:00:01") /*table number and unknown*/ + recordLength + record;
  141. sendDatagram(TableModify);
  142. }
  143. void Socket::tableData()
  144. {
  145. sendDatagram(ReadTable);
  146. datagram[SocketData] = commandID[SocketData] + mac + twenties + zeros + QByteArray::fromHex("04 00 00") + zeros;
  147. datagram[TimingData] = commandID[TimingData] + mac + twenties + zeros + QByteArray::fromHex("03 00 00") + zeros;
  148. // table number + 00 + version number
  149. sendDatagram(SocketData);
  150. sendDatagram(TimingData);
  151. }
  152. bool Socket::parseReply(QByteArray reply)
  153. {
  154. if (reply.left(2) != magicKey) {
  155. return false;
  156. }
  157. QByteArray id = reply.mid(4, 2);
  158. unsigned int datagram = std::distance(commandID, std::find(commandID, commandID + MaxCommands, id)); // match commandID with enum
  159. if (datagram == ReadTable) { // determine the table number
  160. unsigned int table = reply[reply.indexOf(twenties) + 11];
  161. switch (table) {
  162. case 1:
  163. break;
  164. case 3:
  165. datagram = TimingData;
  166. // qDebug() << reply.toHex();
  167. break;
  168. case 4:
  169. datagram = SocketData;
  170. break;
  171. case 0:
  172. qWarning() << "No table"; // FIXME: initial data query
  173. default:
  174. qWarning() << "Failed to identify data table.";
  175. datagram = ReadTable;
  176. return false;
  177. }
  178. }
  179. switch (datagram) {
  180. case QueryAll:
  181. case Discover: {
  182. uint32_t time = hexToInt(reply.right(5).left(4));
  183. socketDateTime.setDate(QDate(1900, 01, 01)); // midnight 1900-01-01
  184. socketDateTime.setTime(QTime(0, 0, 0));
  185. socketDateTime = socketDateTime.addSecs(time);
  186. }
  187. case Subscribe:
  188. case PowerOff:
  189. case PowerOn: {
  190. bool poweredOld = powered;
  191. powered = reply.right(1) == one;
  192. if (powered != poweredOld) {
  193. Q_EMIT stateChanged();
  194. }
  195. if (datagram == PowerOff && powered == true) { // Required to deque
  196. datagram = PowerOn;
  197. }
  198. break;
  199. }
  200. case ReadTable:
  201. // FIXME: order might be swapped;
  202. socketTableVersion = reply.mid(reply.indexOf(QByteArray::fromHex("000100000600")) + 6, 2);
  203. // 000100000600
  204. break;
  205. case SocketData: {
  206. // std::cout << reply.toHex().toStdString() << " " << datagram << std::endl; // for debugging purposes only
  207. unsigned short int index = reply.indexOf(rmac + twenties);
  208. versionID = reply.mid(index - 14, 2);
  209. index += 12; // length of rmac + padding
  210. remotePassword = reply.mid(index, 12); // max 12 symbols
  211. index += 12;
  212. socketName = reply.mid(index, 16); // max 16 symbols
  213. index += 16;
  214. icon = reply.mid(index, 2);
  215. index += 2;
  216. hardwareVersion = reply.mid(index, 4);
  217. index += 4;
  218. firmwareVersion = reply.mid(index, 4);
  219. index += 4;
  220. wifiFirmwareVersion = reply.mid(index, 4);
  221. index += 6;
  222. staticServerIP = reply.mid(index, 4); // 42.121.111.208 is used
  223. index += 6;
  224. domainServerName = reply.mid(index, 40);
  225. index += 40;
  226. localIP = reply.mid(index, 4);
  227. index += 4;
  228. localGatewayIP = reply.mid(index, 4);
  229. index += 4;
  230. localNetMask = reply.mid(index, 4);
  231. index += 4;
  232. DST_TimezoneOffset = reply.mid(index, 1);
  233. dst = DST_TimezoneOffset == QByteArray::fromHex("00") || DST_TimezoneOffset == QByteArray::fromHex("02");
  234. ++index;
  235. discoverable = reply.mid(index, 1);
  236. ++index;
  237. timeZoneSet = reply.mid(index, 1);
  238. ++index;
  239. timezone = hexToInt(reply.mid(index, 1));
  240. socketDateTime = socketDateTime.addSecs(timezone * 3600);
  241. ++index;
  242. offTimerEnabled = reply.mid(index, 2) == QByteArray::fromHex("01:00");
  243. index += 2;
  244. offTime = hexToInt(reply.mid(index, 2));
  245. Q_EMIT stateChanged();
  246. break;
  247. }
  248. case TimingData:
  249. // std::cout << reply.toHex().toStdString() << " " << datagram << std::endl; // for debugging purposes only
  250. break;
  251. case TableModify:
  252. sendDatagram(SocketData);
  253. break;
  254. default:
  255. return false;
  256. }
  257. if (commands.size() > 0) {
  258. if (datagram == commands.head()) {
  259. commands.dequeue();
  260. }
  261. }
  262. return true;
  263. }
  264. // length in bytes
  265. QByteArray Socket::intToHex(unsigned int decimal, unsigned int length, bool littleEndian) {
  266. QByteArray hex;
  267. QDataStream stream(&hex, QIODevice::WriteOnly);
  268. littleEndian ? stream.setByteOrder(QDataStream::LittleEndian) : stream.setByteOrder(QDataStream::BigEndian);
  269. stream << decimal;
  270. return littleEndian ? hex.left(length) : hex.right(length);
  271. }
  272. int Socket::hexToInt(QByteArray hex, bool littleEndian) {
  273. QDataStream stream(&hex, QIODevice::ReadOnly);
  274. littleEndian ? stream.setByteOrder(QDataStream::LittleEndian) : stream.setByteOrder(QDataStream::BigEndian);
  275. switch (hex.length()) {
  276. case 1:
  277. uint8_t value;
  278. stream >> value;
  279. return value;
  280. case 2:
  281. uint16_t value2;
  282. stream >> value2;
  283. return value2;
  284. case 4:
  285. uint32_t value4;
  286. stream >> value4;
  287. return value4;
  288. default:
  289. return 0;
  290. }
  291. }