s20/socket.cpp

325 lines
11 KiB
C++
Raw Permalink Normal View History

/*************************************************************************
* Copyright (C) 2015 by Andrius Štikonas <andrius@stikonas.eu> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
*************************************************************************/
#include "socket.h"
2015-01-14 13:55:28 +00:00
#include <algorithm>
2015-01-25 19:39:15 +00:00
#include <iostream>
2015-03-08 11:29:33 +00:00
2015-09-07 15:43:08 +01:00
#include <QDataStream>
2015-03-08 11:29:33 +00:00
#include <QMutex>
#include <QThread>
#include <QUdpSocket>
2015-01-14 13:55:28 +00:00
2015-08-18 15:29:09 +01:00
Socket::Socket(QHostAddress IPaddress, QByteArray reply)
{
ip = IPaddress;
2015-08-18 15:29:09 +01:00
mac = reply.mid(7, 6);
2015-01-14 13:55:28 +00:00
rmac = mac;
2015-08-18 15:29:09 +01:00
std::reverse(rmac.begin(), rmac.end());
powered = reply.right(1) == one;
2015-01-25 19:39:15 +00:00
2015-08-18 15:29:09 +01:00
commandID[QueryAll] = QStringLiteral("qa").toLatin1();
commandID[Discover] = QStringLiteral("qg").toLatin1(); // qg
commandID[Subscribe] = QStringLiteral("cl").toLatin1(); // Claim
2015-08-18 15:29:09 +01:00
commandID[PowerOn] = QStringLiteral("sf").toLatin1(); // State Flip (change of power state)
2015-01-25 19:39:15 +00:00
commandID[PowerOff] = commandID[PowerOn];
2015-08-18 15:29:09 +01:00
commandID[ReadTable] = QStringLiteral("rt").toLatin1();
commandID[SocketData] = commandID[ReadTable];
commandID[TimingData] = commandID[ReadTable];
2015-08-18 15:29:09 +01:00
commandID[TableModify] = QStringLiteral("tm").toLatin1();
QByteArray commandIDPower = QStringLiteral("dc").toLatin1(); // Socket change responce
2015-01-25 19:39:15 +00:00
// 2 hex bytes are the total length of the message
2015-04-02 22:10:06 +01:00
datagram[Discover] = commandID[Discover] + mac + twenties;
2015-02-15 15:11:39 +00:00
datagram[Subscribe] = commandID[Subscribe] + mac + twenties + rmac + twenties;
2015-02-21 21:14:22 +00:00
datagram[PowerOn] = commandIDPower + mac + twenties + zeros + one;
datagram[PowerOff] = commandIDPower + mac + twenties + zeros + zero;
datagram[ReadTable] = commandID[ReadTable] + mac + twenties + /*zeros*/QByteArray::fromHex("00 00 00 00") /*cookie*/ + QByteArray::fromHex("01 00") + zero + zeros;
2016-09-02 20:56:06 +01:00
udpSocket = new QUdpSocket(this);
2015-08-18 15:29:09 +01:00
udpSocket->connectToHost(ip, 10000);
2015-02-01 01:42:45 +00:00
2015-08-18 15:29:09 +01:00
connect(this, &Socket::datagramQueued, this, &Socket::processQueue);
2015-02-01 01:42:45 +00:00
subscribeTimer = new QTimer(this);
2015-08-18 15:29:09 +01:00
subscribeTimer->setInterval(2 * 60 * 1000); // 2 min
subscribeTimer->setSingleShot(false);
2015-02-01 01:42:45 +00:00
connect(subscribeTimer, &QTimer::timeout, this, &Socket::subscribe);
subscribeTimer->start();
2015-02-07 17:29:12 +00:00
subscribe();
tableData();
2015-02-01 01:42:45 +00:00
}
2015-08-18 15:29:09 +01:00
void Socket::sendDatagram(Datagram d)
2015-02-07 17:29:12 +00:00
{
commands.enqueue(d);
Q_EMIT datagramQueued();
}
void Socket::run()
{
2015-03-08 11:29:33 +00:00
QMutex mutex;
2015-08-18 15:29:09 +01:00
if (!mutex.tryLock()) {
2015-03-08 11:29:33 +00:00
return;
2015-03-11 12:34:56 +00:00
}
2015-03-08 11:29:33 +00:00
unsigned short retryCount = 0;
QByteArray currentDatagram, previousDatagram = 0, recordLength;
2015-08-18 15:29:09 +01:00
while (commands.size() > 0) {
currentDatagram = datagram[commands.head()];
2015-08-18 15:29:09 +01:00
if (previousDatagram == currentDatagram) {
++retryCount;
2015-03-11 12:34:56 +00:00
}
2015-08-18 15:29:09 +01:00
if (retryCount == 5) {
std::cout << "Stop retrying: " << currentDatagram.toHex().toStdString() << std::endl;
commands.dequeue();
2015-03-08 11:29:33 +00:00
retryCount = 0;
}
2015-02-15 15:11:39 +00:00
uint16_t length = currentDatagram.length() + 4; // +4 for magicKey and total message length
recordLength = intToHex(length, 2, false);
2015-02-15 15:11:39 +00:00
2015-08-18 15:29:09 +01:00
udpSocket->write(magicKey + recordLength + currentDatagram);
2015-03-08 11:29:33 +00:00
previousDatagram = currentDatagram;
2015-02-07 17:29:12 +00:00
QThread::msleep(100);
}
2015-03-08 11:29:33 +00:00
mutex.unlock();
2015-02-07 17:29:12 +00:00
}
2015-02-01 01:42:45 +00:00
void Socket::subscribe()
{
2015-08-18 15:29:09 +01:00
sendDatagram(Subscribe);
2015-01-14 13:55:28 +00:00
}
2015-04-02 22:10:06 +01:00
void Socket::discover()
{
2015-08-18 15:29:09 +01:00
sendDatagram(Discover);
2015-04-02 22:10:06 +01:00
}
2015-01-26 00:55:20 +00:00
void Socket::toggle()
2015-01-14 13:55:28 +00:00
{
2015-08-18 15:29:09 +01:00
sendDatagram(powered ? PowerOff : PowerOn);
2015-01-14 13:55:28 +00:00
}
void Socket::powerOff()
{
if (powered)
2015-08-18 15:29:09 +01:00
sendDatagram(PowerOff);
Q_EMIT stateChanged();
}
void Socket::powerOn()
{
if (!powered)
2015-08-18 15:29:09 +01:00
sendDatagram(PowerOn);
Q_EMIT stateChanged();
}
2015-08-18 15:29:09 +01:00
void Socket::changeSocketName(QString newName)
{
QByteArray name = newName.toLatin1().leftJustified(16, ' ', true);
writeSocketData(name, remotePassword, timezone, offTime);
}
2015-08-18 15:29:09 +01:00
void Socket::changeSocketPassword(QString newPassword)
{
QByteArray password = newPassword.toLatin1().leftJustified(12, ' ', true);
writeSocketData(socketName, password, timezone, offTime);
}
2015-08-18 15:29:09 +01:00
void Socket::changeTimezone(int8_t newTimezone)
{
writeSocketData(socketName, remotePassword, newTimezone, offTime);
}
void Socket::setOffTimer(uint16_t offTime)
{
writeSocketData(socketName, remotePassword, timezone, offTime);
2015-08-18 14:25:32 +01:00
}
void Socket::toggleOffTimer()
2015-09-04 22:49:13 +01:00
{
offTimerEnabled=!offTimerEnabled;
writeSocketData(socketName, remotePassword, timezone, offTime);
2015-09-04 22:49:13 +01:00
}
void Socket::writeSocketData(QByteArray socketName, QByteArray remotePassword, int8_t timezone, uint16_t offTime)
2015-08-18 14:25:32 +01:00
{
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();
2015-02-15 15:11:39 +00:00
QByteArray recordLength = intToHex(record.length(), 2); // 2 bytes
2015-02-12 23:26:31 +00:00
2015-08-18 15:29:09 +01:00
datagram[TableModify] = commandID[TableModify] + mac + twenties + zeros + QByteArray::fromHex("04:00:01") /*table number and unknown*/ + recordLength + record;
sendDatagram(TableModify);
}
2015-01-26 00:55:20 +00:00
void Socket::tableData()
{
2015-08-18 15:29:09 +01:00
sendDatagram(ReadTable);
datagram[SocketData] = commandID[SocketData] + mac + twenties + zeros + QByteArray::fromHex("04 00 00") + zeros;
datagram[TimingData] = commandID[TimingData] + mac + twenties + zeros + QByteArray::fromHex("03 00 00") + zeros;
// table number + 00 + version number
2015-08-18 15:29:09 +01:00
sendDatagram(SocketData);
sendDatagram(TimingData);
2015-01-26 00:55:20 +00:00
}
2015-08-18 15:29:09 +01:00
bool Socket::parseReply(QByteArray reply)
{
2015-08-18 15:29:09 +01:00
if (reply.left(2) != magicKey) {
return false;
2015-02-01 01:42:45 +00:00
}
2015-08-18 15:29:09 +01:00
QByteArray id = reply.mid(4, 2);
unsigned int datagram = std::distance(commandID, std::find(commandID, commandID + MaxCommands, id)); // match commandID with enum
if (datagram == ReadTable) { // determine the table number
unsigned int table = reply[reply.indexOf(twenties) + 11];
switch (table) {
case 1:
break;
case 3:
datagram = TimingData;
// qDebug() << reply.toHex();
break;
case 4:
datagram = SocketData;
break;
2015-03-11 12:34:56 +00:00
case 0:
qWarning() << "No table"; // FIXME: initial data query
default:
2015-03-11 12:34:56 +00:00
qWarning() << "Failed to identify data table.";
datagram = ReadTable;
return false;
2015-01-25 19:39:15 +00:00
}
}
2015-08-18 15:29:09 +01:00
switch (datagram) {
case QueryAll:
2015-08-18 15:29:09 +01:00
case Discover: {
uint32_t time = hexToInt(reply.right(5).left(4));
2015-04-02 22:10:06 +01:00
socketDateTime.setDate(QDate(1900, 01, 01)); // midnight 1900-01-01
socketDateTime.setTime(QTime(0, 0, 0));
socketDateTime = socketDateTime.addSecs(time);
}
case Subscribe:
case PowerOff:
2015-08-18 15:29:09 +01:00
case PowerOn: {
2015-02-01 01:42:45 +00:00
bool poweredOld = powered;
2015-03-08 11:34:01 +00:00
powered = reply.right(1) == one;
2015-08-18 15:29:09 +01:00
if (powered != poweredOld) {
2015-02-01 01:42:45 +00:00
Q_EMIT stateChanged();
2015-03-11 12:34:56 +00:00
}
2015-08-18 15:29:09 +01:00
if (datagram == PowerOff && powered == true) { // Required to deque
2015-02-07 17:29:12 +00:00
datagram = PowerOn;
}
break;
2015-02-01 01:42:45 +00:00
}
case ReadTable:
// FIXME: order might be swapped;
2015-08-18 15:29:09 +01:00
socketTableVersion = reply.mid(reply.indexOf(QByteArray::fromHex("000100000600")) + 6, 2);
// 000100000600
break;
2015-08-18 15:29:09 +01:00
case SocketData: {
2015-03-08 11:29:33 +00:00
// std::cout << reply.toHex().toStdString() << " " << datagram << std::endl; // for debugging purposes only
2015-08-18 15:29:09 +01:00
unsigned short int index = reply.indexOf(rmac + twenties);
versionID = reply.mid(index - 14, 2);
index += 12; // length of rmac + padding
2015-08-18 15:29:09 +01:00
remotePassword = reply.mid(index, 12); // max 12 symbols
index += 12;
2015-08-18 15:29:09 +01:00
socketName = reply.mid(index, 16); // max 16 symbols
index += 16;
2015-08-18 15:29:09 +01:00
icon = reply.mid(index, 2);
index += 2;
2015-08-18 15:29:09 +01:00
hardwareVersion = reply.mid(index, 4);
index += 4;
2015-08-18 15:29:09 +01:00
firmwareVersion = reply.mid(index, 4);
index += 4;
2015-08-18 15:29:09 +01:00
wifiFirmwareVersion = reply.mid(index, 4);
index += 6;
2015-08-18 15:29:09 +01:00
staticServerIP = reply.mid(index, 4); // 42.121.111.208 is used
2015-02-15 15:26:37 +00:00
index += 6;
2015-08-18 15:29:09 +01:00
domainServerName = reply.mid(index, 40);
2015-02-15 15:26:37 +00:00
index += 40;
2015-08-18 15:29:09 +01:00
localIP = reply.mid(index, 4);
index += 4;
2015-08-18 15:29:09 +01:00
localGatewayIP = reply.mid(index, 4);
index += 4;
2015-08-18 15:29:09 +01:00
localNetMask = reply.mid(index, 4);
index += 4;
DST_TimezoneOffset = reply.mid(index, 1);
dst = DST_TimezoneOffset == QByteArray::fromHex("00") || DST_TimezoneOffset == QByteArray::fromHex("02");
++index;
2015-08-18 15:29:09 +01:00
discoverable = reply.mid(index, 1);
++index;
2015-08-18 15:29:09 +01:00
timeZoneSet = reply.mid(index, 1);
++index;
2015-11-01 22:25:23 +00:00
timezone = hexToInt(reply.mid(index, 1));
2016-04-30 13:35:52 +01:00
socketDateTime = socketDateTime.addSecs(timezone * 3600);
++index;
offTimerEnabled = reply.mid(index, 2) == QByteArray::fromHex("01:00");
2015-08-18 14:25:32 +01:00
index += 2;
offTime = hexToInt(reply.mid(index, 2));
2015-02-01 01:42:45 +00:00
Q_EMIT stateChanged();
break;
}
case TimingData:
// std::cout << reply.toHex().toStdString() << " " << datagram << std::endl; // for debugging purposes only
break;
case TableModify:
2015-08-18 15:29:09 +01:00
sendDatagram(SocketData);
2015-02-01 01:42:45 +00:00
break;
default:
return false;
}
2015-02-07 17:29:12 +00:00
2015-08-18 15:29:09 +01:00
if (commands.size() > 0) {
if (datagram == commands.head()) {
2015-02-07 17:29:12 +00:00
commands.dequeue();
}
}
return true;
2015-01-14 13:55:28 +00:00
}
2015-11-01 00:31:38 +00:00
// length in bytes
QByteArray Socket::intToHex(unsigned int decimal, unsigned int length, bool littleEndian) {
2015-11-01 00:31:38 +00:00
QByteArray hex;
QDataStream stream(&hex, QIODevice::WriteOnly);
littleEndian ? stream.setByteOrder(QDataStream::LittleEndian) : stream.setByteOrder(QDataStream::BigEndian);
stream << decimal;
return littleEndian ? hex.left(length) : hex.right(length);
2015-11-01 00:31:38 +00:00
}
int Socket::hexToInt(QByteArray hex, bool littleEndian) {
QDataStream stream(&hex, QIODevice::ReadOnly);
littleEndian ? stream.setByteOrder(QDataStream::LittleEndian) : stream.setByteOrder(QDataStream::BigEndian);
switch (hex.length()) {
case 1:
uint8_t value;
stream >> value;
return value;
case 2:
uint16_t value2;
stream >> value2;
return value2;
case 4:
uint32_t value4;
stream >> value4;
return value4;
default:
return 0;
}
}