diff options
-rw-r--r-- | CMakeLists.txt | 20 | ||||
-rw-r--r-- | qafbwebsocketclient.cpp | 173 | ||||
-rw-r--r-- | qafbwebsocketclient.h | 69 | ||||
-rw-r--r-- | qafbwebsocketclient.md | 3 |
4 files changed, 264 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index f031163..5e1eb90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,9 +21,27 @@ # Add target to project dependency list PROJECT_TARGET_ADD(afb-helpers) +set(CMAKE_AUTOMOC ON) + + set(AFB_HELPERS_SRCS curl-wrap.c escape.c wrap-json.c filescan-utils.c) + + option(AFB_HELPERS_QTWSCLIENT "Enable the Qt's websocket client to Application Framework Binders" OFF) + + if (AFB_HELPERS_QTWSCLIENT) + message(STATUS "Qt's WebSocket AFB Client: Enabled!") + set(AFB_HELPERS_SRCS ${AFB_HELPERS_SRCS} qafbwebsocketclient.cpp qafbwebsocketclient.h) + find_package(Qt5WebSockets REQUIRED) + else() + message(STATUS "Qt's WebSocket AFB Client: Disabled!") + endif() # Define targets - ADD_LIBRARY(${TARGET_NAME} STATIC curl-wrap.c escape.c wrap-json.c filescan-utils.c) + ADD_LIBRARY(${TARGET_NAME} STATIC ${AFB_HELPERS_SRCS}) + + if (AFB_HELPERS_QTWSCLIENT) + target_link_libraries(${TARGET_NAME} Qt5::WebSockets) + qt5_use_modules(${TARGET_NAME} WebSockets) + endif() # Library properties SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES diff --git a/qafbwebsocketclient.cpp b/qafbwebsocketclient.cpp new file mode 100644 index 0000000..c4eb8ae --- /dev/null +++ b/qafbwebsocketclient.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2018 Iot.Bzh + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "qafbwebsocketclient.h" +#include <QJsonDocument> +#include <QJsonArray> +#include <QJsonObject> + +/*! + * \brief Default constructor. + * \param parent Parent object. + */ +QAfbWebsocketClient::QAfbWebsocketClient(QObject* parent) + : QObject{parent} + , m_nextCallId{0} +{ + connect(&m_socket, SIGNAL(connected()), this, SLOT(onSocketConnected())); + connect(&m_socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + connect(&m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError))); + connect(&m_socket, SIGNAL(textMessageReceived(QString)), this, SLOT(onSocketTextReceived(QString))); +} + +/*! + * \brief Get last error code. + * \return Return the last error code. + */ +QAbstractSocket::SocketError QAfbWebsocketClient::error() const +{ + return m_socket.error(); +} + +/*! + * \brief Get last error as a string. + * \return Return the last error as a string. + */ +QString QAfbWebsocketClient::errorString() const +{ + return m_socket.errorString(); +} + +/*! + * \brief Check if connection is ready or not. + * \return Return \c true if the connected is ready to read and write, \c false otherwise. + */ +bool QAfbWebsocketClient::isValid() const +{ + return m_socket.isValid(); +} +/*! + * \brief Open the connection. + * \param u Url to connect to. + */ +void QAfbWebsocketClient::open(const QUrl& u) +{ + m_socket.open(u); +} + +/*! + * \brief Close the connection. + */ +void QAfbWebsocketClient::close() +{ + m_socket.close(); +} + +/*! + * \brief Call an api's verb with an argument. + * \param api Api to call. + * \param verb Verb to call. + * \param arg Argument to pass. + */ +void QAfbWebsocketClient::call(const QString& api, const QString& verb, const QJsonValue& arg, closure_t closure) +{ + QString callId = QString::number(m_nextCallId); + m_closures[callId] = closure; + + QJsonArray msg; + msg.append(2); // Call + msg.append(callId); + msg.append(api + "/" + verb); + msg.append(arg); + + m_nextCallId++; + + QJsonDocument value; + value.setArray(msg); + + sendTextMessage(value.toJson(QJsonDocument::Compact)); +} + +/*! + * \brief Send a text message over the websocket. + * \param msg Message to send. + * This is use for test only, you should not use this method as + * it sent text as-is, so you have to follow the binder's + * protocol by your self. + */ +void QAfbWebsocketClient::sendTextMessage(QString msg) +{ + m_socket.sendTextMessage(msg); + qDebug() << "WebSocket Text Sent: " << msg; + emit textSent(msg); +} + +/*! + * \brief Called when socket signals to be connected. + */ +void QAfbWebsocketClient::onSocketConnected() +{ + emit connected(); +} + +/*! + * \brief Called when socket signals to be disconnected. + */ +void QAfbWebsocketClient::onSocketDisconnected() +{ + emit disconnected(); +} + +/*! + * \brief Called when socket signals an error. + * \param e Error code. + */ +void QAfbWebsocketClient::onSocketError(QAbstractSocket::SocketError e) +{ + emit error(e); +} + +/*! + * \brief Called when socket signals a received text. + * \param msg Message received. + */ +void QAfbWebsocketClient::onSocketTextReceived(QString msg) +{ + emit textReceived(msg); + qDebug() << "WebSocket Text Received: " << msg; + + QJsonDocument doc = QJsonDocument::fromJson(msg.toUtf8()); + QJsonArray arr = doc.array(); + + switch(arr[0].toInt()) + { + case 3: // RetOK + case 4: // RetErr + { + auto it = m_closures.find(arr[1].toString()); + if (it != m_closures.end()) + { + closure_t closure = *it; + m_closures.erase(it); + closure(arr[0].toInt() == 3, arr[2]); + } + break; + } + case 5: // Events + emit event(arr[1].toString(), arr[2].toObject()["data"]); + break; + } +} diff --git a/qafbwebsocketclient.h b/qafbwebsocketclient.h new file mode 100644 index 0000000..71e719d --- /dev/null +++ b/qafbwebsocketclient.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 Iot.Bzh + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QAFBWEBSOCKETCLIENT_H +#define QAFBWEBSOCKETCLIENT_H + +#include <QObject> +#include <QWebSocket> +#include <QJsonValue> +#include <functional> + +/*! + * \brief A WebSocket client to an Application Framework Binder. + */ +class QAfbWebsocketClient + : public QObject +{ + Q_OBJECT + +public: + using closure_t = std::function<void(bool, const QJsonValue&)>; + + explicit QAfbWebsocketClient(QObject* parent = nullptr); + + QAbstractSocket::SocketError error() const; + QString errorString() const; + bool isValid() const; + + void call(const QString& api, const QString& verb, const QJsonValue& arg = QJsonValue(), closure_t closure = nullptr); + +public slots: + void open(const QUrl& u); + void close(); + void sendTextMessage(QString msg); + +private slots: + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketError(QAbstractSocket::SocketError e); + void onSocketTextReceived(QString msg); + +signals: + void connected(); + void disconnected(); + void error(QAbstractSocket::SocketError); + void textReceived(QString msg); + void textSent(QString msg); + void event(QString eventName, const QJsonValue& data); + +private: + int m_nextCallId; + QWebSocket m_socket; + QMap<QString, closure_t> m_closures; +}; + +#endif // QAFBWEBSOCKETCLIENT_H diff --git a/qafbwebsocketclient.md b/qafbwebsocketclient.md new file mode 100644 index 0000000..17ce211 --- /dev/null +++ b/qafbwebsocketclient.md @@ -0,0 +1,3 @@ +# Qt WebSocket client to Application Framework Binder + + |