Added a Qt's websocket client to AFB into helpers 97/13797/2
authorLoïc Collignon <loic.collignon@iot.bzh>
Tue, 17 Apr 2018 13:27:14 +0000 (15:27 +0200)
committerLoïc Collignon <loic.collignon@iot.bzh>
Wed, 18 Apr 2018 08:49:33 +0000 (10:49 +0200)
This add a single Qt class to serve as a client
to Application Framework Binder.
It's optional, not enabled by default.
To enable you have to set AFB_HELPERS_QTWSCLIENT
to ON.

Change-Id: Ia0759a95688e48183e6661082693c410a575b14b
Signed-off-by: Loïc Collignon <loic.collignon@iot.bzh>
CMakeLists.txt
qafbwebsocketclient.cpp [new file with mode: 0644]
qafbwebsocketclient.h [new file with mode: 0644]
qafbwebsocketclient.md [new file with mode: 0644]

index f031163..5e1eb90 100644 (file)
 
 # 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 (file)
index 0000000..c4eb8ae
--- /dev/null
@@ -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 (file)
index 0000000..71e719d
--- /dev/null
@@ -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 (file)
index 0000000..17ce211
--- /dev/null
@@ -0,0 +1,3 @@
+# Qt WebSocket client to Application Framework Binder
+
+