Add basic communication with the JSON RPC 2.0 server

This commit is contained in:
Simon Stürz 2025-06-04 16:47:22 +02:00
parent d226b35d55
commit 5acce61d6a
9 changed files with 497 additions and 95 deletions

View File

@ -4,15 +4,19 @@ QT += network websockets
PKGCONFIG += nymea-mqtt
SOURCES += \
jsonrpc/everestjsonrpcclient.cpp \
jsonrpc/everestjsonrpcinterface.cpp \
jsonrpc/everestjsonrpcreply.cpp \
mqtt/everestmqtt.cpp \
mqtt/everestmqttclient.cpp \
mqtt/everestmqttdiscovery.cpp \
integrationplugineverest.cpp \
jsonrpc/everestjsonrpcclient.cpp
integrationplugineverest.cpp
HEADERS += \
jsonrpc/everestjsonrpcclient.h \
jsonrpc/everestjsonrpcinterface.h \
jsonrpc/everestjsonrpcreply.h \
mqtt/everestmqtt.h \
mqtt/everestmqttclient.h \
mqtt/everestmqttdiscovery.h \
integrationplugineverest.h \
jsonrpc/everestjsonrpcclient.h
integrationplugineverest.h

View File

@ -41,7 +41,8 @@ IntegrationPluginEverest::IntegrationPluginEverest()
void IntegrationPluginEverest::init()
{
EverestJsonRpcClient *client = new EverestJsonRpcClient(this);
client->setSeverUrl(QUrl("ws://10.10.10.165:8080"));
}
void IntegrationPluginEverest::startMonitoringAutoThings()

View File

@ -35,6 +35,7 @@
#include "extern-plugininfo.h"
#include "mqtt/everestmqttclient.h"
#include "jsonrpc/everestjsonrpcclient.h"
#include <mqttclient.h>

View File

@ -1,84 +1,109 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* 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 "everestjsonrpcclient.h"
#include "extern-plugininfo.h"
#include <QJsonDocument>
#include <QJsonParseError>
EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent)
: QObject{parent}
{
m_webSocket = new QWebSocket("nymea-client", QWebSocketProtocol::Version13, this);
connect(m_webSocket, &QWebSocket::disconnected, this, &EverestJsonRpcClient::onDisconnected);
connect(m_webSocket, &QWebSocket::textMessageReceived, this, &EverestJsonRpcClient::onTextMessageReceived);
connect(m_webSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
connect(m_webSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState)));
}
EverestJsonRpcClient::~EverestJsonRpcClient()
{
disconnectServer();
}
void EverestJsonRpcClient::sendData(const QByteArray &data)
{
m_webSocket->sendTextMessage(QString::fromUtf8(data));
}
void EverestJsonRpcClient::connectServer(const QUrl &serverUrl)
{
if (m_connected) {
m_connected = false;
emit connectedChanged(m_connected);
m_webSocket->close();
}
m_serverUrl = serverUrl;
qCDebug(dcEverest()) << "Connecting to" << m_serverUrl.toString();
m_webSocket->open(m_serverUrl);
}
void EverestJsonRpcClient::disconnectServer()
{
qCDebug(dcEverest()) << "Disconnecting from" << m_serverUrl.toString();
m_webSocket->close();
}
void EverestJsonRpcClient::onDisconnected()
{
qCDebug(dcEverest()) << "Disconnected from" << m_webSocket->requestUrl().toString() << m_webSocket->closeReason();
if (m_connected) {
m_connected = false;
emit connectedChanged(m_connected);
}
}
void EverestJsonRpcClient::onError(QAbstractSocket::SocketError error)
{
qCDebug(dcEverest()) << "Socket error occurred" << error << m_webSocket->errorString();
}
void EverestJsonRpcClient::onStateChanged(QAbstractSocket::SocketState state)
{
qCDebug(dcEverest()) << "Socket state changed" << state;
switch (state) {
case QAbstractSocket::ConnectedState:
qCDebug(dcEverest()) << "Connected with" << m_webSocket->requestUrl().toString();
if (!m_connected) {
m_connected = true;
emit connectedChanged(m_connected);
m_interface = new EverestJsonRpcInterface(this);
connect(m_interface, &EverestJsonRpcInterface::connectedChanged, this, [this](bool connected){
if (connected) {
EverestJsonRpcReply *reply = hello();
connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){
qCDebug(dcEverest()) << "Reply finished" << m_interface->serverUrl().toString() << reply->method();
});
} else {
// Client not available any more
}
break;
default:
if (m_connected) {
m_connected = false;
emit connectedChanged(m_connected);
}
break;
}
});
connect(m_interface, &EverestJsonRpcInterface::dataReceived, this, &EverestJsonRpcClient::processDataPacket);
}
void EverestJsonRpcClient::onTextMessageReceived(const QString &message)
QUrl EverestJsonRpcClient::serverUrl()
{
emit dataReceived(message.toUtf8());
return m_interface->serverUrl();
}
void EverestJsonRpcClient::setSeverUrl(const QUrl &serverUrl)
{
m_interface->connectServer(serverUrl);
}
EverestJsonRpcReply *EverestJsonRpcClient::hello()
{
EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "API.Hello", QVariantMap(), this);
qCDebug(dcEverest()) << "Calling" << reply->method();
sendRequest(reply->requestMap());
m_replies.insert(m_commandId, reply);
m_commandId++;
return reply;
}
void EverestJsonRpcClient::sendRequest(const QVariantMap &request)
{
QByteArray data = QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact) + '\n';
qCDebug(dcEverest()) << "-->" << m_interface->serverUrl().toString() << qUtf8Printable(data);
m_interface->sendData(data);
}
void EverestJsonRpcClient::processDataPacket(const QByteArray &data)
{
qCDebug(dcEverest()) << "<--" << m_interface->serverUrl().toString() << qUtf8Printable(data);
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcEverest()) << "Invalid JSON data recived" << m_interface->serverUrl().toString() << error.errorString();
return;
}
QVariantMap dataMap = jsonDoc.toVariant().toMap();
int commandId = dataMap.value("id").toInt();
EverestJsonRpcReply *reply = m_replies.take(commandId);
if (reply) {
qCDebug(dcEverest()) << QString("Got response for %1: %2").arg(reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented)));
// if (dataMap.value("status").toString() == "error") {
// qCWarning(dcEverest()) << "Api error happend" << dataMap.value("error").toString();
// // FIMXME: handle json layer errors
// }
reply->setResponse(dataMap);
emit reply->finished();
return;
}
}

View File

@ -1,36 +1,71 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* 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
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef EVERESTJSONRPCCLIENT_H
#define EVERESTJSONRPCCLIENT_H
#include <QObject>
#include <QWebSocket>
#include "everestjsonrpcreply.h"
#include "everestjsonrpcinterface.h"
class EverestJsonRpcClient : public QObject
{
Q_OBJECT
public:
explicit EverestJsonRpcClient(QObject *parent = nullptr);
~EverestJsonRpcClient();
void sendData(const QByteArray &data);
QUrl serverUrl();
void setSeverUrl(const QUrl &serverUrl);
public slots:
void connectServer(const QUrl &serverUrl);
void disconnectServer();
bool available() const;
EverestJsonRpcReply *hello();
signals:
void connectedChanged(bool connected);
void dataReceived(const QByteArray &data);
private slots:
void onDisconnected();
void onError(QAbstractSocket::SocketError error);
void onStateChanged(QAbstractSocket::SocketState state);
void onTextMessageReceived(const QString &message);
void availableChanged(bool available);
private:
QWebSocket *m_webSocket = nullptr;
QUrl m_serverUrl;
bool m_connected = false;
bool m_available = false;
int m_commandId = 0;
EverestJsonRpcInterface *m_interface = nullptr;
QHash<int, EverestJsonRpcReply *> m_replies;
void sendRequest(const QVariantMap &request);
private slots:
void processDataPacket(const QByteArray &data);
};
#endif // EVERESTJSONRPCCLIENT_H

View File

@ -0,0 +1,125 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* 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 "everestjsonrpcinterface.h"
#include "extern-plugininfo.h"
EverestJsonRpcInterface::EverestJsonRpcInterface(QObject *parent)
: QObject{parent}
{
m_webSocket = new QWebSocket("nymea-client", QWebSocketProtocol::Version13, this);
connect(m_webSocket, &QWebSocket::disconnected, this, &EverestJsonRpcInterface::onDisconnected);
connect(m_webSocket, &QWebSocket::textMessageReceived, this, &EverestJsonRpcInterface::onTextMessageReceived);
connect(m_webSocket, &QWebSocket::binaryMessageReceived, this, &EverestJsonRpcInterface::onBinaryMessageReceived);
connect(m_webSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
connect(m_webSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState)));
}
EverestJsonRpcInterface::~EverestJsonRpcInterface()
{
disconnectServer();
}
QUrl EverestJsonRpcInterface::serverUrl() const
{
return m_serverUrl;
}
void EverestJsonRpcInterface::sendData(const QByteArray &data)
{
m_webSocket->sendTextMessage(QString::fromUtf8(data));
}
void EverestJsonRpcInterface::connectServer(const QUrl &serverUrl)
{
if (m_connected) {
m_connected = false;
emit connectedChanged(m_connected);
m_webSocket->close();
}
m_serverUrl = serverUrl;
qCDebug(dcEverest()) << "Connecting to" << m_serverUrl.toString();
m_webSocket->open(m_serverUrl);
}
void EverestJsonRpcInterface::disconnectServer()
{
qCDebug(dcEverest()) << "Disconnecting from" << m_serverUrl.toString();
m_webSocket->close();
}
void EverestJsonRpcInterface::onDisconnected()
{
qCDebug(dcEverest()) << "Disconnected from" << m_webSocket->requestUrl().toString() << m_webSocket->closeReason();
if (m_connected) {
m_connected = false;
emit connectedChanged(m_connected);
}
}
void EverestJsonRpcInterface::onError(QAbstractSocket::SocketError error)
{
qCDebug(dcEverest()) << "Socket error occurred" << error << m_webSocket->errorString();
}
void EverestJsonRpcInterface::onStateChanged(QAbstractSocket::SocketState state)
{
qCDebug(dcEverest()) << "Socket state changed" << state;
switch (state) {
case QAbstractSocket::ConnectedState:
qCDebug(dcEverest()) << "Connected with" << m_webSocket->requestUrl().toString();
if (!m_connected) {
m_connected = true;
emit connectedChanged(m_connected);
}
break;
default:
if (m_connected) {
m_connected = false;
emit connectedChanged(m_connected);
}
break;
}
}
void EverestJsonRpcInterface::onTextMessageReceived(const QString &message)
{
emit dataReceived(message.toUtf8());
}
void EverestJsonRpcInterface::onBinaryMessageReceived(const QByteArray &message)
{
emit dataReceived(message);
}

View File

@ -0,0 +1,70 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* 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
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef EVERESTJSONRPCINTERFACE_H
#define EVERESTJSONRPCINTERFACE_H
#include <QObject>
#include <QWebSocket>
class EverestJsonRpcInterface : public QObject
{
Q_OBJECT
public:
explicit EverestJsonRpcInterface(QObject *parent = nullptr);
~EverestJsonRpcInterface();
QUrl serverUrl() const;
void sendData(const QByteArray &data);
public slots:
void connectServer(const QUrl &serverUrl);
void disconnectServer();
signals:
void connectedChanged(bool connected);
void dataReceived(const QByteArray &data);
private slots:
void onDisconnected();
void onError(QAbstractSocket::SocketError error);
void onStateChanged(QAbstractSocket::SocketState state);
void onTextMessageReceived(const QString &message);
void onBinaryMessageReceived(const QByteArray &message);
private:
QWebSocket *m_webSocket = nullptr;
QUrl m_serverUrl;
bool m_connected = false;
};
#endif // EVERESTJSONRPCINTERFACE_H

View File

@ -0,0 +1,79 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* 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 "everestjsonrpcreply.h"
EverestJsonRpcReply::EverestJsonRpcReply(int commandId, QString method, QVariantMap params, QObject *parent)
: QObject{parent},
m_commandId{commandId},
m_method{method},
m_params{params}
{
}
int EverestJsonRpcReply::commandId() const
{
return m_commandId;
}
QString EverestJsonRpcReply::method() const
{
return m_method;
}
QVariantMap EverestJsonRpcReply::params() const
{
return m_params;
}
QVariantMap EverestJsonRpcReply::requestMap()
{
QVariantMap request;
request.insert("id", commandId());
request.insert("jsonrpc", "2.0");
request.insert("method", method());
if (!m_params.isEmpty())
request.insert("params", params());
m_commandId++;
return request;
}
QVariantMap EverestJsonRpcReply::response() const
{
return m_response;
}
void EverestJsonRpcReply::setResponse(const QVariantMap &response)
{
m_response = response;
}

View File

@ -0,0 +1,62 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* 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
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef EVERESTJSONRPCREPLY_H
#define EVERESTJSONRPCREPLY_H
#include <QObject>
#include <QVariantMap>
class EverestJsonRpcReply : public QObject
{
Q_OBJECT
public:
explicit EverestJsonRpcReply(int commandId, QString method, QVariantMap params = QVariantMap(), QObject *parent = nullptr);
int commandId() const;
QString method() const;
QVariantMap params() const;
QVariantMap requestMap();
QVariantMap response() const;
void setResponse(const QVariantMap &response);
signals:
void finished();
private:
int m_commandId;
QString m_method;
QVariantMap m_params;
QVariantMap m_response;
};
#endif // EVERESTJSONRPCREPLY_H