/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2020, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * This project including source code and documentation is protected by * copyright law, and remains the property of nymea GmbH. All rights, including * reproduction, publication, editing and translation, are reserved. The use of * this project is subject to the terms of a license agreement to be concluded * with nymea GmbH in accordance with the terms of use of nymea GmbH, available * under https://nymea.io/license * * GNU General Public License Usage * Alternatively, this project may be redistributed and/or modified under the * terms of the GNU General Public License as published by the Free Software * Foundation, GNU version 3. This project 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 project. If not, see . * * For any further details and any questions please contact us under * contact@nymea.io or see our FAQ/Licensing Information on * https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "httpdaemon.h" #include "integrations/thing.h" #include "integrations/integrationplugin.h" #include "types/thingclass.h" #include "types/statetype.h" #include "extern-plugininfo.h" #include #include #include #include #include #include HttpDaemon::HttpDaemon(Thing *thing, IntegrationPlugin *parent): QTcpServer(parent), disabled(false), m_plugin(parent), m_thing(thing) { QHash portMap; portMap.insert(mockThingClassId, mockThingHttpportParamTypeId); portMap.insert(autoMockThingClassId, autoMockThingHttpportParamTypeId); listen(QHostAddress::Any, thing->paramValue(portMap.value(thing->thingClassId())).toInt()); } HttpDaemon::~HttpDaemon() { close(); } void HttpDaemon::incomingConnection(qintptr socket) { if (disabled) return; // When a new client connects, the server constructs a QTcpSocket and all // communication with the client is done over this QTcpSocket. QTcpSocket // works asynchronously, this means that all the communication is done // in the two slots readClient() and discardClient(). QTcpSocket* s = new QTcpSocket(this); connect(s, SIGNAL(readyRead()), this, SLOT(readClient())); connect(s, SIGNAL(disconnected()), this, SLOT(discardClient())); s->setSocketDescriptor(socket); } void HttpDaemon::actionExecuted(const ActionTypeId &actionTypeId) { m_actionList.append(qMakePair(actionTypeId, QDateTime::currentDateTime())); } void HttpDaemon::readClient() { if (disabled) return; // This slot is called when the client sent data to the server. The // server looks if it was a get request and sends a very simple HTML // document back. QTcpSocket* socket = (QTcpSocket*)sender(); if (socket->canReadLine()) { QByteArray data = socket->readLine(); QStringList tokens = QString(data).split(QRegExp("[ \r\n][ \r\n]*")); QUrl url("http://foo.bar" + tokens[1]); QUrlQuery query(url); if (url.path() == "/setstate") { StateTypeId stateTypeId = StateTypeId(query.queryItems().first().first); QVariant stateValue = query.queryItems().first().second; if (stateTypeId == mockBoolStateTypeId || stateTypeId == mockBatteryCriticalStateTypeId) { stateValue.convert(QVariant::Bool); } else if (stateTypeId == mockIntStateTypeId) { stateValue.convert(QVariant::Int); } else if (stateTypeId == mockSignalStrengthStateTypeId) { stateValue.convert(QVariant::UInt); } else if (stateTypeId == mockDoubleStateTypeId) { stateValue.convert(QVariant::Double); } qCDebug(dcMock()) << "Setting state value" << stateValue; emit setState(stateTypeId, stateValue); } else if (url.path() == "/generateevent") { qCDebug(dcMock()) << "Generate event called" << url.query(); QList> queryItems = query.queryItems(); ParamList params; for (int i = 0; i < queryItems.count(); i++) { QPair item = queryItems.at(i); if (item.first != "eventtypeid") { params.append(Param(ParamTypeId(item.first), item.second)); } } emit triggerEvent(EventTypeId(query.queryItemValue("eventtypeid")), params); } else if (url.path() == "/actionhistory") { qCDebug(dcMock()) << "Get action history called"; QTextStream os(socket); os.setAutoDetectUnicode(true); os << generateHeader(); for (int i = 0; i < m_actionList.count(); ++i) { os << m_actionList.at(i).first.toString() << '\n'; qCDebug(dcMock()) << " " << m_actionList.at(i).first.toString(); } socket->close(); return; } else if (url.path() == "/clearactionhistory") { qCDebug(dcMock()) << "Clear action history"; m_actionList.clear(); } else if (url.path() == "/disappear") { qCDebug(dcMock()) << "Should disappear"; emit disappear(); } else if (url.path() == "/reconfigureautodevice") { qCDebug(dcMock()) << "Reconfigure auto device"; emit reconfigureAutodevice(); } if (tokens[0] == "GET") { QTextStream os(socket); os.setAutoDetectUnicode(true); os << generateWebPage(); socket->close(); if (socket->state() == QTcpSocket::UnconnectedState) delete socket; } } } void HttpDaemon::discardClient() { QTcpSocket* socket = (QTcpSocket*)sender(); socket->deleteLater(); } QString HttpDaemon::generateHeader() { QString contentHeader( "HTTP/1.0 200 Ok\r\n" "Content-Type: text/html; charset=\"utf-8\"\r\n" "\r\n" ); return contentHeader; } QString HttpDaemon::generateWebPage() { ThingClass thingClass; foreach (const ThingClass &tc, m_plugin->supportedThings()) { if (tc.id() == m_thing->thingClassId()) { thingClass = tc; } } Q_ASSERT(thingClass.isValid()); QString body = QString( "" "" "

Mock thing controller

\n" "
" "

Thing Information

" "Name: %1
" "ID: %2
" "ThingClass ID: %3
").arg(m_thing->name()).arg(m_thing->id().toString()).arg(thingClass.id().toString()); body.append("
"); body.append("

States

"); body.append(""); for (int i = 0; i < thingClass.stateTypes().count(); ++i) { body.append(""); body.append(""); StateType stateType = thingClass.stateTypes().at(i); body.append(""); body.append(QString("").arg(stateType.id().toString()).arg(m_thing->states().at(i).value().toString())); body.append(""); body.append(""); body.append(""); } body.append("
" + stateType.name() + "
"); body.append("
"); body.append("

Events

"); body.append(""); for (int i = 0; i < thingClass.eventTypes().count(); ++i) { EventType eventType = thingClass.eventTypes().at(i); body.append(QString( "" "" "" "" "" "" ); } body.append("
%1").arg(eventType.name()).arg(eventType.id().toString())); if (!eventType.displayName().endsWith(" changed")) { body.append(QStringLiteral("")); } body.append("
"); body.append("
"); body.append("

Actions

"); body.append(""); body.append(""); for (int i = 0; i < m_actionList.count(); ++i) { ActionTypeId actionTypeId = ActionTypeId(m_actionList.at(i).first); QDateTime timestamp = m_actionList.at(i).second; QString actionName; foreach (const ActionType &at, thingClass.actionTypes()) { if (at.id() == actionTypeId) { actionName = at.name(); break; } } body.append(QString( "" "" "" "" "" ).arg(actionName).arg(actionTypeId.toString()).arg(timestamp.toString())); } body.append("
NameType IDTimestamp
%1%2%3
"); body.append("\n"); return generateHeader() + body; }