diff options
author | maratsabitov <marat.sabitov@mera.com> | 2020-05-08 18:03:57 +0300 |
---|---|---|
committer | Marat Sabitov <marat.sabitov@mera.com> | 2020-07-09 09:38:01 +0300 |
commit | 0cecffd4565b52bd8d200f6bc9f4144f244a6515 (patch) | |
tree | c0969bb192bb711c0bd2e2444a66c0060b2e13af | |
parent | f496bfe75ef0a1ec2658d3fec51866d4c7551282 (diff) |
Introduce cloudproxy servicejellyfish_9.99.4jellyfish_9.99.3jellyfish_9.99.2jellyfish/9.99.4jellyfish/9.99.3jellyfish/9.99.29.99.49.99.39.99.2
Cloudproxy service allows applications to communicate with clouds.
The commit includes the test applications as sample for real client imlementation.
Bug-AGL: SPEC-3370
Change-Id: I82d8122e93d01451f4471cc20c706e75c16f1c29
Signed-off-by: maratsabitov <marat.sabitov@mera.com>
53 files changed, 4729 insertions, 0 deletions
diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..9bb3b58 --- /dev/null +++ b/.gitreview @@ -0,0 +1,5 @@ +[gerrit] +host=gerrit.automotivelinux.org +port=29418 +project=apps/agl-service-cloudproxy +defaultbranch=master diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..a7f89ba --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +########################################################################### +# Copyright (C) 2020 MERA +# +# 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. +########################################################################### + +project(CloudProxyService C CXX) + +cmake_minimum_required(VERSION 3.0) + +set(PROJECT_NAME "CloudProxy Service") +set(PROJECT_VERSION "1.0") +set(PROJECT_DESCRIPTION "CloudProxy Service") +set(PROJECT_INCLUDEDIR ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) +set(PROJECT_LIBDIR ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) +set(binding_install_dir ${PREFIX}/lib/cloudproxy) +set(PROJECT_BINDINGDIR ${binding_install_dir}) + +# get pkgconfig +INCLUDE(FindPkgConfig) +link_libraries(-Wl,--as-needed -Wl,--gc-sections -Wl,--no-undefined) + +add_subdirectory(src) @@ -0,0 +1,177 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..876e80c --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Cloud Proxy Service + +## Overview + +*Cloud proxy service* provides possibility to send and receive data to/from clouds. + +It is allowed to use *Cloud proxy service* from several application. +The messages/events separtion between application is done within service. + +## Verbs + +``` +| Name | Description | Data | +| ------------------ |:----------------------------------------|:----------------------------------------------------| +| sendMessage | send data to the cloud | application ID, JSON {"data":"message content"} | +| subscribe | subs. for particular application id | application ID extracted from msg | +| unsubscribe | unsubs. for particular application id | application ID extracted from msg | +| ping | verify connectivity to service | N/A | +``` +## Events + +``` +| Name | Description | +| ---------------------------- |:--------------------------------------------------------| +| sendMessageConfirmation | confirmation from cloud for previously sent message | +| receivedMessage | message from cloud to be propagated to the application | +``` + +## Configuration + +Cloud proxy service requires configuration of the cloud to be used. Configuration is stored in the **config.ini** file. + +By default the configuration file **config.ini** is searched stored in + +* $AFM_APP_INSTALL_DIR/etc/config.ini - **default one** +* /etc/cloudproxy-service/etc/config.ini + +At the moment only Azure cloud is supported. + +### Azure configuration +The configuration has to contain the connection string for device: **DeviceConnectionString** in +section **[AzureCloudConnection]** + +``` +[AzureCloudConnection] +DeviceConnectionString=My=Azure=Device=Connection=String +``` + + + diff --git a/app_client_examples/README.md b/app_client_examples/README.md new file mode 100755 index 0000000..8edaddd --- /dev/null +++ b/app_client_examples/README.md @@ -0,0 +1,98 @@ +# Test cloud applications #
+
+## Description
+
+The test-cloud-app and telemetry-cloud-app are examples of an agl-cloudproxy-service client.
+They are used to demonstrate the possibilities of the cloudproxy service.
+
+## Applications
+
+* test-cloud-app: Simple application demonstrating the following possibilities:
+ * Sending the messages to cloud over cloud proxy server
+ * Receiving confirmations from cloud on the sent messages
+ * Receiving messages from cloud
+
+
+* telemetry-cloud-app: Application which sends the GPS coordinates to the cloud
+ * Sending of the messages with telemetry to the cloud
+ * Receiving of the messages from the cloud
+
+Applications use CloudProxy client as high level interface for the messages sending and receiving
+
+## CloudProxy client
+
+CloudProxy client hides the communication with CloudProxy server. It incupsulates the following actions
+
+* Connection to **cloudporxy** over wsj1 websocket
+* Subscription to the event
+* Event loop handling
+
+**Note:** At the moment only two event types are supported
+1. Event_SendMessageConfirmation
+2. Event_ReceivedMessage
+
+### Usage of a Cloudproxy client:
+
+* Include the appropriate header to appliciation
+
+ ```
+ #include "cloudproxy-client/cloudproxyclient.h"
+ ```
+
+* Initialization of the CloudProxy client inside the application:
+
+```
+CloudProxyClient* g_cloudproxyclient{nullptr};
+
+g_cloudproxyclient = new CloudProxyClient();
+g_cloudproxyclient->init(port, token.c_str());
+
+```
+
+### Message handling from/to the CloudProxy server:
+
+
+* Subsription to the Event_SendMessageConfirmation
+
+
+
+```
+g_cloudproxyclient->set_event_handler(CloudProxyClient::Event_SendMessageConfirmation, [](json_object* object){
+ json_object *j_result;
+ if(!json_object_object_get_ex(object, "result", &j_result))
+ {
+ qDebug("Can't read confirmation result");
+ return;
+ }
+
+ qDebug("Confirmation result %d", (int)json_object_get_boolean(j_result));
+ });
+
+ ```
+
+
+
+* Subsription to the Event_SendMessageConfirmation
+
+```
+g_cloudproxyclient->set_event_handler(CloudProxyClient::Event_ReceivedMessage, [](json_object* object)
+ json_object *event_data;
+ const char* data_str{nullptr};
+ if(!json_object_object_get_ex(object, "data", &event_data) ||
+ data_str = json_object_get_string(event_data)) == nullptr)
+ {
+ qDebug("Can't read event data");
+ return;
+ }
+
+ qDebug("Received data: %s", data_str);
+ });
+ ```
+
+
+* Message sending:
+
+
+```
+qDebug() << "sendMessage result: " << g_cloudproxyclient->sendMessage("{\"app_key\": \"app_value1111\"}");
+```
\ No newline at end of file diff --git a/app_client_examples/telemetry-cloud-app/app/TelemetryWorker.cpp b/app_client_examples/telemetry-cloud-app/app/TelemetryWorker.cpp new file mode 100755 index 0000000..2fb2a15 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/TelemetryWorker.cpp @@ -0,0 +1,116 @@ +/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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 "TelemetryWorker.h"
+#include <QDebug>
+#include <nlohmann/json.hpp>
+#include <time.h>
+#include <stdlib.h>
+
+#include "cloudproxy-client/cloudproxyclient.h"
+
+/*
+left up: 56.327 43.981
+right up: 56.327 44.03
+right bottom: 56.3 44.03
+left bottom: 56.3 43.981
+
+start: 56.321674 44.006383
+top: 56.329702 44.021352
+
+diff -0.008028 -0.014969
+coeff lon/lat = 1,8646
+
+
+https://gps-coordinates.org/distance-between-coordinates.php
+*/
+
+
+static double drand(double min, double max)
+{
+ double f = (double)rand() / RAND_MAX;
+ return min + f * (max - min);
+}
+
+
+void TelemetryWorker::send()
+{
+ const double lat_top{56.329702};
+ const double lon_lat_coef{1.8646};
+ const double lat_step{0.001}, lon_step{lat_step*lon_lat_coef};
+
+ if (direction == 0 && lat >= lat_top)
+ {
+ direction = 1;
+ qDebug() << "Change direction to " << direction;
+ }
+ else if (direction == 1 && lat <= init_lat)
+ {
+ direction = 2;
+ qDebug() << "Change direction to " << direction;
+ }
+ else if (direction == 2 && lat <= (init_lat - (lat_top - init_lat)))
+ {
+ direction = 3;
+ qDebug() << "Change direction to " << direction;
+ }
+ else if (direction == 3 && lat >= init_lat)
+ {
+ direction = 0;
+ qDebug() << "Change direction to " << direction;
+ }
+
+
+ if (direction == 0)
+ {
+ lat += lat_step;
+ lon += lon_step;
+ }
+ else if (direction == 1)
+ {
+ lat -= lat_step;
+ lon += lon_step;
+ }
+ else if (direction == 2)
+ {
+ lat -= lat_step;
+ lon -= lon_step;
+ }
+ else if (direction == 3)
+ {
+ lat += lat_step;
+ lon -= lon_step;
+ }
+
+ nlohmann::json jmsg{
+ {"tele_ver", "1.0"},
+ {"lat", lat},
+ {"lon", lon},
+ {"alt", (size_t)(alt + drand(0, 10))},
+ {"speed", (size_t)(speed + drand(20, 30))},
+ {"ts", time(nullptr)}
+ };
+
+ {
+ //WARN: remove
+ const auto& msg{jmsg.dump()};
+ qDebug() << "TelemetryWorker: " << msg.c_str();
+ }
+
+ if (cloudproxyclient)
+ qDebug() << "TelemetryWorker: cloud sendMessage result: " << cloudproxyclient->sendMessage(jmsg.dump());
+
+}
diff --git a/app_client_examples/telemetry-cloud-app/app/TelemetryWorker.h b/app_client_examples/telemetry-cloud-app/app/TelemetryWorker.h new file mode 100755 index 0000000..11210cb --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/TelemetryWorker.h @@ -0,0 +1,48 @@ +/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <QTimer>
+
+class CloudProxyClient;
+
+class TelemetryWorker : public QTimer
+{
+ Q_OBJECT
+public:
+ TelemetryWorker(CloudProxyClient* cloudproxy, QObject* parent = nullptr)
+ : QTimer(parent),
+ cloudproxyclient{cloudproxy}
+ {
+ }
+
+public slots:
+ void send();
+
+private:
+ CloudProxyClient* cloudproxyclient{nullptr};
+
+ int direction{0};
+ const double init_lat{56.306};
+ const double init_lon{44.070};
+ double lat{init_lat};
+ double lon{init_lon};
+ double alt{300.0};
+ double speed{500.0};
+ uint32_t engine_rpm{3000};
+ int32_t engine_temp{97};
+};
diff --git a/app_client_examples/telemetry-cloud-app/app/app.pri b/app_client_examples/telemetry-cloud-app/app/app.pri new file mode 100755 index 0000000..6d710e4 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/app.pri @@ -0,0 +1,3 @@ +TEMPLATE = app +QMAKE_LFLAGS += "-Wl,--hash-style=gnu -Wl,--as-needed" +DESTDIR = $${OUT_PWD}/../package/root/bin diff --git a/app_client_examples/telemetry-cloud-app/app/app.pro b/app_client_examples/telemetry-cloud-app/app/app.pro new file mode 100755 index 0000000..351d89d --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/app.pro @@ -0,0 +1,35 @@ +TARGET = telemetrycloudapp +QT += core gui gui-private + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +DEFINES += QT_DEPRECATED_WARNINGS + +SOURCES = main.cpp \ + mainwindow.cpp \ + TelemetryWorker.cpp \ + cloudproxy-client/cloudproxyclient.cpp + +HEADERS += mainwindow.h \ + ui_mainwindow.h \ + TelemetryWorker.h \ + cloudproxy-client/cloudproxyclient.h \ + cloudproxy-client/hmi-debug.h + +CONFIG += link_pkgconfig +PKGCONFIG += libhomescreen qlibwindowmanager qtappfw-core + +CONFIG(release, debug|release) { + QMAKE_POST_LINK = $(STRIP) --strip-unneeded $(TARGET) +} + +LIBS += -ljson-c -lafbwsc -lsystemd + +RESOURCES += \ + images/images.qrc + +include(app.pri) + +DISTFILES += \ + images/AGL_HMI_Blue_Background_NoCar-01.png + diff --git a/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/cloudproxyclient.cpp b/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/cloudproxyclient.cpp new file mode 100755 index 0000000..0a30446 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/cloudproxyclient.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 <thread> +#include <algorithm> + +#include "cloudproxyclient.h" +#include "hmi-debug.h" + + +static const char API_name[] = "cloudproxy"; + +const std::vector<std::string> CloudProxyClient::m_api_list { + std::string("ping"), + std::string("createConnection"), + std::string("sendMessage"), + std::string("destroyConnection") + }; + +const std::vector<std::string> CloudProxyClient::m_event_list { + std::string("sendMessageConfirmation"), + std::string("receivedMessage") + }; + +static void event_loop_run(sd_event* loop) +{ + sd_event_loop(loop); + sd_event_unref(loop); +} + +static void on_hangup(void *closure, afb_wsj1 *wsj) +{ + HMI_DEBUG("cloudproxyclient", "called"); + (void)closure; + (void)wsj; +} + +static void on_call(void *closure, const char *api, const char *verb, afb_wsj1_msg *msg) +{ + HMI_ERROR("cloudproxyclient", "this method should not be called"); + (void)closure; + (void)api; + (void)verb; + (void)msg; +} + +static void on_event(void* closure, const char* event, afb_wsj1_msg *msg) +{ + HMI_DEBUG("cloudproxyclient", "event [%s]", (event ? event: "")); + (void)closure; + + static_cast<CloudProxyClient*>(closure)->on_event(nullptr, event, msg); +} + +static void on_reply(void *closure, afb_wsj1_msg *msg) +{ + HMI_DEBUG("cloudproxyclient", "called"); + (void)closure; + (void)msg; +} + + +CloudProxyClient::CloudProxyClient() +{ +} + +CloudProxyClient::~CloudProxyClient() +{ + if(m_websock) + { + afb_wsj1_unref(m_websock); + } + if(m_loop) + { + sd_event_exit(m_loop, 0); + } +} + +int CloudProxyClient::init(const int port, const std::string& token) +{ + int ret = 0; + if(port <= 0 && token.size() == 0) + { + HMI_ERROR("cloudproxyclient","port and token should be > 0, Initial port and token uses."); + return -1; + } + + { + m_loop = nullptr; + int ret = sd_event_new(&m_loop); + if(ret < 0) + { + HMI_ERROR("cloudproxyclient","Failed to create event loop"); + return -1; + } + + { + // enforce context to avoid initialization/goto error + std::thread th(event_loop_run, m_loop); + th.detach(); + } + + /* Initialize interface from websocket */ + m_itf.on_hangup = ::on_hangup; + m_itf.on_call = ::on_call; + m_itf.on_event = ::on_event; + + m_uri += "ws://localhost:" + std::to_string(port) + "/api?token=" + token; + + m_websock = afb_ws_client_connect_wsj1(m_loop, m_uri.c_str(), &m_itf, this); + if(!m_websock) + { + HMI_ERROR("cloudproxyclient","Failed to create websocket connection"); + return -1; + } + } + + HMI_DEBUG("cloudproxyclient", "Initialized"); + + return ret; +} + + +int CloudProxyClient::call(const std::string& verb, json_object* arg) +{ + int ret; + if(!m_websock) + { + return -1; + } + + if (verb.empty() || m_api_list.end() == std::find(m_api_list.begin(), m_api_list.end(), verb)) + { + HMI_ERROR("cloudproxyclient","verb [%s] doesn't exit", verb.c_str()); + return -1; + } + + ret = afb_wsj1_call_j(m_websock, API_name, verb.c_str(), arg, ::on_reply, this); + if (ret < 0) + { + HMI_ERROR("cloudproxyclient", "Failed to call verb:%s", verb.c_str()); + } + + return ret; +} + + +int CloudProxyClient::sendMessage(const std::string& data) +{ + if(!m_websock) + return -1; + + json_object* j_obj = json_object_new_object(); + json_object_object_add(j_obj, "data", json_object_new_string(data.c_str())); + + return this->call("sendMessage", j_obj); +} + +void CloudProxyClient::set_event_handler(enum EventType et, handler_func f) +{ + if (et > Event_Min && et < Event_Max) + { + switch (et) + { + case Event_SendMessageConfirmation: + this->subscribe(CloudProxyClient::m_event_list[0]); + break; + case Event_ReceivedMessage: + this->subscribe(CloudProxyClient::m_event_list[1]); + break; + default: + break; + } + + this->handlers[et] = std::move(f); + } +} + +int CloudProxyClient::subscribe(const std::string& event_name) +{ + if(!m_websock) + return -1; + + json_object* j_obj = json_object_new_object(); + json_object_object_add(j_obj, "event", json_object_new_string(event_name.c_str())); + + int ret = afb_wsj1_call_j(m_websock, API_name, "subscribe", j_obj, ::on_reply, this); + if (ret < 0) + HMI_ERROR("cloudproxyclient", "subscribe filed for '%s'", event_name.c_str()); + + return ret; +} + +int CloudProxyClient::unsubscribe(const std::string& event_name) +{ + if(!m_websock) + return -1; + + json_object* j_obj = json_object_new_object(); + json_object_object_add(j_obj, "event", json_object_new_string(event_name.c_str())); + + int ret = afb_wsj1_call_j(m_websock, API_name, "unsubscribe", j_obj, ::on_reply, this); + if (ret < 0) + HMI_ERROR("cloudproxyclient", "unsubscribe filed for '%s'", event_name.c_str()); + + return ret; +} + +void CloudProxyClient::on_event(void *closure, const char *event, afb_wsj1_msg *msg) +{ + HMI_DEBUG("cloudproxyclient", "event: (%s) msg: (%s).", event, afb_wsj1_msg_object_s(msg)); + (void) closure; + + if (strstr(event, API_name) == nullptr) + return; + + + json_object* ev_contents = afb_wsj1_msg_object_j(msg); + json_object *json_data; + if(!json_object_object_get_ex(ev_contents, "data", &json_data)) + { + HMI_ERROR("cloudproxyclient", "got ev_contents error."); + return; + } + + const char* event_type = nullptr; + json_object *json_event_type; + if(!json_object_object_get_ex(json_data, "type", &json_event_type) || + (event_type = json_object_get_string(json_event_type)) == nullptr) + { + HMI_ERROR("cloudproxyclient", "event_type is null."); + return; + } + + const std::string et{event_type}; + if (CloudProxyClient::m_event_list[0] == et) + { + auto i = this->handlers.find(Event_SendMessageConfirmation); + if (i != this->handlers.end()) + i->second(json_data); + } + else if (CloudProxyClient::m_event_list[1] == et) + { + auto i = this->handlers.find(Event_ReceivedMessage); + if (i != this->handlers.end()) + i->second(json_data); + } +} diff --git a/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/cloudproxyclient.h b/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/cloudproxyclient.h new file mode 100755 index 0000000..b333a14 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/cloudproxyclient.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020 MERA + * + * 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. + */ + +#pragma once + +#include <vector> +#include <map> +#include <string> +#include <functional> +#include <json-c/json.h> +#include <systemd/sd-event.h> + +extern "C" +{ +#include <afb/afb-wsj1.h> +#include <afb/afb-ws-client.h> +} + +class CloudProxyClient +{ +public: + using handler_func = std::function<void(json_object*)>; + + enum EventType { + Event_Min, + Event_SendMessageConfirmation = 1, + Event_ReceivedMessage, + Event_Max + }; + + + CloudProxyClient(); + ~CloudProxyClient(); + + CloudProxyClient(const CloudProxyClient &) = delete; + CloudProxyClient &operator=(const CloudProxyClient &) = delete; + + int init(const int port, const std::string& token); + + int sendMessage(const std::string& data); + + void set_event_handler(enum EventType et, handler_func f); + + void on_event(void *closure, const char *event, struct afb_wsj1_msg *msg); + + int subscribe(const std::string& event_name); + int unsubscribe(const std::string& event_name); + +private: + int call(const std::string& verb, struct json_object* arg); + +private: + static const std::vector<std::string> m_api_list; + static const std::vector<std::string> m_event_list; + + std::map<EventType, handler_func> handlers; + struct afb_wsj1_itf m_itf; + struct afb_wsj1* m_websock{nullptr}; + sd_event* m_loop{nullptr}; + std::string m_uri; +}; diff --git a/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/hmi-debug.h b/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/hmi-debug.h new file mode 100755 index 0000000..674f5df --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/cloudproxy-client/hmi-debug.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 TOYOTA MOTOR CORPORATION + * + * 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 __HMI_DEBUG_H__ +#define __HMI_DEBUG_H__ + +#include <time.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> + +enum LOG_LEVEL{ + LOG_LEVEL_NONE = 0, + LOG_LEVEL_ERROR, + LOG_LEVEL_WARNING, + LOG_LEVEL_NOTICE, + LOG_LEVEL_INFO, + LOG_LEVEL_DEBUG, + LOG_LEVEL_MAX = LOG_LEVEL_DEBUG +}; + +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + +#define HMI_ERROR(prefix, args,...) _HMI_LOG(LOG_LEVEL_ERROR, __FILENAME__, __FUNCTION__, __LINE__, prefix, args, ##__VA_ARGS__) +#define HMI_WARNING(prefix, args,...) _HMI_LOG(LOG_LEVEL_WARNING, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_NOTICE(prefix, args,...) _HMI_LOG(LOG_LEVEL_NOTICE, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_INFO(prefix, args,...) _HMI_LOG(LOG_LEVEL_INFO, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_DEBUG(prefix, args,...) _HMI_LOG(LOG_LEVEL_DEBUG, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) + +static char ERROR_FLAG[6][20] = {"NONE", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"}; + +static void _HMI_LOG(enum LOG_LEVEL level, const char* file, const char* func, const int line, const char* prefix, const char* log, ...) +{ + const int log_level = (getenv("USE_HMI_DEBUG") == NULL)?LOG_LEVEL_ERROR:atoi(getenv("USE_HMI_DEBUG")); + if(log_level < level) + { + return; + } + + char *message; + struct timespec tp; + unsigned int time; + + clock_gettime(CLOCK_REALTIME, &tp); + time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000); + + va_list args; + va_start(args, log); + if (log == NULL || vasprintf(&message, log, args) < 0) + message = NULL; + fprintf(stderr, "[%10.3f] [%s %s] [%s, %s(), Line:%d] >>> %s \n", time / 1000.0, prefix, ERROR_FLAG[level], file, func, line, message); + va_end(args); + free(message); +} + +#endif //__HMI_DEBUG_H__ diff --git a/app_client_examples/telemetry-cloud-app/app/images/AGL_HMI_Blue_Background_NoCar-01.png b/app_client_examples/telemetry-cloud-app/app/images/AGL_HMI_Blue_Background_NoCar-01.png Binary files differnew file mode 100755 index 0000000..a76b140 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/images/AGL_HMI_Blue_Background_NoCar-01.png diff --git a/app_client_examples/telemetry-cloud-app/app/images/images.qrc b/app_client_examples/telemetry-cloud-app/app/images/images.qrc new file mode 100755 index 0000000..78d9b77 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/images/images.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/images"> + <file>AGL_HMI_Blue_Background_NoCar-01.png</file> + </qresource> +</RCC> diff --git a/app_client_examples/telemetry-cloud-app/app/main.cpp b/app_client_examples/telemetry-cloud-app/app/main.cpp new file mode 100755 index 0000000..e0551fe --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/main.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 <QtCore/QUrlQuery> +#include <QtGui/QGuiApplication> +#include <libhomescreen.hpp> +#include <qlibwindowmanager.h> +#include <QtCore/QCommandLineParser> +#include <QScopedPointer> + +#include "mainwindow.h" +#include <QApplication> +#include <QDebug> + +#include "cloudproxy-client/cloudproxyclient.h" + + +CloudProxyClient* g_cloudproxyclient{nullptr}; + + +int main(int argc, char *argv[]) +{ + QString graphic_role = QString("telemetrycloud"); + + QApplication app(argc, argv); + QScopedPointer<MainWindow> window; + + QCommandLineParser parser; + parser.addPositionalArgument("port", app.translate("main", "port for binding")); + parser.addPositionalArgument("secret", app.translate("main", "secret for binding")); + parser.addHelpOption(); + parser.addVersionOption(); + parser.process(app); + QStringList positionalArguments = parser.positionalArguments(); + + if (positionalArguments.length() == 2) + { + int port = positionalArguments.takeFirst().toInt(); + QString secret = positionalArguments.takeFirst(); + QUrl bindingAddress; + bindingAddress.setScheme(QStringLiteral("ws")); + bindingAddress.setHost(QStringLiteral("localhost")); + bindingAddress.setPort(port); + bindingAddress.setPath(QStringLiteral("/api")); + QUrlQuery query; + query.addQueryItem(QStringLiteral("token"), secret); + bindingAddress.setQuery(query); + + std::string token = secret.toStdString(); + LibHomeScreen* hs = new LibHomeScreen(); + QLibWindowmanager* qwm = new QLibWindowmanager(); + + // WindowManager + if(qwm->init(port,secret) != 0){ + exit(EXIT_FAILURE); + } + AGLScreenInfo screenInfo(qwm->get_scale_factor()); + window.reset(new MainWindow(screenInfo.scale_factor())); + + // Request a surface as described in layers.json windowmanager’s file + if (qwm->requestSurface(graphic_role) != 0) { + exit(EXIT_FAILURE); + } + // Create an event callback against an event type. Here a lambda is called when SyncDraw event occurs + qwm->set_event_handler(QLibWindowmanager::Event_SyncDraw, [qwm, &graphic_role](json_object *object) { + (void)object; + qDebug() << "Surface got syncDraw!"; + qwm->endDraw(graphic_role); + }); + + // HomeScreen + hs->init(port, token.c_str()); + // Set the event handler for Event_ShowWindow which will activate the surface for windowmanager + hs->set_event_handler(LibHomeScreen::Event_ShowWindow, [qwm, &graphic_role](json_object *object){ + (void)object; + qDebug("Surface %s got showWindow\n", graphic_role.toStdString().c_str()); + qwm->activateWindow(graphic_role); + }); + + g_cloudproxyclient = new CloudProxyClient(); + g_cloudproxyclient->init(port, token.c_str()); + + g_cloudproxyclient->set_event_handler(CloudProxyClient::Event_ReceivedMessage, [&window](json_object* object){ + qDebug("CloudProxyClient::Event_ReceivedMessage: object ptr %p", object); + + const char* str = object ? json_object_to_json_string_ext(object, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) : "<obj_null>"; + if (!str) + str = "<empty>"; + qDebug("Event_SendMessageConfirmation: %s", str); + + json_object *event_data; + const char* data_str{nullptr}; + if(!json_object_object_get_ex(object, "data", &event_data) || + (data_str = json_object_get_string(event_data)) == nullptr) + { + qDebug("Can't read event data"); + return; + } + + window->updateState(QString(data_str)); + qDebug("Application received data: %s", data_str); + }); + + window->show(); + qwm->slotActivateWindow(); + } + + return app.exec(); +} diff --git a/app_client_examples/telemetry-cloud-app/app/mainwindow.cpp b/app_client_examples/telemetry-cloud-app/app/mainwindow.cpp new file mode 100755 index 0000000..aecabc0 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/mainwindow.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 "mainwindow.h" +#include "ui_mainwindow.h" +#include <QDebug> + +#include <unistd.h> +#include <sys/stat.h> +#include <nlohmann/json.hpp> + + +#include "cloudproxy-client/cloudproxyclient.h" +extern CloudProxyClient* g_cloudproxyclient; + + +MainWindow::MainWindow(double scale_factor, QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(scale_factor, this); + + //AGL style + setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); + this->setStyleSheet( + "background-image:url(:/images/AGL_HMI_Blue_Background_NoCar-01.png) 0 0 0 0 stretch stretch; background: transparent;" ); +} + +void MainWindow::updateState(QVariant recvMsg) +{ + if (!recvMsg.isValid()) + return; + + // expected format: {"message": "bla-bla"} + std::string cloud_msg; + try + { + const std::string& msg = recvMsg.toString().toStdString(); + if (msg.empty()) + { + qDebug() << "Received message is empty"; + } + else + { + const nlohmann::json& jmsg = nlohmann::json::parse(msg); + cloud_msg = jmsg["message"].get<std::string>(); + } + } + catch (std::exception& ex) + { + qDebug() << "Can't parse incomming message: " << ex.what(); + } + + QString str = QString::asprintf("Cloud info: %s", cloud_msg.c_str()); + ui->stateLabel->setText(str); +} + +void MainWindow::startMsgButtonClick() +{ + qDebug() << "MainWindow::startMsgButtonClick()"; + + if (started) + ui->startMsgButton->setText("Start"); + else + ui->startMsgButton->setText("Stop"); + started = !started; + + if (worker) + { + worker->stop(); + worker.reset(); + qDebug() << "Worker destroyed"; + } + + if (started) + { + worker.reset(new TelemetryWorker(g_cloudproxyclient, this)); + connect(worker.get(), SIGNAL(timeout()), worker.get(), SLOT(send())); + worker->start(1001); + } +} + + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/app_client_examples/telemetry-cloud-app/app/mainwindow.h b/app_client_examples/telemetry-cloud-app/app/mainwindow.h new file mode 100755 index 0000000..ce21845 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/mainwindow.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QVariant> +#include <QScopedPointer> +#include <memory> + +#include "TelemetryWorker.h" + + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(double scale_factor, QWidget *parent = 0); + ~MainWindow(); + +public slots: + void updateState(QVariant recvMsg); + +private slots: + void startMsgButtonClick(); + +private: + Ui::MainWindow *ui; + bool started{false}; + QScopedPointer<TelemetryWorker> worker; +}; + +#endif // MAINWINDOW_H diff --git a/app_client_examples/telemetry-cloud-app/app/ui_mainwindow.h b/app_client_examples/telemetry-cloud-app/app/ui_mainwindow.h new file mode 100755 index 0000000..5dbcbd5 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/app/ui_mainwindow.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 UI_MAINWINDOW_H +#define UI_MAINWINDOW_H + +#include <QtCore/QVariant> +#include <QtWidgets/QApplication> +#include <QtWidgets/QLabel> +#include <QtWidgets/QMainWindow> +#include <QtWidgets/QPushButton> +#include <QtWidgets/QLineEdit> +#include <QtWidgets/QWidget> +#include <QtWidgets/QFrame> +#include <QtWidgets/QGroupBox> +#include <QtWidgets/QRadioButton> +#include <QtWidgets/QComboBox> + +QT_BEGIN_NAMESPACE + +class Ui_MainWindow +{ +public: + QWidget *centralWidget{nullptr}; + QPushButton *startMsgButton{nullptr}; + QLabel *stateLabel{nullptr}; + + void setupUi(double scale_factor, QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QString::fromUtf8("MainWindow")); + MainWindow->resize(1080*scale_factor, 1400*scale_factor); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QString::fromUtf8("centralWidget")); + + //QString style("color: white; font-size: 24px"); + QString style("color: white"); + + startMsgButton = new QPushButton(centralWidget); + startMsgButton->setObjectName(QString::fromUtf8("startMsgButton")); + startMsgButton->setGeometry(QRect((540-242/2)*scale_factor, 20*scale_factor, 242*scale_factor, 64*scale_factor)); + startMsgButton->setStyleSheet(style); + + stateLabel = new QLabel(centralWidget); + stateLabel->setObjectName(QString::fromUtf8("label")); + stateLabel->setGeometry(QRect(40*scale_factor, 180*scale_factor, 1000*scale_factor, 420*scale_factor)); + stateLabel->setWordWrap(true); + stateLabel->setAlignment(Qt::AlignTop); + stateLabel->setStyleSheet(style); + + MainWindow->setCentralWidget(centralWidget); + + QObject::connect(startMsgButton, SIGNAL(clicked()), MainWindow, SLOT(startMsgButtonClick())); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", nullptr)); + startMsgButton->setText(QApplication::translate("MainWindow", "Start", nullptr)); + } // retranslateUi +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_MAINWINDOW_H diff --git a/app_client_examples/telemetry-cloud-app/autobuild/agl/autobuild b/app_client_examples/telemetry-cloud-app/autobuild/agl/autobuild new file mode 100755 index 0000000..bbbc13e --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/autobuild/agl/autobuild @@ -0,0 +1,110 @@ +#!/usr/bin/make -f +# Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group +# Author "Romain Forlot" <romain.forlot@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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) + +# Build directories +# Note that the debug directory is defined in relation to the release +# directory (BUILD_DIR), this needs to be kept in mind if over-riding +# it and building that widget types, the specific widget type variable +# (e.g. BUILD_DIR_DEBUG) may also need to be specified to yield the +# desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) + +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release builds +BUILD_TYPE = release + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-all +.PHONY: configure configure-release configure-debug +.PHONY: build build-release build-debug build-all +.PHONY: package package-release package-debug package-all + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- help" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in $(DEST) directory" + @echo "" + @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = debug + +clean-release clean-debug: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ + fi + +clean: clean-release + +clean-all: clean-release clean-debug + +distclean: clean-all + +configure-release configure-debug: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && qmake CONFIG+=$(BUILD_TYPE) $(CONFIGURE_ARGS) $(ROOT_DIR)); \ + fi + +configure: configure-release + +build-release build-debug: build-%: configure-% + @$(MAKE) -C $(OUTPUT_DIR) $(BUILD_ARGS) all + +build: build-release + +build-all: build-release build-debug + +package-release package-debug: package-%: build-% + @cp $(OUTPUT_DIR)/package/*.wgt $(OUTPUT_DIR)/ + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + + +package: package-release + +package-all: package-release package-debug + +install: build + @$(MAKE) -C $(BUILD_DIR) $(INSTALL_ARGS) install diff --git a/app_client_examples/telemetry-cloud-app/autobuild/linux/autobuild b/app_client_examples/telemetry-cloud-app/autobuild/linux/autobuild new file mode 100755 index 0000000..bbbc13e --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/autobuild/linux/autobuild @@ -0,0 +1,110 @@ +#!/usr/bin/make -f +# Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group +# Author "Romain Forlot" <romain.forlot@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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) + +# Build directories +# Note that the debug directory is defined in relation to the release +# directory (BUILD_DIR), this needs to be kept in mind if over-riding +# it and building that widget types, the specific widget type variable +# (e.g. BUILD_DIR_DEBUG) may also need to be specified to yield the +# desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) + +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release builds +BUILD_TYPE = release + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-all +.PHONY: configure configure-release configure-debug +.PHONY: build build-release build-debug build-all +.PHONY: package package-release package-debug package-all + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- help" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in $(DEST) directory" + @echo "" + @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = debug + +clean-release clean-debug: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ + fi + +clean: clean-release + +clean-all: clean-release clean-debug + +distclean: clean-all + +configure-release configure-debug: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && qmake CONFIG+=$(BUILD_TYPE) $(CONFIGURE_ARGS) $(ROOT_DIR)); \ + fi + +configure: configure-release + +build-release build-debug: build-%: configure-% + @$(MAKE) -C $(OUTPUT_DIR) $(BUILD_ARGS) all + +build: build-release + +build-all: build-release build-debug + +package-release package-debug: package-%: build-% + @cp $(OUTPUT_DIR)/package/*.wgt $(OUTPUT_DIR)/ + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + + +package: package-release + +package-all: package-release package-debug + +install: build + @$(MAKE) -C $(BUILD_DIR) $(INSTALL_ARGS) install diff --git a/app_client_examples/telemetry-cloud-app/package/config.xml b/app_client_examples/telemetry-cloud-app/package/config.xml new file mode 100755 index 0000000..a49b1c3 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/package/config.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<widget xmlns="http://www.w3.org/ns/widgets" id="telemetrycloudapp" version="0.1"> + <name>CloudTelemetry</name> + <icon src="icon.svg"/> + <content src="bin/telemetrycloudapp" type="application/vnd.agl.native"/> + <description>Test Cloud App</description> + <author>Mera</author> + <license>APL 2.0</license> + <feature name="urn:AGL:widget:required-api"> + <param name="windowmanager" value="ws"/> + <param name="homescreen" value="ws"/> + <param name="cloudproxy" value="ws"/> + </feature> + <feature name="urn:AGL:widget:required-permission"> + <param name="urn:AGL:permission::public:no-htdocs" value="required"/> + <param name="urn:AGL:permission::public:display" value="required" /> + </feature> +</widget> diff --git a/app_client_examples/telemetry-cloud-app/package/icon.svg b/app_client_examples/telemetry-cloud-app/package/icon.svg new file mode 100755 index 0000000..b9b8e67 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/package/icon.svg @@ -0,0 +1,266 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:i="&#38;ns_ai;" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + x="0px" + y="0px" + viewBox="0 0 320 320" + style="enable-background:new 0 0 320 320;" + xml:space="preserve" + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="icon.svg"><metadata + id="metadata1292"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs1290" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1464" + id="namedview1288" + showgrid="false" + inkscape:zoom="4.17193" + inkscape:cx="183.76952" + inkscape:cy="191.17869" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /><style + type="text/css" + id="style4"> + .st0{display:none;} + .st1{display:inline;} + .st2{opacity:0.4;fill:url(#SVGID_1_);} + .st3{fill:url(#SVGID_2_);} + .st4{fill:#FFFFFF;} + .st5{font-family:'Roboto-Regular';} + .st6{font-size:25px;} + .st7{letter-spacing:6;} + .st8{fill:url(#SVGID_3_);} + .st9{fill:url(#SVGID_4_);} + .st10{fill:url(#SVGID_5_);} + .st11{fill:url(#SVGID_6_);} + .st12{fill:url(#SVGID_7_);} + .st13{fill:url(#SVGID_8_);} + .st14{fill:url(#SVGID_9_);} + .st15{fill:url(#SVGID_10_);} + .st16{fill:url(#SVGID_11_);} + .st17{fill:url(#SVGID_12_);} + .st18{fill:url(#SVGID_13_);} + .st19{fill:url(#SVGID_14_);} + .st20{fill:url(#SVGID_15_);} + .st21{fill:url(#SVGID_16_);} + .st22{fill:url(#SVGID_17_);} + .st23{fill:url(#SVGID_18_);} + .st24{opacity:0.29;} + .st25{fill:url(#SVGID_19_);} + .st26{fill:url(#SVGID_20_);} + .st27{fill:url(#SVGID_21_);} + .st28{fill:url(#SVGID_22_);} + .st29{fill:url(#SVGID_23_);} + .st30{fill:url(#SVGID_24_);} + .st31{fill:url(#SVGID_25_);} + .st32{fill:url(#SVGID_26_);} + .st33{fill:url(#SVGID_27_);} + .st34{fill:url(#SVGID_28_);} + .st35{fill:url(#SVGID_29_);} + .st36{fill:url(#SVGID_30_);} + .st37{fill:url(#SVGID_31_);} + .st38{fill:url(#SVGID_32_);} + .st39{fill:url(#SVGID_33_);} + .st40{fill:url(#SVGID_34_);} + .st41{fill:url(#SVGID_35_);} + .st42{fill:url(#SVGID_36_);} + .st43{opacity:0.4;fill:url(#SVGID_37_);} + .st44{fill:url(#SVGID_38_);} + .st45{fill:url(#SVGID_39_);} + .st46{fill:url(#SVGID_40_);} + .st47{fill:url(#SVGID_41_);} + .st48{fill:url(#SVGID_42_);} + .st49{fill:url(#SVGID_43_);} + .st50{fill:url(#SVGID_44_);} + .st51{display:inline;opacity:0.29;} + .st52{display:inline;fill:url(#SVGID_45_);} + .st53{display:inline;fill:url(#SVGID_46_);} + .st54{display:inline;fill:#FFFFFF;} + .st55{display:inline;fill:url(#SVGID_47_);} + .st56{display:inline;fill:url(#SVGID_48_);} + .st57{display:inline;fill:url(#SVGID_49_);} + .st58{display:inline;fill:url(#SVGID_50_);} + .st59{display:inline;fill:url(#SVGID_51_);} + .st60{display:inline;fill:url(#SVGID_52_);} + .st61{opacity:0.4;fill:url(#SVGID_53_);} + .st62{fill:url(#SVGID_54_);} + .st63{fill:url(#SVGID_55_);} + .st64{fill:url(#SVGID_56_);} + .st65{fill:url(#SVGID_57_);} + .st66{fill:url(#SVGID_58_);} + .st67{opacity:0.4;fill:url(#SVGID_59_);} + .st68{fill:url(#SVGID_60_);} + .st69{fill:url(#SVGID_61_);} + .st70{fill:url(#SVGID_62_);} + .st71{fill:url(#SVGID_63_);} + .st72{fill:url(#SVGID_64_);} + .st73{fill:url(#SVGID_65_);} + .st74{fill:url(#SVGID_66_);} + .st75{fill:url(#SVGID_67_);} + .st76{fill:url(#SVGID_68_);} + .st77{fill:url(#SVGID_69_);} + .st78{fill:url(#SVGID_70_);} + .st79{fill:url(#SVGID_71_);} + .st80{fill:url(#SVGID_72_);} + .st81{fill:url(#SVGID_73_);} + .st82{fill:url(#SVGID_74_);} + .st83{fill:url(#SVGID_75_);} + .st84{fill:url(#SVGID_76_);} + .st85{fill:url(#SVGID_77_);} + .st86{fill:url(#SVGID_78_);} + .st87{fill:url(#SVGID_79_);} + .st88{fill:url(#SVGID_80_);} + .st89{fill:url(#SVGID_81_);} + .st90{fill:url(#SVGID_82_);} + .st91{fill:url(#SVGID_83_);} + .st92{fill:url(#SVGID_84_);} + .st93{fill:url(#SVGID_85_);} + .st94{fill:url(#SVGID_86_);} + .st95{opacity:0.4;fill:url(#SVGID_87_);} + .st96{fill:url(#SVGID_88_);} + .st97{fill:url(#SVGID_89_);} + .st98{fill:url(#SVGID_90_);} + .st99{fill:url(#SVGID_91_);} + .st100{fill:url(#SVGID_92_);} + .st101{fill:url(#SVGID_93_);} + .st102{fill:url(#SVGID_94_);} + .st103{opacity:0.4;fill:url(#SVGID_95_);} + .st104{fill:url(#SVGID_96_);} + .st105{fill:url(#SVGID_97_);} + .st106{fill:url(#SVGID_98_);} + .st107{fill:url(#SVGID_99_);} + .st108{fill:url(#SVGID_100_);} + .st109{fill:url(#SVGID_101_);} + .st110{display:inline;fill:url(#SVGID_102_);} + .st111{display:inline;fill:url(#SVGID_103_);} + .st112{fill:url(#SVGID_104_);} + .st113{fill:url(#SVGID_105_);} + .st114{fill:url(#SVGID_106_);} + .st115{fill:url(#SVGID_107_);} + .st116{fill:url(#SVGID_108_);} + .st117{opacity:0.4;fill:url(#SVGID_109_);} + .st118{fill:url(#SVGID_110_);} + .st119{fill:url(#SVGID_111_);} + .st120{fill:url(#SVGID_112_);} + .st121{fill:url(#SVGID_113_);} + .st122{fill:url(#SVGID_114_);} + .st123{opacity:0.4;fill:url(#SVGID_115_);} + .st124{fill:url(#SVGID_116_);} + .st125{fill:url(#SVGID_117_);} + .st126{fill:url(#SVGID_118_);} + .st127{display:inline;fill:url(#SVGID_119_);} + .st128{display:inline;fill:url(#SVGID_120_);} + .st129{fill:url(#SVGID_121_);} + .st130{fill:url(#SVGID_122_);} +</style><switch + id="switch6"><g + i:extraneous="self" + id="g8"><g + id="Multimedia_Inactive_copy"><circle + class="st24" + cx="159.7" + cy="133.4" + r="101.9" + id="circle884" /><linearGradient + id="SVGID_91_" + gradientUnits="userSpaceOnUse" + x1="115.9317" + y1="254.1836" + x2="256.3852" + y2="-133.5267"><stop + offset="0" + style="stop-color:#8BC53F" + id="stop887" /><stop + offset="2.015080e-02" + style="stop-color:#7CCB56;stop-opacity:0.9678" + id="stop889" /><stop + offset="6.089833e-02" + style="stop-color:#62D67D;stop-opacity:0.9028" + id="stop891" /><stop + offset="0.1057" + style="stop-color:#4BDFA0;stop-opacity:0.8312" + id="stop893" /><stop + offset="0.1543" + style="stop-color:#38E7BE;stop-opacity:0.7537" + id="stop895" /><stop + offset="0.2077" + style="stop-color:#28EED6;stop-opacity:0.6684" + id="stop897" /><stop + offset="0.2681" + style="stop-color:#1CF3E8;stop-opacity:0.572" + id="stop899" /><stop + offset="0.3394" + style="stop-color:#13F6F5;stop-opacity:0.4581" + id="stop901" /><stop + offset="0.4323" + style="stop-color:#0EF8FD;stop-opacity:0.3098" + id="stop903" /><stop + offset="0.6264" + style="stop-color:#0DF9FF;stop-opacity:0" + id="stop905" /></linearGradient><circle + class="st99" + cx="159.7" + cy="133.4" + r="101.9" + id="circle907" /><linearGradient + id="SVGID_92_" + gradientUnits="userSpaceOnUse" + x1="4.0481" + y1="287.9492" + x2="320.4859" + y2="-15.4029" + gradientTransform="matrix(1 5.464556e-03 -5.464556e-03 1 -2.0192 -3.0212)"><stop + offset="0" + style="stop-color:#59FF7F" + id="stop910" /><stop + offset="1" + style="stop-color:#6BFBFF" + id="stop912" /></linearGradient><path + class="st100" + d="M160,238.8c-0.2,0-0.4,0-0.6,0c-58-0.3-104.9-47.7-104.6-105.7C55.2,75.3,102.3,28.5,160,28.5 c0.2,0,0.4,0,0.6,0c58,0.3,104.9,47.7,104.6,105.7l0,0C264.8,192,217.7,238.8,160,238.8z M160,32.2 c-55.7,0-101.2,45.2-101.5,100.9c-0.3,55.9,45,101.7,100.9,102c0.2,0,0.4,0,0.6,0c55.7,0,101.2-45.2,101.5-100.9 c0.3-55.9-45-101.7-100.9-102C160.4,32.2,160.2,32.2,160,32.2z" + id="path914" /><g + id="g916"><text + transform="matrix(1 0 0 1 53.5841 284.7119)" + class="st4 st5 st6 st7" + id="text918">VIDEOPLAYER +</text> +<linearGradient + id="SVGID_94_" + gradientUnits="userSpaceOnUse" + x1="145.3286" + y1="203.8951" + x2="191.6285" + y2="70.3637"><stop + offset="0" + style="stop-color:#59FF7F" + id="stop928" /><stop + offset="1" + style="stop-color:#6BFBFF" + id="stop930" /></linearGradient><polygon + class="st102" + points="125,75 125,200 225,137.5 " + id="polygon932" + style="fill:url(#SVGID_94_)" /></g></g></g></switch></svg>
\ No newline at end of file diff --git a/app_client_examples/telemetry-cloud-app/package/package.pro b/app_client_examples/telemetry-cloud-app/package/package.pro new file mode 100755 index 0000000..7a138a3 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/package/package.pro @@ -0,0 +1,28 @@ + +DISTFILES = icon.svg config.xml + +copy_icon.target = $$OUT_PWD/root/icon.svg +copy_icon.depends = $$_PRO_FILE_PWD_/icon.svg +copy_icon.commands = $(COPY_FILE) \"$$replace(copy_icon.depends, /, $$QMAKE_DIR_SEP)\" \"$$replace(copy_icon.target, /, $$QMAKE_DIR_SEP)\" +QMAKE_EXTRA_TARGETS += copy_icon +PRE_TARGETDEPS += $$copy_icon.target + +copy_config.target = $$OUT_PWD/root/config.xml +copy_config.depends = $$_PRO_FILE_PWD_/config.xml +copy_config.commands = $(COPY_FILE) \"$$replace(copy_config.depends, /, $$QMAKE_DIR_SEP)\" \"$$replace(copy_config.target, /, $$QMAKE_DIR_SEP)\" +QMAKE_EXTRA_TARGETS += copy_config +PRE_TARGETDEPS += $$copy_config.target + +WGT_TYPE = +CONFIG(debug, debug|release) { + WGT_TYPE = -debug +} + +wgt.target = package +wgt.commands = wgtpkg-pack -f -o telemetrycloudapp$${WGT_TYPE}.wgt root + +QMAKE_EXTRA_TARGETS += wgt + +deploy.files = telemetrycloudapp.wgt +deploy.path = /usr/AGL/apps/autoinstall +INSTALLS += deploy diff --git a/app_client_examples/telemetry-cloud-app/telemetrycloudapp.pro b/app_client_examples/telemetry-cloud-app/telemetrycloudapp.pro new file mode 100755 index 0000000..579a952 --- /dev/null +++ b/app_client_examples/telemetry-cloud-app/telemetrycloudapp.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = app package +package.depends += app diff --git a/app_client_examples/test-cloud-app/app/app.pri b/app_client_examples/test-cloud-app/app/app.pri new file mode 100755 index 0000000..6d710e4 --- /dev/null +++ b/app_client_examples/test-cloud-app/app/app.pri @@ -0,0 +1,3 @@ +TEMPLATE = app +QMAKE_LFLAGS += "-Wl,--hash-style=gnu -Wl,--as-needed" +DESTDIR = $${OUT_PWD}/../package/root/bin diff --git a/app_client_examples/test-cloud-app/app/app.pro b/app_client_examples/test-cloud-app/app/app.pro new file mode 100755 index 0000000..4e8ae9a --- /dev/null +++ b/app_client_examples/test-cloud-app/app/app.pro @@ -0,0 +1,34 @@ +TARGET = testcloudapp +QT += core gui gui-private + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += APP_INSTANCE_NUM='\\"1\\"' + +SOURCES = main.cpp \ + mainwindow.cpp \ + cloudproxy-client/cloudproxyclient.cpp + +HEADERS += mainwindow.h \ + ui_mainwindow.h \ + cloudproxy-client/cloudproxyclient.h \ + cloudproxy-client/hmi-debug.h \ + +CONFIG += link_pkgconfig +PKGCONFIG += libhomescreen qlibwindowmanager qtappfw-core + +CONFIG(release, debug|release) { + QMAKE_POST_LINK = $(STRIP) --strip-unneeded $(TARGET) +} + +LIBS += -ljson-c -lafbwsc -lsystemd + +RESOURCES += \ + images/images.qrc + +include(app.pri) + +DISTFILES += \ + images/AGL_HMI_Blue_Background_NoCar-01.png + diff --git a/app_client_examples/test-cloud-app/app/cloudproxy-client/cloudproxyclient.cpp b/app_client_examples/test-cloud-app/app/cloudproxy-client/cloudproxyclient.cpp new file mode 100755 index 0000000..cfbf943 --- /dev/null +++ b/app_client_examples/test-cloud-app/app/cloudproxy-client/cloudproxyclient.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 <thread> +#include <algorithm> + +#include "cloudproxyclient.h" +#include "hmi-debug.h" + + +static const char API_name[] = "cloudproxy"; + +const std::vector<std::string> CloudProxyClient::m_api_list { + std::string("ping"), + std::string("createConnection"), + std::string("sendMessage"), + std::string("destroyConnection") + }; + +const std::vector<std::string> CloudProxyClient::m_event_list { + std::string("sendMessageConfirmation"), + std::string("receivedMessage") + }; + +static void event_loop_run(sd_event* loop) +{ + sd_event_loop(loop); + sd_event_unref(loop); +} + +static void on_hangup(void *closure, afb_wsj1 *wsj) +{ + HMI_DEBUG("cloudproxyclient", "called"); + (void)closure; + (void)wsj; +} + +static void on_call(void *closure, const char *api, const char *verb, afb_wsj1_msg *msg) +{ + HMI_ERROR("cloudproxyclient", "this method should not be called"); + (void)closure; + (void)api; + (void)verb; + (void)msg; +} + +static void on_event(void* closure, const char* event, afb_wsj1_msg *msg) +{ + HMI_DEBUG("cloudproxyclient", "event [%s]", (event ? event: "")); + (void)closure; + + static_cast<CloudProxyClient*>(closure)->on_event(nullptr, event, msg); +} + +static void on_reply(void *closure, afb_wsj1_msg *msg) +{ + HMI_DEBUG("cloudproxyclient", "called"); + (void)closure; + (void)msg; +} + + +CloudProxyClient::CloudProxyClient() +{ +} + +CloudProxyClient::~CloudProxyClient() +{ + if(m_websock) + { + afb_wsj1_unref(m_websock); + } + if(m_loop) + { + sd_event_exit(m_loop, 0); + } +} + +int CloudProxyClient::init(const int port, const std::string& token) +{ + int ret = 0; + if(port <= 0 && token.size() == 0) + { + HMI_ERROR("cloudproxyclient","port and token should be > 0, Initial port and token uses."); + return -1; + } + + { + m_loop = nullptr; + int ret = sd_event_new(&m_loop); + if(ret < 0) + { + HMI_ERROR("cloudproxyclient","Failed to create event loop"); + return -1; + } + + { + // enforce context to avoid initialization/goto error + std::thread th(event_loop_run, m_loop); + th.detach(); + } + + /* Initialize interface from websocket */ + m_itf.on_hangup = ::on_hangup; + m_itf.on_call = ::on_call; + m_itf.on_event = ::on_event; + + m_uri += "ws://localhost:" + std::to_string(port) + "/api?token=" + token; + + m_websock = afb_ws_client_connect_wsj1(m_loop, m_uri.c_str(), &m_itf, this); + if(!m_websock) + { + HMI_ERROR("cloudproxyclient","Failed to create websocket connection"); + return -1; + } + } + + HMI_DEBUG("cloudproxyclient", "Initialized"); + + return ret; +} + + +int CloudProxyClient::call(const std::string& verb, json_object* arg) +{ + int ret; + if(!m_websock) + { + return -1; + } + + if (verb.empty() || m_api_list.end() == std::find(m_api_list.begin(), m_api_list.end(), verb)) + { + HMI_ERROR("cloudproxyclient","verb [%s] doesn't exit", verb.c_str()); + return -1; + } + + ret = afb_wsj1_call_j(m_websock, API_name, verb.c_str(), arg, ::on_reply, this); + if (ret < 0) + { + HMI_ERROR("cloudproxyclient", "Failed to call verb:%s", verb.c_str()); + } + + return ret; +} + + +int CloudProxyClient::sendMessage(const std::string& data) +{ + if(!m_websock) + return -1; + + json_object* j_obj = json_object_new_object(); + json_object_object_add(j_obj, "data", json_object_new_string(data.c_str())); + + return this->call("sendMessage", j_obj); +} + +void CloudProxyClient::set_event_handler(enum EventType et, handler_func f) +{ + if (et > Event_Min && et < Event_Max) + { + switch (et) + { + case Event_SendMessageConfirmation: + this->subscribe(CloudProxyClient::m_event_list[0]); + break; + case Event_ReceivedMessage: + this->subscribe(CloudProxyClient::m_event_list[1]); + break; + default: + break; + } + + this->handlers[et] = std::move(f); + } +} + +int CloudProxyClient::subscribe(const std::string& event_name) +{ + if(!m_websock) + return -1; + + json_object* j_obj = json_object_new_object(); + json_object_object_add(j_obj, "event", json_object_new_string(event_name.c_str())); + + int ret = afb_wsj1_call_j(m_websock, API_name, "subscribe", j_obj, ::on_reply, this); + if (ret < 0) + HMI_ERROR("cloudproxyclient", "subscribe filed for '%s'", event_name.c_str()); + + return ret; +} + +int CloudProxyClient::unsubscribe(const std::string& event_name) +{ + if(!m_websock) + return -1; + + json_object* j_obj = json_object_new_object(); + json_object_object_add(j_obj, "event", json_object_new_string(event_name.c_str())); + + int ret = afb_wsj1_call_j(m_websock, API_name, "unsubscribe", j_obj, ::on_reply, this); + if (ret < 0) + HMI_ERROR("cloudproxyclient", "unsubscribe filed for '%s'", event_name.c_str()); + + return ret; +} + +void CloudProxyClient::on_event(void *closure, const char *event, afb_wsj1_msg *msg) +{ + HMI_DEBUG("cloudproxyclient", "event: (%s) msg: (%s).", event, afb_wsj1_msg_object_s(msg)); + (void) closure; + + if (strstr(event, API_name) == nullptr) + return; + + + json_object* ev_contents = afb_wsj1_msg_object_j(msg); + json_object *json_data; + if(!json_object_object_get_ex(ev_contents, "data", &json_data)) + { + HMI_ERROR("cloudproxyclient", "got ev_contents error."); + return; + } + + const char* event_type = nullptr; + json_object *json_event_type; + if(!json_object_object_get_ex(json_data, "type", &json_event_type) || + (event_type = json_object_get_string(json_event_type)) == nullptr) + { + HMI_ERROR("cloudproxyclient", "event_type is null."); + return; + } + + const std::string et{event_type}; + if (CloudProxyClient::m_event_list[0] == et) + { + auto i = this->handlers.find(Event_SendMessageConfirmation); + if (i != this->handlers.end()) + i->second(json_data); + } + else if (CloudProxyClient::m_event_list[1] == et) + { + auto i = this->handlers.find(Event_ReceivedMessage); + if (i != this->handlers.end()) + i->second(json_data); + } +} diff --git a/app_client_examples/test-cloud-app/app/cloudproxy-client/cloudproxyclient.h b/app_client_examples/test-cloud-app/app/cloudproxy-client/cloudproxyclient.h new file mode 100755 index 0000000..b333a14 --- /dev/null +++ b/app_client_examples/test-cloud-app/app/cloudproxy-client/cloudproxyclient.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020 MERA + * + * 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. + */ + +#pragma once + +#include <vector> +#include <map> +#include <string> +#include <functional> +#include <json-c/json.h> +#include <systemd/sd-event.h> + +extern "C" +{ +#include <afb/afb-wsj1.h> +#include <afb/afb-ws-client.h> +} + +class CloudProxyClient +{ +public: + using handler_func = std::function<void(json_object*)>; + + enum EventType { + Event_Min, + Event_SendMessageConfirmation = 1, + Event_ReceivedMessage, + Event_Max + }; + + + CloudProxyClient(); + ~CloudProxyClient(); + + CloudProxyClient(const CloudProxyClient &) = delete; + CloudProxyClient &operator=(const CloudProxyClient &) = delete; + + int init(const int port, const std::string& token); + + int sendMessage(const std::string& data); + + void set_event_handler(enum EventType et, handler_func f); + + void on_event(void *closure, const char *event, struct afb_wsj1_msg *msg); + + int subscribe(const std::string& event_name); + int unsubscribe(const std::string& event_name); + +private: + int call(const std::string& verb, struct json_object* arg); + +private: + static const std::vector<std::string> m_api_list; + static const std::vector<std::string> m_event_list; + + std::map<EventType, handler_func> handlers; + struct afb_wsj1_itf m_itf; + struct afb_wsj1* m_websock{nullptr}; + sd_event* m_loop{nullptr}; + std::string m_uri; +}; diff --git a/app_client_examples/test-cloud-app/app/cloudproxy-client/hmi-debug.h b/app_client_examples/test-cloud-app/app/cloudproxy-client/hmi-debug.h new file mode 100755 index 0000000..674f5df --- /dev/null +++ b/app_client_examples/test-cloud-app/app/cloudproxy-client/hmi-debug.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 TOYOTA MOTOR CORPORATION + * + * 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 __HMI_DEBUG_H__ +#define __HMI_DEBUG_H__ + +#include <time.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> + +enum LOG_LEVEL{ + LOG_LEVEL_NONE = 0, + LOG_LEVEL_ERROR, + LOG_LEVEL_WARNING, + LOG_LEVEL_NOTICE, + LOG_LEVEL_INFO, + LOG_LEVEL_DEBUG, + LOG_LEVEL_MAX = LOG_LEVEL_DEBUG +}; + +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + +#define HMI_ERROR(prefix, args,...) _HMI_LOG(LOG_LEVEL_ERROR, __FILENAME__, __FUNCTION__, __LINE__, prefix, args, ##__VA_ARGS__) +#define HMI_WARNING(prefix, args,...) _HMI_LOG(LOG_LEVEL_WARNING, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_NOTICE(prefix, args,...) _HMI_LOG(LOG_LEVEL_NOTICE, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_INFO(prefix, args,...) _HMI_LOG(LOG_LEVEL_INFO, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_DEBUG(prefix, args,...) _HMI_LOG(LOG_LEVEL_DEBUG, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) + +static char ERROR_FLAG[6][20] = {"NONE", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"}; + +static void _HMI_LOG(enum LOG_LEVEL level, const char* file, const char* func, const int line, const char* prefix, const char* log, ...) +{ + const int log_level = (getenv("USE_HMI_DEBUG") == NULL)?LOG_LEVEL_ERROR:atoi(getenv("USE_HMI_DEBUG")); + if(log_level < level) + { + return; + } + + char *message; + struct timespec tp; + unsigned int time; + + clock_gettime(CLOCK_REALTIME, &tp); + time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000); + + va_list args; + va_start(args, log); + if (log == NULL || vasprintf(&message, log, args) < 0) + message = NULL; + fprintf(stderr, "[%10.3f] [%s %s] [%s, %s(), Line:%d] >>> %s \n", time / 1000.0, prefix, ERROR_FLAG[level], file, func, line, message); + va_end(args); + free(message); +} + +#endif //__HMI_DEBUG_H__ diff --git a/app_client_examples/test-cloud-app/app/images/AGL_HMI_Blue_Background_NoCar-01.png b/app_client_examples/test-cloud-app/app/images/AGL_HMI_Blue_Background_NoCar-01.png Binary files differnew file mode 100755 index 0000000..a76b140 --- /dev/null +++ b/app_client_examples/test-cloud-app/app/images/AGL_HMI_Blue_Background_NoCar-01.png diff --git a/app_client_examples/test-cloud-app/app/images/images.qrc b/app_client_examples/test-cloud-app/app/images/images.qrc new file mode 100755 index 0000000..78d9b77 --- /dev/null +++ b/app_client_examples/test-cloud-app/app/images/images.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/images"> + <file>AGL_HMI_Blue_Background_NoCar-01.png</file> + </qresource> +</RCC> diff --git a/app_client_examples/test-cloud-app/app/main.cpp b/app_client_examples/test-cloud-app/app/main.cpp new file mode 100755 index 0000000..45a877b --- /dev/null +++ b/app_client_examples/test-cloud-app/app/main.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 <QtCore/QUrlQuery> +#include <QtGui/QGuiApplication> +#include <libhomescreen.hpp> +#include <qlibwindowmanager.h> +#include <QtCore/QCommandLineParser> +#include <QScopedPointer> + +#include "mainwindow.h" +#include <QApplication> +#include <QDebug> + +#include "cloudproxy-client/cloudproxyclient.h" + + +CloudProxyClient* g_cloudproxyclient{nullptr}; + + +int main(int argc, char *argv[]) +{ + QString graphic_role = QString("cloudapp") + QString(APP_INSTANCE_NUM); + + QApplication app(argc, argv); + QScopedPointer<MainWindow> window; + + QCommandLineParser parser; + parser.addPositionalArgument("port", app.translate("main", "port for binding")); + parser.addPositionalArgument("secret", app.translate("main", "secret for binding")); + parser.addHelpOption(); + parser.addVersionOption(); + parser.process(app); + QStringList positionalArguments = parser.positionalArguments(); + + if (positionalArguments.length() == 2) + { + int port = positionalArguments.takeFirst().toInt(); + QString secret = positionalArguments.takeFirst(); + QUrl bindingAddress; + bindingAddress.setScheme(QStringLiteral("ws")); + bindingAddress.setHost(QStringLiteral("localhost")); + bindingAddress.setPort(port); + bindingAddress.setPath(QStringLiteral("/api")); + QUrlQuery query; + query.addQueryItem(QStringLiteral("token"), secret); + bindingAddress.setQuery(query); + + std::string token = secret.toStdString(); + LibHomeScreen* hs = new LibHomeScreen(); + QLibWindowmanager* qwm = new QLibWindowmanager(); + + // WindowManager + if(qwm->init(port,secret) != 0){ + exit(EXIT_FAILURE); + } + AGLScreenInfo screenInfo(qwm->get_scale_factor()); + window.reset(new MainWindow(screenInfo.scale_factor())); + + // Request a surface as described in layers.json windowmanager’s file + if (qwm->requestSurface(graphic_role) != 0) { + exit(EXIT_FAILURE); + } + // Create an event callback against an event type. Here a lambda is called when SyncDraw event occurs + qwm->set_event_handler(QLibWindowmanager::Event_SyncDraw, [qwm, &graphic_role](json_object *object) { + (void)object; + qDebug() << "Surface got syncDraw!"; + qwm->endDraw(graphic_role); + }); + + // HomeScreen + hs->init(port, token.c_str()); + // Set the event handler for Event_ShowWindow which will activate the surface for windowmanager + hs->set_event_handler(LibHomeScreen::Event_ShowWindow, [qwm, &graphic_role](json_object *object){ + (void)object; + qDebug("Surface %s got showWindow\n", graphic_role.toStdString().c_str()); + qwm->activateWindow(graphic_role); + }); + + g_cloudproxyclient = new CloudProxyClient(); + g_cloudproxyclient->init(port, token.c_str()); + + static int i_confirm_good{0}, i_confirm_bad{0}, i_recv{0}; + g_cloudproxyclient->set_event_handler(CloudProxyClient::Event_SendMessageConfirmation, [&window](json_object* object){ + qDebug("CloudProxyClient::Event_SendMessageConfirmation: object ptr %p", object); + + const char* str = object ? json_object_to_json_string_ext(object, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) : "<obj_null>"; + if (!str) + str = "<empty>"; + qDebug("Event_SendMessageConfirmation: %s", str); + + json_object *j_result; + if(!json_object_object_get_ex(object, "result", &j_result)) + { + qDebug("Can't read confirmation result"); + return; + } + + int result = (int)json_object_get_boolean(j_result); + + if (result) + ++i_confirm_good; + else + ++i_confirm_bad; + + window->updateStat(i_confirm_good, i_confirm_bad, i_recv, QVariant()); + qDebug("Application received confirmation result [good: %d, bad: %d]: %d", i_confirm_good, i_confirm_bad, result); + }); + g_cloudproxyclient->set_event_handler(CloudProxyClient::Event_ReceivedMessage, [&window](json_object* object){ + qDebug("CloudProxyClient::Event_ReceivedMessage: object ptr %p", object); + + const char* str = object ? json_object_to_json_string_ext(object, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) : "<obj_null>"; + if (!str) + str = "<empty>"; + qDebug("Event_SendMessageConfirmation: %s", str); + + json_object *event_data; + const char* data_str{nullptr}; + if(!json_object_object_get_ex(object, "data", &event_data) || + (data_str = json_object_get_string(event_data)) == nullptr) + { + qDebug("Can't read event data"); + return; + } + + ++i_recv; + window->updateStat(i_confirm_good, i_confirm_bad, i_recv, QString(data_str)); + qDebug("Application received data [count: %d, good: %d, bad %d]: %s", i_recv, i_confirm_good, i_confirm_bad, data_str); + }); + + + window->show(); + qwm->slotActivateWindow(); + } + + return app.exec(); +} diff --git a/app_client_examples/test-cloud-app/app/mainwindow.cpp b/app_client_examples/test-cloud-app/app/mainwindow.cpp new file mode 100755 index 0000000..fda05e7 --- /dev/null +++ b/app_client_examples/test-cloud-app/app/mainwindow.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 "mainwindow.h" +#include "ui_mainwindow.h" +#include <QDebug> + +#include <unistd.h> +#include <sys/stat.h> + + +#include "cloudproxy-client/cloudproxyclient.h" +extern CloudProxyClient* g_cloudproxyclient; + + +MainWindow::MainWindow(double scale_factor, QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(scale_factor, this); + + //AGL style + setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); + this->setStyleSheet( + "background-image:url(:/images/AGL_HMI_Blue_Background_NoCar-01.png) 0 0 0 0 stretch stretch; background: transparent;" ); +} + +void MainWindow::updateStat(int confirm_good, int confirm_bad, int received, QVariant recvMsg) +{ + confirmGood = confirm_good; + confirmBad = confirm_bad; + receivedCount = received; + + if (recvMsg.isValid()) + lastReceivedMsg = recvMsg.toString(); + + updateStat(); +} + +void MainWindow::updateStat() +{ + QString str = QString::asprintf("Sent: OK=%d, NOK=%d\nConfirmed: OK=%d, NOK=%d\nReceived=%d\n\n", + sendCount, sendErrorCount, confirmGood, confirmBad, receivedCount); + str.append("Last sent message:\n"); + str.append(lastSentMsg); + str.append("\n\nLast received message:\n"); + str.append(lastReceivedMsg); + ui->statLabel->setText(str); + //QApplication::processEvents(); +} + +void MainWindow::sendMsgButtonClick() +{ + qDebug() << "MainWindow::sendMegButtonClick()"; + + std::string msg = std::string{"{\"app_key\": \"app_value_"} + std::to_string(sendCount + sendErrorCount) + "\"}"; + int res = g_cloudproxyclient->sendMessage(msg); + if (res == 0) + ++sendCount; + else + ++sendErrorCount; + + lastSentMsg = msg.c_str(); + qDebug() << "cloud sendMessage result: " << res; +} + + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/app_client_examples/test-cloud-app/app/mainwindow.h b/app_client_examples/test-cloud-app/app/mainwindow.h new file mode 100755 index 0000000..ae66729 --- /dev/null +++ b/app_client_examples/test-cloud-app/app/mainwindow.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QVariant> +#include <memory> + + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(double scale_factor, QWidget *parent = 0); + ~MainWindow(); + +public slots: + void updateStat(int confirm_good, int confirm_bad, int received, QVariant recvMsg); + +private slots: + void sendMsgButtonClick(); + void updateStat(); + +private: + Ui::MainWindow *ui; + int sendCount{0}; + int sendErrorCount{0}; + int confirmGood{0}; + int confirmBad{0}; + int receivedCount{0}; + QString lastSentMsg; + QString lastReceivedMsg; +}; + +#endif // MAINWINDOW_H diff --git a/app_client_examples/test-cloud-app/app/ui_mainwindow.h b/app_client_examples/test-cloud-app/app/ui_mainwindow.h new file mode 100755 index 0000000..3dac77e --- /dev/null +++ b/app_client_examples/test-cloud-app/app/ui_mainwindow.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 UI_MAINWINDOW_H +#define UI_MAINWINDOW_H + +#include <QtCore/QVariant> +#include <QtWidgets/QApplication> +#include <QtWidgets/QLabel> +#include <QtWidgets/QMainWindow> +#include <QtWidgets/QPushButton> +#include <QtWidgets/QLineEdit> +#include <QtWidgets/QWidget> +#include <QtWidgets/QFrame> +#include <QtWidgets/QGroupBox> +#include <QtWidgets/QRadioButton> +#include <QtWidgets/QComboBox> + +QT_BEGIN_NAMESPACE + +class Ui_MainWindow +{ +public: + QWidget *centralWidget{nullptr}; + QPushButton *sendMsgButton{nullptr}; + QLabel *appNameLabel{nullptr}; + QLabel *statLabel{nullptr}; + + void setupUi(double scale_factor, QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QString::fromUtf8("MainWindow")); + MainWindow->resize(1080*scale_factor, 1400*scale_factor); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QString::fromUtf8("centralWidget")); + + //QString style("color: white; font-size: 24px"); + QString style("color: white"); + + sendMsgButton = new QPushButton(centralWidget); + sendMsgButton->setObjectName(QString::fromUtf8("sendMsgButton")); + sendMsgButton->setGeometry(QRect((540-242/2)*scale_factor, 100*scale_factor, 242*scale_factor, 64*scale_factor)); + sendMsgButton->setStyleSheet(style); + + appNameLabel = new QLabel(centralWidget); + appNameLabel->setObjectName(QString::fromUtf8("label")); + appNameLabel->setGeometry(QRect(40*scale_factor, 20*scale_factor, 1000*scale_factor, 32*scale_factor)); + appNameLabel->setStyleSheet(style); + + statLabel = new QLabel(centralWidget); + statLabel->setObjectName(QString::fromUtf8("label")); + statLabel->setGeometry(QRect(40*scale_factor, 180*scale_factor, 1000*scale_factor, 420*scale_factor)); + statLabel->setWordWrap(true); + statLabel->setAlignment(Qt::AlignTop); + statLabel->setStyleSheet(style); + + MainWindow->setCentralWidget(centralWidget); + + QObject::connect(sendMsgButton, SIGNAL(clicked()), MainWindow, SLOT(sendMsgButtonClick())); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", nullptr)); + sendMsgButton->setText(QApplication::translate("MainWindow", "Send message", nullptr)); + QString name = QString("Test application ") + APP_INSTANCE_NUM; + appNameLabel->setText(QApplication::translate("MainWindow", name.toUtf8().constData(), nullptr)); + } // retranslateUi +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_MAINWINDOW_H diff --git a/app_client_examples/test-cloud-app/autobuild/agl/autobuild b/app_client_examples/test-cloud-app/autobuild/agl/autobuild new file mode 100755 index 0000000..bbbc13e --- /dev/null +++ b/app_client_examples/test-cloud-app/autobuild/agl/autobuild @@ -0,0 +1,110 @@ +#!/usr/bin/make -f +# Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group +# Author "Romain Forlot" <romain.forlot@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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) + +# Build directories +# Note that the debug directory is defined in relation to the release +# directory (BUILD_DIR), this needs to be kept in mind if over-riding +# it and building that widget types, the specific widget type variable +# (e.g. BUILD_DIR_DEBUG) may also need to be specified to yield the +# desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) + +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release builds +BUILD_TYPE = release + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-all +.PHONY: configure configure-release configure-debug +.PHONY: build build-release build-debug build-all +.PHONY: package package-release package-debug package-all + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- help" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in $(DEST) directory" + @echo "" + @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = debug + +clean-release clean-debug: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ + fi + +clean: clean-release + +clean-all: clean-release clean-debug + +distclean: clean-all + +configure-release configure-debug: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && qmake CONFIG+=$(BUILD_TYPE) $(CONFIGURE_ARGS) $(ROOT_DIR)); \ + fi + +configure: configure-release + +build-release build-debug: build-%: configure-% + @$(MAKE) -C $(OUTPUT_DIR) $(BUILD_ARGS) all + +build: build-release + +build-all: build-release build-debug + +package-release package-debug: package-%: build-% + @cp $(OUTPUT_DIR)/package/*.wgt $(OUTPUT_DIR)/ + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + + +package: package-release + +package-all: package-release package-debug + +install: build + @$(MAKE) -C $(BUILD_DIR) $(INSTALL_ARGS) install diff --git a/app_client_examples/test-cloud-app/autobuild/linux/autobuild b/app_client_examples/test-cloud-app/autobuild/linux/autobuild new file mode 100755 index 0000000..bbbc13e --- /dev/null +++ b/app_client_examples/test-cloud-app/autobuild/linux/autobuild @@ -0,0 +1,110 @@ +#!/usr/bin/make -f +# Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group +# Author "Romain Forlot" <romain.forlot@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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) + +# Build directories +# Note that the debug directory is defined in relation to the release +# directory (BUILD_DIR), this needs to be kept in mind if over-riding +# it and building that widget types, the specific widget type variable +# (e.g. BUILD_DIR_DEBUG) may also need to be specified to yield the +# desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) + +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release builds +BUILD_TYPE = release + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-all +.PHONY: configure configure-release configure-debug +.PHONY: build build-release build-debug build-all +.PHONY: package package-release package-debug package-all + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- help" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in $(DEST) directory" + @echo "" + @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = debug + +clean-release clean-debug: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ + fi + +clean: clean-release + +clean-all: clean-release clean-debug + +distclean: clean-all + +configure-release configure-debug: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && qmake CONFIG+=$(BUILD_TYPE) $(CONFIGURE_ARGS) $(ROOT_DIR)); \ + fi + +configure: configure-release + +build-release build-debug: build-%: configure-% + @$(MAKE) -C $(OUTPUT_DIR) $(BUILD_ARGS) all + +build: build-release + +build-all: build-release build-debug + +package-release package-debug: package-%: build-% + @cp $(OUTPUT_DIR)/package/*.wgt $(OUTPUT_DIR)/ + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + + +package: package-release + +package-all: package-release package-debug + +install: build + @$(MAKE) -C $(BUILD_DIR) $(INSTALL_ARGS) install diff --git a/app_client_examples/test-cloud-app/package/config.xml b/app_client_examples/test-cloud-app/package/config.xml new file mode 100755 index 0000000..43eceaf --- /dev/null +++ b/app_client_examples/test-cloud-app/package/config.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<widget xmlns="http://www.w3.org/ns/widgets" id="testcloudapp1" version="0.1"> + <name>CloudApp1</name> + <icon src="icon.svg"/> + <content src="bin/testcloudapp" type="application/vnd.agl.native"/> + <description>Test Cloud App</description> + <author>Mera</author> + <license>APL 2.0</license> + <feature name="urn:AGL:widget:required-api"> + <param name="windowmanager" value="ws"/> + <param name="homescreen" value="ws"/> + <param name="cloudproxy" value="ws"/> + </feature> + <feature name="urn:AGL:widget:required-permission"> + <param name="urn:AGL:permission::public:no-htdocs" value="required"/> + <param name="urn:AGL:permission::public:display" value="required" /> + </feature> +</widget> diff --git a/app_client_examples/test-cloud-app/package/icon.svg b/app_client_examples/test-cloud-app/package/icon.svg new file mode 100755 index 0000000..b9b8e67 --- /dev/null +++ b/app_client_examples/test-cloud-app/package/icon.svg @@ -0,0 +1,266 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:i="&#38;ns_ai;" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + x="0px" + y="0px" + viewBox="0 0 320 320" + style="enable-background:new 0 0 320 320;" + xml:space="preserve" + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="icon.svg"><metadata + id="metadata1292"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs1290" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1464" + id="namedview1288" + showgrid="false" + inkscape:zoom="4.17193" + inkscape:cx="183.76952" + inkscape:cy="191.17869" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /><style + type="text/css" + id="style4"> + .st0{display:none;} + .st1{display:inline;} + .st2{opacity:0.4;fill:url(#SVGID_1_);} + .st3{fill:url(#SVGID_2_);} + .st4{fill:#FFFFFF;} + .st5{font-family:'Roboto-Regular';} + .st6{font-size:25px;} + .st7{letter-spacing:6;} + .st8{fill:url(#SVGID_3_);} + .st9{fill:url(#SVGID_4_);} + .st10{fill:url(#SVGID_5_);} + .st11{fill:url(#SVGID_6_);} + .st12{fill:url(#SVGID_7_);} + .st13{fill:url(#SVGID_8_);} + .st14{fill:url(#SVGID_9_);} + .st15{fill:url(#SVGID_10_);} + .st16{fill:url(#SVGID_11_);} + .st17{fill:url(#SVGID_12_);} + .st18{fill:url(#SVGID_13_);} + .st19{fill:url(#SVGID_14_);} + .st20{fill:url(#SVGID_15_);} + .st21{fill:url(#SVGID_16_);} + .st22{fill:url(#SVGID_17_);} + .st23{fill:url(#SVGID_18_);} + .st24{opacity:0.29;} + .st25{fill:url(#SVGID_19_);} + .st26{fill:url(#SVGID_20_);} + .st27{fill:url(#SVGID_21_);} + .st28{fill:url(#SVGID_22_);} + .st29{fill:url(#SVGID_23_);} + .st30{fill:url(#SVGID_24_);} + .st31{fill:url(#SVGID_25_);} + .st32{fill:url(#SVGID_26_);} + .st33{fill:url(#SVGID_27_);} + .st34{fill:url(#SVGID_28_);} + .st35{fill:url(#SVGID_29_);} + .st36{fill:url(#SVGID_30_);} + .st37{fill:url(#SVGID_31_);} + .st38{fill:url(#SVGID_32_);} + .st39{fill:url(#SVGID_33_);} + .st40{fill:url(#SVGID_34_);} + .st41{fill:url(#SVGID_35_);} + .st42{fill:url(#SVGID_36_);} + .st43{opacity:0.4;fill:url(#SVGID_37_);} + .st44{fill:url(#SVGID_38_);} + .st45{fill:url(#SVGID_39_);} + .st46{fill:url(#SVGID_40_);} + .st47{fill:url(#SVGID_41_);} + .st48{fill:url(#SVGID_42_);} + .st49{fill:url(#SVGID_43_);} + .st50{fill:url(#SVGID_44_);} + .st51{display:inline;opacity:0.29;} + .st52{display:inline;fill:url(#SVGID_45_);} + .st53{display:inline;fill:url(#SVGID_46_);} + .st54{display:inline;fill:#FFFFFF;} + .st55{display:inline;fill:url(#SVGID_47_);} + .st56{display:inline;fill:url(#SVGID_48_);} + .st57{display:inline;fill:url(#SVGID_49_);} + .st58{display:inline;fill:url(#SVGID_50_);} + .st59{display:inline;fill:url(#SVGID_51_);} + .st60{display:inline;fill:url(#SVGID_52_);} + .st61{opacity:0.4;fill:url(#SVGID_53_);} + .st62{fill:url(#SVGID_54_);} + .st63{fill:url(#SVGID_55_);} + .st64{fill:url(#SVGID_56_);} + .st65{fill:url(#SVGID_57_);} + .st66{fill:url(#SVGID_58_);} + .st67{opacity:0.4;fill:url(#SVGID_59_);} + .st68{fill:url(#SVGID_60_);} + .st69{fill:url(#SVGID_61_);} + .st70{fill:url(#SVGID_62_);} + .st71{fill:url(#SVGID_63_);} + .st72{fill:url(#SVGID_64_);} + .st73{fill:url(#SVGID_65_);} + .st74{fill:url(#SVGID_66_);} + .st75{fill:url(#SVGID_67_);} + .st76{fill:url(#SVGID_68_);} + .st77{fill:url(#SVGID_69_);} + .st78{fill:url(#SVGID_70_);} + .st79{fill:url(#SVGID_71_);} + .st80{fill:url(#SVGID_72_);} + .st81{fill:url(#SVGID_73_);} + .st82{fill:url(#SVGID_74_);} + .st83{fill:url(#SVGID_75_);} + .st84{fill:url(#SVGID_76_);} + .st85{fill:url(#SVGID_77_);} + .st86{fill:url(#SVGID_78_);} + .st87{fill:url(#SVGID_79_);} + .st88{fill:url(#SVGID_80_);} + .st89{fill:url(#SVGID_81_);} + .st90{fill:url(#SVGID_82_);} + .st91{fill:url(#SVGID_83_);} + .st92{fill:url(#SVGID_84_);} + .st93{fill:url(#SVGID_85_);} + .st94{fill:url(#SVGID_86_);} + .st95{opacity:0.4;fill:url(#SVGID_87_);} + .st96{fill:url(#SVGID_88_);} + .st97{fill:url(#SVGID_89_);} + .st98{fill:url(#SVGID_90_);} + .st99{fill:url(#SVGID_91_);} + .st100{fill:url(#SVGID_92_);} + .st101{fill:url(#SVGID_93_);} + .st102{fill:url(#SVGID_94_);} + .st103{opacity:0.4;fill:url(#SVGID_95_);} + .st104{fill:url(#SVGID_96_);} + .st105{fill:url(#SVGID_97_);} + .st106{fill:url(#SVGID_98_);} + .st107{fill:url(#SVGID_99_);} + .st108{fill:url(#SVGID_100_);} + .st109{fill:url(#SVGID_101_);} + .st110{display:inline;fill:url(#SVGID_102_);} + .st111{display:inline;fill:url(#SVGID_103_);} + .st112{fill:url(#SVGID_104_);} + .st113{fill:url(#SVGID_105_);} + .st114{fill:url(#SVGID_106_);} + .st115{fill:url(#SVGID_107_);} + .st116{fill:url(#SVGID_108_);} + .st117{opacity:0.4;fill:url(#SVGID_109_);} + .st118{fill:url(#SVGID_110_);} + .st119{fill:url(#SVGID_111_);} + .st120{fill:url(#SVGID_112_);} + .st121{fill:url(#SVGID_113_);} + .st122{fill:url(#SVGID_114_);} + .st123{opacity:0.4;fill:url(#SVGID_115_);} + .st124{fill:url(#SVGID_116_);} + .st125{fill:url(#SVGID_117_);} + .st126{fill:url(#SVGID_118_);} + .st127{display:inline;fill:url(#SVGID_119_);} + .st128{display:inline;fill:url(#SVGID_120_);} + .st129{fill:url(#SVGID_121_);} + .st130{fill:url(#SVGID_122_);} +</style><switch + id="switch6"><g + i:extraneous="self" + id="g8"><g + id="Multimedia_Inactive_copy"><circle + class="st24" + cx="159.7" + cy="133.4" + r="101.9" + id="circle884" /><linearGradient + id="SVGID_91_" + gradientUnits="userSpaceOnUse" + x1="115.9317" + y1="254.1836" + x2="256.3852" + y2="-133.5267"><stop + offset="0" + style="stop-color:#8BC53F" + id="stop887" /><stop + offset="2.015080e-02" + style="stop-color:#7CCB56;stop-opacity:0.9678" + id="stop889" /><stop + offset="6.089833e-02" + style="stop-color:#62D67D;stop-opacity:0.9028" + id="stop891" /><stop + offset="0.1057" + style="stop-color:#4BDFA0;stop-opacity:0.8312" + id="stop893" /><stop + offset="0.1543" + style="stop-color:#38E7BE;stop-opacity:0.7537" + id="stop895" /><stop + offset="0.2077" + style="stop-color:#28EED6;stop-opacity:0.6684" + id="stop897" /><stop + offset="0.2681" + style="stop-color:#1CF3E8;stop-opacity:0.572" + id="stop899" /><stop + offset="0.3394" + style="stop-color:#13F6F5;stop-opacity:0.4581" + id="stop901" /><stop + offset="0.4323" + style="stop-color:#0EF8FD;stop-opacity:0.3098" + id="stop903" /><stop + offset="0.6264" + style="stop-color:#0DF9FF;stop-opacity:0" + id="stop905" /></linearGradient><circle + class="st99" + cx="159.7" + cy="133.4" + r="101.9" + id="circle907" /><linearGradient + id="SVGID_92_" + gradientUnits="userSpaceOnUse" + x1="4.0481" + y1="287.9492" + x2="320.4859" + y2="-15.4029" + gradientTransform="matrix(1 5.464556e-03 -5.464556e-03 1 -2.0192 -3.0212)"><stop + offset="0" + style="stop-color:#59FF7F" + id="stop910" /><stop + offset="1" + style="stop-color:#6BFBFF" + id="stop912" /></linearGradient><path + class="st100" + d="M160,238.8c-0.2,0-0.4,0-0.6,0c-58-0.3-104.9-47.7-104.6-105.7C55.2,75.3,102.3,28.5,160,28.5 c0.2,0,0.4,0,0.6,0c58,0.3,104.9,47.7,104.6,105.7l0,0C264.8,192,217.7,238.8,160,238.8z M160,32.2 c-55.7,0-101.2,45.2-101.5,100.9c-0.3,55.9,45,101.7,100.9,102c0.2,0,0.4,0,0.6,0c55.7,0,101.2-45.2,101.5-100.9 c0.3-55.9-45-101.7-100.9-102C160.4,32.2,160.2,32.2,160,32.2z" + id="path914" /><g + id="g916"><text + transform="matrix(1 0 0 1 53.5841 284.7119)" + class="st4 st5 st6 st7" + id="text918">VIDEOPLAYER +</text> +<linearGradient + id="SVGID_94_" + gradientUnits="userSpaceOnUse" + x1="145.3286" + y1="203.8951" + x2="191.6285" + y2="70.3637"><stop + offset="0" + style="stop-color:#59FF7F" + id="stop928" /><stop + offset="1" + style="stop-color:#6BFBFF" + id="stop930" /></linearGradient><polygon + class="st102" + points="125,75 125,200 225,137.5 " + id="polygon932" + style="fill:url(#SVGID_94_)" /></g></g></g></switch></svg>
\ No newline at end of file diff --git a/app_client_examples/test-cloud-app/package/package.pro b/app_client_examples/test-cloud-app/package/package.pro new file mode 100755 index 0000000..c8af452 --- /dev/null +++ b/app_client_examples/test-cloud-app/package/package.pro @@ -0,0 +1,28 @@ + +DISTFILES = icon.svg config.xml + +copy_icon.target = $$OUT_PWD/root/icon.svg +copy_icon.depends = $$_PRO_FILE_PWD_/icon.svg +copy_icon.commands = $(COPY_FILE) \"$$replace(copy_icon.depends, /, $$QMAKE_DIR_SEP)\" \"$$replace(copy_icon.target, /, $$QMAKE_DIR_SEP)\" +QMAKE_EXTRA_TARGETS += copy_icon +PRE_TARGETDEPS += $$copy_icon.target + +copy_config.target = $$OUT_PWD/root/config.xml +copy_config.depends = $$_PRO_FILE_PWD_/config.xml +copy_config.commands = $(COPY_FILE) \"$$replace(copy_config.depends, /, $$QMAKE_DIR_SEP)\" \"$$replace(copy_config.target, /, $$QMAKE_DIR_SEP)\" +QMAKE_EXTRA_TARGETS += copy_config +PRE_TARGETDEPS += $$copy_config.target + +WGT_TYPE = +CONFIG(debug, debug|release) { + WGT_TYPE = -debug +} + +wgt.target = package +wgt.commands = wgtpkg-pack -f -o testcloudapp$${WGT_TYPE}.wgt root + +QMAKE_EXTRA_TARGETS += wgt + +deploy.files = testcloudapp.wgt +deploy.path = /usr/AGL/apps/autoinstall +INSTALLS += deploy diff --git a/app_client_examples/test-cloud-app/testcloudapp.pro b/app_client_examples/test-cloud-app/testcloudapp.pro new file mode 100755 index 0000000..579a952 --- /dev/null +++ b/app_client_examples/test-cloud-app/testcloudapp.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = app package +package.depends += app diff --git a/autobuild/agl/autobuild b/autobuild/agl/autobuild new file mode 100755 index 0000000..16181b8 --- /dev/null +++ b/autobuild/agl/autobuild @@ -0,0 +1,128 @@ +#!/usr/bin/make -f +# Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group +# Author "Romain Forlot" <romain.forlot@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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) + +# Build directories +# Note that the debug/test/coverage directories are defined in relation +# to the release directory (BUILD_DIR), this needs to be kept in mind +# if over-riding it and building those widget types, the specific widget +# type variable (e.g. BUILD_DIR_DEBUG) may also need to be specified +# to yield the desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) +BUILD_DIR_TEST = $(abspath $(BUILD_DIR)/../build-test) +BUILD_DIR_COVERAGE = $(abspath $(BUILD_DIR)/../build-coverage) + +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release/test builds +BUILD_TYPE = RELEASE + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-test clean-coverage clean-all +.PHONY: configure configure-release configure-debug configure-test configure-coverage +.PHONY: build build-release build-debug build-test build-coverage build-all +.PHONY: package package-release package-debug package-test package-coverage package-all + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- help" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in your $(CMAKE_INSTALL_DIR) directory" + @echo "" + @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-test build-test package-test clean-test: OUTPUT_DIR = $(BUILD_DIR_TEST) + +configure-coverage build-coverage package-coverage clean-coverage: OUTPUT_DIR = $(BUILD_DIR_COVERAGE) +configure-coverage build-coverage package-coverage: BUILD_TYPE = COVERAGE + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = DEBUG + +clean-release clean-test clean-debug clean-coverage: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ + fi + +clean: clean-release + +clean-all: clean-release clean-test clean-debug clean-coverage + +distclean: clean-all + +configure-release configure-test configure-debug configure-coverage: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && cmake -S $(ROOT_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CONFIGURE_ARGS)); \ + fi + +configure: configure-release + +build-release build-debug build-coverage: build-%: configure-% + @cmake --build $(OUTPUT_DIR) $(BUILD_ARGS) --target all + +# Kept for consistency, empty to avoid building everything for test widget +build-test: configure-test + +build: build-release + +build-all: build-release build-debug build-test build-coverage + +package-release package-debug package-coverage: package-%: build-% + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package-test: build-test + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target test_widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package: package-release + +package-all: package-release package-test package-coverage package-debug + +update: configure + @cmake --build $(BUILD_DIR) --target autobuild + +install: build + @cmake --build $(BUILD_DIR) $(INSTALL_ARGS) --target install diff --git a/autobuild/linux/autobuild b/autobuild/linux/autobuild new file mode 100755 index 0000000..16181b8 --- /dev/null +++ b/autobuild/linux/autobuild @@ -0,0 +1,128 @@ +#!/usr/bin/make -f +# Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group +# Author "Romain Forlot" <romain.forlot@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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) + +# Build directories +# Note that the debug/test/coverage directories are defined in relation +# to the release directory (BUILD_DIR), this needs to be kept in mind +# if over-riding it and building those widget types, the specific widget +# type variable (e.g. BUILD_DIR_DEBUG) may also need to be specified +# to yield the desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) +BUILD_DIR_TEST = $(abspath $(BUILD_DIR)/../build-test) +BUILD_DIR_COVERAGE = $(abspath $(BUILD_DIR)/../build-coverage) + +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release/test builds +BUILD_TYPE = RELEASE + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-test clean-coverage clean-all +.PHONY: configure configure-release configure-debug configure-test configure-coverage +.PHONY: build build-release build-debug build-test build-coverage build-all +.PHONY: package package-release package-debug package-test package-coverage package-all + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- help" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in your $(CMAKE_INSTALL_DIR) directory" + @echo "" + @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-test build-test package-test clean-test: OUTPUT_DIR = $(BUILD_DIR_TEST) + +configure-coverage build-coverage package-coverage clean-coverage: OUTPUT_DIR = $(BUILD_DIR_COVERAGE) +configure-coverage build-coverage package-coverage: BUILD_TYPE = COVERAGE + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = DEBUG + +clean-release clean-test clean-debug clean-coverage: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ + fi + +clean: clean-release + +clean-all: clean-release clean-test clean-debug clean-coverage + +distclean: clean-all + +configure-release configure-test configure-debug configure-coverage: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && cmake -S $(ROOT_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CONFIGURE_ARGS)); \ + fi + +configure: configure-release + +build-release build-debug build-coverage: build-%: configure-% + @cmake --build $(OUTPUT_DIR) $(BUILD_ARGS) --target all + +# Kept for consistency, empty to avoid building everything for test widget +build-test: configure-test + +build: build-release + +build-all: build-release build-debug build-test build-coverage + +package-release package-debug package-coverage: package-%: build-% + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package-test: build-test + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target test_widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package: package-release + +package-all: package-release package-test package-coverage package-debug + +update: configure + @cmake --build $(BUILD_DIR) --target autobuild + +install: build + @cmake --build $(BUILD_DIR) $(INSTALL_ARGS) --target install diff --git a/package/root/config.xml b/package/root/config.xml new file mode 100755 index 0000000..acfdc1e --- /dev/null +++ b/package/root/config.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<widget xmlns="http://www.w3.org/ns/widgets" id="cloudproxy-service" version="0.1"> + <name>cloudproxy-service</name> + <icon src="icon.svg"/> + <content src="config.xml" type="application/vnd.agl.service"/> + <description>CloudProxy service</description> + <author>MERA</author> + <license>APL 2.0</license> + <feature name="urn:AGL:widget:required-permission"> + <param name="urn:AGL:permission::public:hidden" value="required" /> + <param name="urn:AGL:permission::system:run-by-default" value="required" /> + <param name="http://tizen.org/privilege/internal/dbus" value="required" /> + </feature> + <feature name="urn:AGL:widget:provided-api"> + <param name="cloudproxy" value="ws" /> + </feature> + <feature name="urn:AGL:widget:required-api"> + <param name="afm-main" value="ws" /> + </feature> + <feature name="urn:AGL:widget:required-binding"> + <param name="lib/cloudproxy-service.so" value="local" /> + </feature> +</widget> diff --git a/package/root/icon.svg b/package/root/icon.svg new file mode 100755 index 0000000..6628784 --- /dev/null +++ b/package/root/icon.svg @@ -0,0 +1,283 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:i="&ns_ai;" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + x="0px" + y="0px" + viewBox="0 0 320 320" + style="enable-background:new 0 0 320 320;" + xml:space="preserve" + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="icon.svg"><metadata + id="metadata1292"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs1290" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1464" + id="namedview1288" + showgrid="false" + inkscape:zoom="0.7375" + inkscape:cx="-697.62712" + inkscape:cy="160" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /><style + type="text/css" + id="style4"> + .st0{display:none;} + .st1{display:inline;} + .st2{opacity:0.4;fill:url(#SVGID_1_);} + .st3{fill:url(#SVGID_2_);} + .st4{fill:#FFFFFF;} + .st5{font-family:'Roboto-Regular';} + .st6{font-size:25px;} + .st7{letter-spacing:6;} + .st8{fill:url(#SVGID_3_);} + .st9{fill:url(#SVGID_4_);} + .st10{fill:url(#SVGID_5_);} + .st11{fill:url(#SVGID_6_);} + .st12{fill:url(#SVGID_7_);} + .st13{fill:url(#SVGID_8_);} + .st14{fill:url(#SVGID_9_);} + .st15{fill:url(#SVGID_10_);} + .st16{fill:url(#SVGID_11_);} + .st17{fill:url(#SVGID_12_);} + .st18{fill:url(#SVGID_13_);} + .st19{fill:url(#SVGID_14_);} + .st20{fill:url(#SVGID_15_);} + .st21{fill:url(#SVGID_16_);} + .st22{fill:url(#SVGID_17_);} + .st23{fill:url(#SVGID_18_);} + .st24{opacity:0.29;} + .st25{fill:url(#SVGID_19_);} + .st26{fill:url(#SVGID_20_);} + .st27{fill:url(#SVGID_21_);} + .st28{fill:url(#SVGID_22_);} + .st29{fill:url(#SVGID_23_);} + .st30{fill:url(#SVGID_24_);} + .st31{fill:url(#SVGID_25_);} + .st32{fill:url(#SVGID_26_);} + .st33{fill:url(#SVGID_27_);} + .st34{fill:url(#SVGID_28_);} + .st35{fill:url(#SVGID_29_);} + .st36{fill:url(#SVGID_30_);} + .st37{fill:url(#SVGID_31_);} + .st38{fill:url(#SVGID_32_);} + .st39{fill:url(#SVGID_33_);} + .st40{fill:url(#SVGID_34_);} + .st41{fill:url(#SVGID_35_);} + .st42{fill:url(#SVGID_36_);} + .st43{opacity:0.4;fill:url(#SVGID_37_);} + .st44{fill:url(#SVGID_38_);} + .st45{fill:url(#SVGID_39_);} + .st46{fill:url(#SVGID_40_);} + .st47{fill:url(#SVGID_41_);} + .st48{fill:url(#SVGID_42_);} + .st49{fill:url(#SVGID_43_);} + .st50{fill:url(#SVGID_44_);} + .st51{display:inline;opacity:0.29;} + .st52{display:inline;fill:url(#SVGID_45_);} + .st53{display:inline;fill:url(#SVGID_46_);} + .st54{display:inline;fill:#FFFFFF;} + .st55{display:inline;fill:url(#SVGID_47_);} + .st56{display:inline;fill:url(#SVGID_48_);} + .st57{display:inline;fill:url(#SVGID_49_);} + .st58{display:inline;fill:url(#SVGID_50_);} + .st59{display:inline;fill:url(#SVGID_51_);} + .st60{display:inline;fill:url(#SVGID_52_);} + .st61{opacity:0.4;fill:url(#SVGID_53_);} + .st62{fill:url(#SVGID_54_);} + .st63{fill:url(#SVGID_55_);} + .st64{fill:url(#SVGID_56_);} + .st65{fill:url(#SVGID_57_);} + .st66{fill:url(#SVGID_58_);} + .st67{opacity:0.4;fill:url(#SVGID_59_);} + .st68{fill:url(#SVGID_60_);} + .st69{fill:url(#SVGID_61_);} + .st70{fill:url(#SVGID_62_);} + .st71{fill:url(#SVGID_63_);} + .st72{fill:url(#SVGID_64_);} + .st73{fill:url(#SVGID_65_);} + .st74{fill:url(#SVGID_66_);} + .st75{fill:url(#SVGID_67_);} + .st76{fill:url(#SVGID_68_);} + .st77{fill:url(#SVGID_69_);} + .st78{fill:url(#SVGID_70_);} + .st79{fill:url(#SVGID_71_);} + .st80{fill:url(#SVGID_72_);} + .st81{fill:url(#SVGID_73_);} + .st82{fill:url(#SVGID_74_);} + .st83{fill:url(#SVGID_75_);} + .st84{fill:url(#SVGID_76_);} + .st85{fill:url(#SVGID_77_);} + .st86{fill:url(#SVGID_78_);} + .st87{fill:url(#SVGID_79_);} + .st88{fill:url(#SVGID_80_);} + .st89{fill:url(#SVGID_81_);} + .st90{fill:url(#SVGID_82_);} + .st91{fill:url(#SVGID_83_);} + .st92{fill:url(#SVGID_84_);} + .st93{fill:url(#SVGID_85_);} + .st94{fill:url(#SVGID_86_);} + .st95{opacity:0.4;fill:url(#SVGID_87_);} + .st96{fill:url(#SVGID_88_);} + .st97{fill:url(#SVGID_89_);} + .st98{fill:url(#SVGID_90_);} + .st99{display:inline;fill:url(#SVGID_91_);} + .st100{display:inline;fill:url(#SVGID_92_);} + .st101{fill:url(#SVGID_93_);} + .st102{fill:url(#SVGID_94_);} + .st103{opacity:0.4;fill:url(#SVGID_95_);} + .st104{fill:url(#SVGID_96_);} + .st105{fill:url(#SVGID_97_);} + .st106{fill:url(#SVGID_98_);} + .st107{fill:url(#SVGID_99_);} + .st108{fill:url(#SVGID_100_);} + .st109{fill:url(#SVGID_101_);} + .st110{display:inline;fill:url(#SVGID_102_);} + .st111{display:inline;fill:url(#SVGID_103_);} + .st112{fill:url(#SVGID_104_);} + .st113{fill:url(#SVGID_105_);} + .st114{fill:url(#SVGID_106_);} + .st115{fill:url(#SVGID_107_);} + .st116{fill:url(#SVGID_108_);} + .st117{opacity:0.4;fill:url(#SVGID_109_);} + .st118{fill:url(#SVGID_110_);} + .st119{fill:url(#SVGID_111_);} + .st120{fill:url(#SVGID_112_);} + .st121{fill:url(#SVGID_113_);} + .st122{fill:url(#SVGID_114_);} + .st123{opacity:0.4;fill:url(#SVGID_115_);} + .st124{fill:url(#SVGID_116_);} + .st125{fill:url(#SVGID_117_);} + .st126{fill:url(#SVGID_118_);} + .st127{fill:url(#SVGID_119_);} + .st128{fill:url(#SVGID_120_);} + .st129{fill:url(#SVGID_121_);} + .st130{fill:url(#SVGID_122_);} +</style><switch + id="switch6"><g + i:extraneous="self" + id="g8"><g + id="Settings_Active"><circle + class="st24" + cx="159.7" + cy="133.4" + r="101.9" + id="circle1230" /><linearGradient + id="SVGID_119_" + gradientUnits="userSpaceOnUse" + x1="115.9317" + y1="254.1836" + x2="256.3852" + y2="-133.5267"><stop + offset="0" + style="stop-color:#8BC53F" + id="stop1233" /><stop + offset="2.015080e-02" + style="stop-color:#7CCB56;stop-opacity:0.9678" + id="stop1235" /><stop + offset="6.089833e-02" + style="stop-color:#62D67D;stop-opacity:0.9028" + id="stop1237" /><stop + offset="0.1057" + style="stop-color:#4BDFA0;stop-opacity:0.8312" + id="stop1239" /><stop + offset="0.1543" + style="stop-color:#38E7BE;stop-opacity:0.7537" + id="stop1241" /><stop + offset="0.2077" + style="stop-color:#28EED6;stop-opacity:0.6684" + id="stop1243" /><stop + offset="0.2681" + style="stop-color:#1CF3E8;stop-opacity:0.572" + id="stop1245" /><stop + offset="0.3394" + style="stop-color:#13F6F5;stop-opacity:0.4581" + id="stop1247" /><stop + offset="0.4323" + style="stop-color:#0EF8FD;stop-opacity:0.3098" + id="stop1249" /><stop + offset="0.6264" + style="stop-color:#0DF9FF;stop-opacity:0" + id="stop1251" /></linearGradient><circle + class="st127" + cx="159.7" + cy="133.4" + r="101.9" + id="circle1253" /><linearGradient + id="SVGID_120_" + gradientUnits="userSpaceOnUse" + x1="4.0481" + y1="287.9492" + x2="320.4859" + y2="-15.4029" + gradientTransform="matrix(1 5.464556e-03 -5.464556e-03 1 -2.0192 -3.0212)"><stop + offset="0" + style="stop-color:#59FF7F" + id="stop1256" /><stop + offset="1" + style="stop-color:#6BFBFF" + id="stop1258" /></linearGradient><path + class="st128" + d="M160,238.8c-0.2,0-0.4,0-0.6,0c-58-0.3-104.9-47.7-104.6-105.7C55.2,75.3,102.3,28.5,160,28.5 c0.2,0,0.4,0,0.6,0c58,0.3,104.9,47.7,104.6,105.7l0,0C264.8,192,217.7,238.8,160,238.8z M160,32.2 c-55.7,0-101.2,45.2-101.5,100.9c-0.3,55.9,45,101.7,100.9,102c0.2,0,0.4,0,0.6,0c55.7,0,101.2-45.2,101.5-100.9 c0.3-55.9-45-101.7-100.9-102C160.4,32.2,160.2,32.2,160,32.2z" + id="path1260" /><g + id="g1262"><text + transform="matrix(1 0 0 1 75.4379 284.7129)" + class="st4 st5 st6 st7" + id="text1264">SETTINGS</text> +<g + id="g1266"><g + id="g1268"><g + id="g1270"><linearGradient + id="SVGID_121_" + gradientUnits="userSpaceOnUse" + x1="79.1804" + y1="226.0817" + x2="282.752" + y2="-4.8609"><stop + offset="0" + style="stop-color:#59FF7F" + id="stop1273" /><stop + offset="1" + style="stop-color:#6BFBFF" + id="stop1275" /></linearGradient><path + class="st129" + d="M159.9,163.9c-16.3,0-29.5-13.2-29.5-29.4s13.2-29.4,29.5-29.4v3.9c-14.1,0-25.5,11.4-25.5,25.5 c0,14,11.5,25.5,25.5,25.5c14.1,0,25.6-11.4,25.6-25.5h3.9C189.4,150.7,176.2,163.9,159.9,163.9z" + id="path1277" /></g><g + id="g1279"><linearGradient + id="SVGID_122_" + gradientUnits="userSpaceOnUse" + x1="79.2457" + y1="226.1393" + x2="282.8174" + y2="-4.8033"><stop + offset="0" + style="stop-color:#59FF7F" + id="stop1282" /><stop + offset="1" + style="stop-color:#6BFBFF" + id="stop1284" /></linearGradient><path + class="st130" + d="M171.7,197.4h-23.4c-2.2,0-4-1.8-4-3.9V181c-2-0.7-4-1.5-6-2.5l-8.8,8.8c-1.5,1.5-4,1.5-5.6-0.1 l-16.6-16.6c-1.6-1.6-1.6-4.1-0.1-5.6l8.7-8.7c-1-2-1.8-4-2.5-6.1h-12.3c-2.2,0-3.9-1.8-3.9-4v-23.4c0-2.2,1.8-4,3.9-4h12.3 c0.9-2.6,1.9-5.1,3.2-7.4l3.5,1.8c-1.4,2.6-2.5,5.3-3.4,8.1l-0.4,1.4h-15.2l0,23.5l15.2,0.1l0.4,1.4c0.9,2.8,2,5.5,3.4,8 l0.7,1.3L110,167.8l16.6,16.6l10.9-10.8l1.3,0.7c2.6,1.4,5.2,2.5,8,3.3l1.4,0.4v15.4l23.5,0l0.1-15.4l1.4-0.4 c2.7-0.8,5.4-1.9,7.9-3.3l1.3-0.7l10.9,10.9l16.6-16.6l-10.8-11l0.7-1.3c1.4-2.6,2.5-5.2,3.3-7.9l0.4-1.4h15.4l0-23.5 l-15.3-0.1l-0.4-1.4c-0.8-2.8-1.9-5.5-3.3-8l-0.7-1.3l10.8-10.8l-16.6-16.6l-10.8,10.7l-1.3-0.7c-2.6-1.4-5.3-2.5-8.1-3.4 l-1.4-0.4V75.6l-23.5,0l-0.1,15.1l-1.4,0.4c-2.8,0.9-5.6,2-8.1,3.4l-1.3,0.7l-10.7-10.7L107.2,104c-1.5-1.5-1.5-4,0.1-5.6 l16.5-16.5c0.8-0.8,1.8-1.3,2.9-1.2c1,0,2,0.4,2.7,1.1l8.7,8.6c2-1,4-1.8,6.2-2.5V75.6c0-2.2,1.8-3.9,4-3.9h23.4 c2.2,0,4,1.8,4,3.9v12.3c2.1,0.7,4.1,1.6,6.1,2.5l8.7-8.7c0.7-0.7,1.7-1.1,2.7-1.1h0c1.1,0,2.1,0.4,2.9,1.2l16.6,16.6 c0.8,0.8,1.2,1.8,1.2,2.9c0,1-0.4,2-1.1,2.7l-8.8,8.8c1,2,1.8,4,2.5,6h12.4c2.2,0,3.9,1.8,3.9,4v23.4c0,2.2-1.8,4-3.9,4 h-12.5c-0.7,2-1.5,4-2.5,6l8.9,8.9c1.5,1.5,1.5,4-0.1,5.6l-16.6,16.6c-0.8,0.8-1.8,1.2-2.9,1.2h0c-1,0-2-0.4-2.7-1.1 l-8.9-8.9c-1.9,1-3.9,1.8-5.9,2.5v12.5C175.7,195.6,173.9,197.4,171.7,197.4z" + id="path1286" /></g></g></g></g></g></g></switch></svg>
\ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100755 index 0000000..fc397f1 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,77 @@ +########################################################################### +# Copyright (C) 2020 MERA +# +# 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. +########################################################################### + +cmake_minimum_required(VERSION 2.8) + +set(TARGETS_ARBINDER cloudproxy-service) + +INCLUDE(FindThreads) +FIND_PACKAGE(Threads) + +pkg_check_modules(cp_binding_depends afb-daemon glib-2.0 gio-2.0 gio-unix-2.0 json-c) +set(binding_cp_sources + cloudproxy-bindings.cpp + utils.h + hmi-debug.h + ClientManager.h + ClientManager.cpp +) + +link_libraries(-Wl,--as-needed -Wl,--gc-sections -Wl,--no-undefined) + +set(AZURE_DEP_LIBS iothub_client) + +add_library(${TARGETS_ARBINDER} MODULE ${binding_cp_sources}) + +include_directories(${CMAKE_SYSROOT}/usr/include/azureiot) + +target_compile_options(${TARGETS_ARBINDER} PRIVATE ${cp_binding_depends_CFLAGS}) +if(DEFINED DEBUGMODE) + target_compile_options(${TARGETS_ARBINDER} PRIVATE -g -O0) +else(DEFINED DEBUGMODE) + target_compile_options(${TARGETS_ARBINDER} PRIVATE -g -O2) +endif(DEFINED DEBUGMODE) + +target_link_libraries(${TARGETS_ARBINDER} ${CMAKE_THREAD_LIBS_INIT} ${link_libraries} ${cp_binding_depends_LIBRARIES} ${AZURE_DEP_LIBS}) + + +set_target_properties(${TARGETS_ARBINDER} PROPERTIES + PREFIX "" + LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/export.map" + ) + +target_compile_definitions(${TARGETS_ARBINDER} + PRIVATE + AFB_BINDING_VERSION=3 + _GNU_SOURCE) + + +if(NOT EXISTS ${PROJECT_BINARY_DIR}/package) + add_custom_command(TARGET ${TARGETS_ARBINDER} POST_BUILD + COMMAND cp -rf ${PROJECT_SOURCE_DIR}/package ${PROJECT_BINARY_DIR} + ) +endif() + +add_custom_command(TARGET ${TARGETS_ARBINDER} POST_BUILD + COMMAND mkdir -p ${PROJECT_BINARY_DIR}/package/root/lib + COMMAND cp -rf ${PROJECT_BINARY_DIR}/src/${TARGETS_ARBINDER}.so ${PROJECT_BINARY_DIR}/package/root/lib + COMMAND mkdir -p ${PROJECT_BINARY_DIR}/package/root/etc + COMMAND cp -f ${PROJECT_SOURCE_DIR}/src/config_sample.ini ${PROJECT_BINARY_DIR}/package/root/etc/config.ini +) + +add_custom_target(widget DEPENDS ${PROJECT_BINARY_DIR}/package/root + COMMAND wgtpkg-pack -f -o ${PROJECT_BINARY_DIR}/${TARGETS_ARBINDER}.wgt ${PROJECT_BINARY_DIR}/package/root +) diff --git a/src/ClientManager.cpp b/src/ClientManager.cpp new file mode 100755 index 0000000..74d672d --- /dev/null +++ b/src/ClientManager.cpp @@ -0,0 +1,215 @@ +/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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 "ClientManager.h"
+#include "hmi-debug.h"
+
+#include <set>
+#include <json-c/json.h>
+
+// static
+void ClientManager::cbRemoveClientCtx(void *data)
+{
+ ClientManager::instance().removeClient((ClientManager::ClientCtx*)data);
+}
+
+
+// static
+ClientManager& ClientManager::instance()
+{
+ static ClientManager instance;
+ return instance;
+}
+
+
+ClientManager::~ClientManager()
+{
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+ for (auto cl : m_clients)
+ delete cl.second;
+}
+
+
+ClientManager::ClientCtx* ClientManager::addClient(afb_req_t req, const std::string& appid)
+{
+ ClientCtx* ctx = (ClientCtx*)afb_req_context_get(req);
+ if (!ctx)
+ {
+ HMI_NOTICE("cloudproxy-service", "create new session for %s", appid.c_str());
+ ctx = new ClientCtx{appid, afb_api_make_event(req->api, appid.c_str())};
+ afb_req_session_set_LOA(req, 1);
+ afb_req_context_set(req, ctx, cbRemoveClientCtx);
+ }
+
+ m_clients[appid] = ctx;
+ return ctx;
+}
+
+
+void ClientManager::removeClient(ClientCtx* ctx)
+{
+ if(!ctx)
+ {
+ HMI_ERROR("cloudproxy-service", "data is nullptr");
+ return;
+ }
+
+ HMI_NOTICE("cloudproxy-service", "remove app %s", ctx->appid.c_str());
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+
+ auto it = m_clients.find(ctx->appid);
+ if (it != m_clients.end())
+ {
+ delete it->second;
+ m_clients.erase(it);
+ }
+}
+
+
+bool ClientManager::handleRequest(afb_req_t request, const std::string& verb, const std::string& appid)
+{
+ HMI_NOTICE("cloudproxy-service", "handleRequest: verb='%s', appid='%s'", verb.c_str(), appid.c_str());
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+
+ if (appid.empty())
+ {
+ HMI_ERROR("cloudproxy-service", "appid is empty");
+ return false;
+ }
+
+ auto client_it = m_clients.find(appid);
+ if (verb != "subscribe" && client_it == m_clients.end())
+ {
+ HMI_NOTICE("cloudproxy-service", "client with appid '%s' is not registered", appid.c_str());
+ return false;
+ }
+
+ if (verb == "subscribe")
+ {
+ const char *value = afb_req_value(request, "event");
+ if(!value)
+ {
+ HMI_ERROR("cloudproxy-service", "Can't subscribe: event name is not defined");
+ return false;
+ }
+ std::string req_event{value};
+ HMI_NOTICE("cloudproxy-service", "subscribe req: appid '%s', event '%s'", appid.c_str(), req_event.c_str());
+
+ if (!isSupportedEvent(req_event))
+ {
+ HMI_ERROR("cloudproxy-service", "event '%s' is not supported", req_event.c_str());
+ return false;
+ }
+
+ ClientCtx* ctx = addClient(request, appid);
+ ctx->subs_events.insert(req_event);
+ if(!ctx->subscription)
+ {
+ if(afb_req_subscribe(request, ctx->event) == 0)
+ {
+ ctx->subscription = true;
+ }
+ else
+ {
+ HMI_ERROR("cloudproxy-service", "API error in afb_req_subscribe");
+ return false;
+ }
+ }
+
+ return true;
+ }
+ else if (verb == "unsubscribe")
+ {
+ const char *value = afb_req_value(request, "event");
+ if(!value)
+ {
+ HMI_ERROR("cloudproxy-service", "Can't unsubscribe: event name is not defined");
+ return false;
+ }
+ std::string req_event{value};
+ HMI_NOTICE("cloudproxy-service", "unsubscribe req: appid '%s', event '%s'", appid.c_str(), req_event.c_str());
+
+ ClientCtx* ctx{client_it->second};
+ ctx->subs_events.erase(req_event);
+
+ if(ctx->subs_events.empty())
+ {
+ if (afb_req_unsubscribe(request, ctx->event) != 0)
+ HMI_ERROR("cloudproxy-service", "API error in afb_req_unsubscribe");
+
+ ctx->subscription = false;
+ }
+
+ return true;
+ }
+
+ HMI_NOTICE("cloudproxy-service", "Unsupported verb '%s'", verb.c_str());
+ return false;
+}
+
+
+bool ClientManager::isSupportedEvent(const std::string& event)
+{
+ const std::set<std::string> event_list{
+ "sendMessageConfirmation",
+ "receivedMessage"
+ };
+
+ return (event_list.end() != event_list.find(event));
+}
+
+
+bool ClientManager::emitReceivedMessage(const std::string& appid, const std::string& data)
+{
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+
+ auto it = m_clients.find(appid);
+ if (it == m_clients.end())
+ {
+ HMI_WARNING("cloudproxy-service", "Client with appid '%s' is not present in list", appid.c_str());
+ // print app list
+ for (const auto& i : m_clients)
+ HMI_DEBUG("cloudproxy-service", "Client list: appid '%s' - '%s'", i.first.c_str(), i.second->appid.c_str());
+
+ return false;
+ }
+
+ json_object* push_obj = json_object_new_object();
+ json_object_object_add(push_obj, "type", json_object_new_string("receivedMessage"));
+ json_object_object_add(push_obj, "data", json_object_new_string(data.c_str()));
+ return (0 == afb_event_push(it->second->event, push_obj));
+}
+
+bool ClientManager::emitSendMessageConfirmation(const std::string& appid, bool result)
+{
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+
+ auto it = m_clients.find(appid);
+ if (it == m_clients.end())
+ {
+ HMI_WARNING("cloudproxy-service", "Client with appid '%s' is not present in list", appid.c_str());
+ // print app list
+ for (const auto& i : m_clients)
+ HMI_DEBUG("cloudproxy-service", "Client list: appid '%s' - '%s'", i.first.c_str(), i.second->appid.c_str());
+
+ return false;
+ }
+
+ json_object* push_obj = json_object_new_object();
+ json_object_object_add(push_obj, "type", json_object_new_string("sendMessageConfirmation"));
+ json_object_object_add(push_obj, "result", json_object_new_boolean(result));
+ return (0 == afb_event_push(it->second->event, push_obj));
+}
diff --git a/src/ClientManager.h b/src/ClientManager.h new file mode 100755 index 0000000..a1038e1 --- /dev/null +++ b/src/ClientManager.h @@ -0,0 +1,59 @@ +/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <afb/afb-binding.h>
+
+#include <map>
+#include <unordered_set>
+#include <mutex>
+#include <string>
+
+class ClientManager
+{
+public:
+ struct ClientCtx
+ {
+ std::string appid;
+ afb_event_t event;
+ std::unordered_set<std::string> subs_events;
+ bool subscription{false};
+ };
+
+ ClientManager() = default;
+ ~ClientManager();
+ ClientManager(const ClientManager&) = delete;
+ void operator=(const ClientManager&) = delete;
+
+ bool handleRequest(afb_req_t request, const std::string& verb, const std::string& appid);
+ bool emitReceivedMessage(const std::string& appid, const std::string& data);
+ bool emitSendMessageConfirmation(const std::string& appid, bool result);
+
+ static ClientManager& instance();
+
+ // don't use it directly
+ static void cbRemoveClientCtx(void *data);
+
+protected:
+ ClientCtx* addClient(afb_req_t req, const std::string& appid);
+ void removeClient(ClientCtx* ctx);
+ bool isSupportedEvent(const std::string& event);
+
+protected:
+ std::mutex m_mutex;
+ std::map<std::string, ClientCtx*> m_clients;
+};
diff --git a/src/cloudproxy-bindings.cpp b/src/cloudproxy-bindings.cpp new file mode 100755 index 0000000..1a40730 --- /dev/null +++ b/src/cloudproxy-bindings.cpp @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2020 MERA + * + * 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 <afb/afb-binding.h> +#include <json-c/json.h> + +#include <memory> +#include <algorithm> + +#include <iothub.h> +#include <iothub_device_client.h> +#include <iothub_client_options.h> +#include <iothub_message.h> +#include <iothubtransportmqtt.h> +#include <azure_c_shared_utility/threadapi.h> // ThreadAPI_Sleep() +#include <azure_c_shared_utility/tickcounter.h> // tickcounter_ms_t + +#include "hmi-debug.h" +#include "utils.h" +#include "ClientManager.h" + +#include <glib.h> + +static const char* API_name{"cloudproxy"}; + +static std::string g_connectionString; +static IOTHUB_DEVICE_CLIENT_HANDLE g_device_handle{nullptr}; +static bool g_iot_inited{false}; + + + +static utils::scope_exit g_destroy([]{ + if (g_iot_inited) + { + if (g_device_handle) + IoTHubDeviceClient_Destroy(g_device_handle); + + IoTHub_Deinit(); + } +}); + + +static bool loadConf() +{ + const char* CONF_FILE_PATH{"/etc/config.ini"}; + const char* DEFAULT_ETC_PATH{"/etc/cloudproxy-service"}; + const char *p = getenv("AFM_APP_INSTALL_DIR"); + if(!p) + { + HMI_ERROR("cloudproxy-service", "AFM_APP_INSTALL_DIR is not set, try to find conf in %s", DEFAULT_ETC_PATH); + p = DEFAULT_ETC_PATH; + } + + std::string conf_path = p; + conf_path += CONF_FILE_PATH; + + g_autoptr(GKeyFile) conf_file = g_key_file_new(); + g_autoptr(GError) error = nullptr; + + if (!conf_file || !g_key_file_load_from_file(conf_file, conf_path.c_str(), G_KEY_FILE_NONE, &error)) + { + HMI_ERROR("cloudproxy-service", "can't load file %s", conf_path.c_str()); + return false; + } + + g_autofree gchar *value = g_key_file_get_string(conf_file, "AzureCloudConnection", "DeviceConnectionString", &error); + if (value == nullptr) + { + HMI_ERROR("cloudproxy-service", "can't read DeviceConnectionString from config %d", conf_path.c_str()); + return false; + } + + g_connectionString = value; + if (g_connectionString.empty()) + { + HMI_ERROR("cloudproxy-service", "DeviceConnectionString is empty"); + return false; + } + + return true; +} + + +//-------------- Iot callbacks +static void connection_status_callback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void* user_context) +{ + HMI_NOTICE("cloudproxy-service", "%s called: result %d, reason %d", __FUNCTION__, result, reason); + + (void)reason; + (void)user_context; + // This sample DOES NOT take into consideration network outages. + if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED && reason == IOTHUB_CLIENT_CONNECTION_OK) + { + HMI_NOTICE("cloudproxy-service", "The device client is connected to iothub"); + } + else + { + HMI_NOTICE("cloudproxy-service", "The device client has been disconnected"); + } +} + +static IOTHUBMESSAGE_DISPOSITION_RESULT receive_msg_callback(IOTHUB_MESSAGE_HANDLE message, void* user_context) +{ + HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__); + (void)user_context; + const char* messageId; + const char* correlationId; + + IOTHUBMESSAGE_CONTENT_TYPE content_type = IoTHubMessage_GetContentType(message); + + if (content_type == IOTHUBMESSAGE_BYTEARRAY) + { + const unsigned char* buff_msg; + size_t buff_len; + + if (IoTHubMessage_GetByteArray(message, &buff_msg, &buff_len) != IOTHUB_MESSAGE_OK) + { + HMI_ERROR("cloudproxy-service", "Failure retrieving byte array message"); + } + else + { + HMI_NOTICE("cloudproxy-service", "Received Binary message, size %d, data '%.*s'", (int)buff_len, (int)buff_len, buff_msg); + } + + const char* app_id = IoTHubMessage_GetProperty(message, "application_id"); + HMI_NOTICE("cloudproxy-service", "Received property 'application_id': %s", (app_id ? app_id : "<unavailable>")); + + if (app_id && app_id[0]) + ClientManager::instance().emitReceivedMessage(app_id, std::string((const char*)buff_msg, buff_len)); + else + HMI_ERROR("cloudproxy-service", "Can't emit SendMessageConfirmation: appid is not valid"); + } + else if (content_type == IOTHUBMESSAGE_STRING) + { + const char* string_msg = IoTHubMessage_GetString(message); + if (string_msg == nullptr) + { + HMI_NOTICE("cloudproxy-service", "Failure retrieving String message"); + } + else + { + HMI_NOTICE("cloudproxy-service", "Received String message, size %d, data '%s'", strlen(string_msg), string_msg); + } + + const char* app_id = IoTHubMessage_GetProperty(message, "application_id"); + HMI_NOTICE("cloudproxy-service", "Received property 'application_id': %s", (app_id ? app_id : "<unavailable>")); + + if (app_id && app_id[0]) + ClientManager::instance().emitReceivedMessage(app_id, string_msg); + else + HMI_ERROR("cloudproxy-service", "Can't emit SendMessageConfirmation: appid is not valid"); + } + else + { + HMI_ERROR("cloudproxy-service", "Unsupported message content type"); + } + + return IOTHUBMESSAGE_ACCEPTED; +} + + +static int device_method_callback(const char* method_name, const unsigned char* payload, size_t size, unsigned char** response, size_t* resp_size, void* userContextCallback) +{ + HMI_NOTICE("cloudproxy-service", "%s called, method_name %s", __FUNCTION__, method_name); + + const char* device_id = (const char*)userContextCallback; + char* end = nullptr; + int newInterval; + + int status = 501; + const char* RESPONSE_STRING = "{ \"Response\": \"Unknown method requested.\" }"; + + HMI_NOTICE("cloudproxy-service", "Device Method called for device %s", device_id); + HMI_NOTICE("cloudproxy-service", "Device Method name: %s", method_name); + HMI_NOTICE("cloudproxy-service", "Device Method payload: %.*s", (int)size, (const char*)payload); + + HMI_NOTICE("cloudproxy-service", "Response status: %d", status); + HMI_NOTICE("cloudproxy-service", "Response payload: %s", RESPONSE_STRING); + + *resp_size = strlen(RESPONSE_STRING); + if ((*response = (unsigned char*)malloc(*resp_size)) == NULL) + { + status = -1; + } + else + { + memcpy(*response, RESPONSE_STRING, *resp_size); + } + + return status; +} + + +static void send_confirm_callback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback) +{ + HMI_NOTICE("cloudproxy-service", "%s called, result %d", __FUNCTION__, result); + (void)userContextCallback; + // When a message is sent this callback will get invoked + + HMI_NOTICE("cloudproxy-service", "Confirmation callback result %s", MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result)); + + const char* appid = (const char*)userContextCallback; + if (!appid || !appid[0]) + { + HMI_ERROR("cloudproxy-service", "Confirmation callback: appid is not set"); + + if (userContextCallback) + free(userContextCallback); + + return; + } + + ClientManager::instance().emitSendMessageConfirmation(appid, result == IOTHUB_CLIENT_CONFIRMATION_OK); + free(userContextCallback); +} +//-------------- + +//-------------- help functions +static bool createConnection() +{ + HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__); + + if (g_device_handle) + { + HMI_WARNING("cloudproxy-service", "connection already created"); + return true; + } + + g_device_handle = IoTHubDeviceClient_CreateFromConnectionString(g_connectionString.c_str(), MQTT_Protocol); + if (!g_device_handle) + { + HMI_ERROR("cloudproxy-service", "Failure creating IoTHubDeviceClient device"); + return false; + } + + bool traceOn = false; + IoTHubDeviceClient_SetOption(g_device_handle, OPTION_LOG_TRACE, &traceOn); + IoTHubDeviceClient_SetConnectionStatusCallback(g_device_handle, connection_status_callback, nullptr); + IoTHubDeviceClient_SetMessageCallback(g_device_handle, receive_msg_callback, nullptr); + IoTHubDeviceClient_SetDeviceMethodCallback(g_device_handle, device_method_callback, nullptr); + + tickcounter_ms_t ms_delay = 10; + IoTHubDeviceClient_SetOption(g_device_handle, OPTION_DO_WORK_FREQUENCY_IN_MS, &ms_delay); // DoWork multithread + + return true; +} + +//-------------- + + +static void pingSample(afb_req_t request) +{ + static int pingcount = 0; + afb_req_success_f(request, json_object_new_int(pingcount), "Ping count = %d", pingcount); + HMI_NOTICE("cloudproxy-service", "Verbosity macro at level notice invoked at ping invocation count = %d", pingcount); + pingcount++; +} + + +static bool initAzureSdk() +{ + //Allow program to try to establish connection several times + if (!g_iot_inited) + { + if(IoTHub_Init()) + { + HMI_ERROR("cloudproxy-service","Azure IoTHub_Init() failed"); + } + else + { + g_iot_inited = true; + } + } + + return g_iot_inited; +} + +static void sendMessage(afb_req_t request) +{ + HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__); + + json_object* object = afb_req_json(request); + if (!object) + { + HMI_ERROR("cloudproxy-service", "Can't parse request"); + afb_req_fail_f(request, "failed", "called %s", __FUNCTION__); + return; + } + + const std::string appid{utils::get_application_id(request)}; + std::string data; + json_object *obj_data; + if(!json_object_object_get_ex(object, "data", &obj_data)) + { + HMI_ERROR("cloudproxy-service", "can't obtain application_id or data from request"); + return; + } + data = json_object_get_string(obj_data); + + if (!g_device_handle && !createConnection()) + { + HMI_ERROR("cloudproxy-service", "Can't create connection to cloud"); + afb_req_fail_f(request, "failed", "called %s", __FUNCTION__); + return; + } + + IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(data.c_str()); + + utils::scope_exit message_handle_destroy([&message_handle](){ + // The message is copied to the sdk, so the we can destroy it + if (message_handle) + IoTHubMessage_Destroy(message_handle); + }); + + if (!message_handle) + { + HMI_ERROR("cloudproxy-service", "Can't create IoTHubMessage message"); + afb_req_fail_f(request, "failed", "called %s", __FUNCTION__); + return; + } + + IoTHubMessage_SetProperty(message_handle, "application_id", appid.c_str()); + + if (IoTHubDeviceClient_SendEventAsync(g_device_handle, message_handle, send_confirm_callback, strdup(appid.c_str()))) + { + HMI_ERROR("cloudproxy-service", "Can't send IoTHubMessage message"); + afb_req_fail_f(request, "failed", "called %s", __FUNCTION__); + return; + } + + afb_req_success(request, json_object_new_object(), __FUNCTION__); +} + + +static void subscribe(afb_req_t request) +{ + HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__); + + std::string req_appid{utils::get_application_id(request)}; + if(req_appid.empty()) + { + HMI_ERROR("cloudproxy-service", "Can't subscribe: empty appid"); + afb_req_fail_f(request, "%s failed: application_id is not defined in request", __FUNCTION__); + return; + } + + if (!ClientManager::instance().handleRequest(request, __FUNCTION__, req_appid)) + { + HMI_ERROR("cloudproxy-service", "%s failed in handleRequest", __FUNCTION__); + afb_req_fail_f(request, "%s failed", __FUNCTION__); + } + else + { + afb_req_success(request, json_object_new_object(), __FUNCTION__); + } +} + +static void unsubscribe(afb_req_t request) +{ + HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__); + + std::string req_appid{utils::get_application_id(request)}; + if(req_appid.empty()) + { + HMI_ERROR("cloudproxy-service", "Can't unsubscribe: empty appid"); + afb_req_fail_f(request, "%s failed: application_id is not defined in request", __FUNCTION__); + return; + } + + if (!ClientManager::instance().handleRequest(request, __FUNCTION__, req_appid)) + { + HMI_ERROR("cloudproxy-service", "%s failedin handleRequest", __FUNCTION__); + afb_req_fail_f(request, "%s failed", __FUNCTION__); + } + else + { + afb_req_success(request, json_object_new_object(), __FUNCTION__); + } +} + +/* + * array of the verbs exported to afb-daemon + */ +static const afb_verb_t verbs[]= { + /* VERB'S NAME FUNCTION TO CALL */ + { .verb="ping", .callback=pingSample }, + { .verb="sendMessage", .callback=sendMessage }, + { .verb="subscribe", .callback=subscribe }, + { .verb="unsubscribe", .callback=unsubscribe }, + {nullptr } /* marker for end of the array */ +}; + + +static int preinit(afb_api_t api) +{ + HMI_NOTICE("cloudproxy-service", "binding preinit (was register)"); + + if (!loadConf()) + { + HMI_ERROR("cloudproxy-service", "Can't load configuration file or configuration is wrong"); + return -1; + } + + if (!initAzureSdk()) + { + HMI_ERROR("cloudproxy-service", "Can't initialize Azure SDK"); + return -1; + } + + return 0; +} + + +static int init(afb_api_t api) +{ + HMI_NOTICE("cloudproxy-service","binding init"); + return (g_iot_inited ? 0 : -1); +} + +const afb_binding_t afbBindingExport = { + .api = "cloudproxy", + .specification = nullptr, + .info = nullptr, + .verbs = verbs, + .preinit = preinit, + .init = init, + .onevent = nullptr +}; diff --git a/src/config_sample.ini b/src/config_sample.ini new file mode 100644 index 0000000..fa97cd7 --- /dev/null +++ b/src/config_sample.ini @@ -0,0 +1,3 @@ +[AzureCloudConnection] +DeviceConnectionString=My=Azure=Device=Connection=String + diff --git a/src/export.map b/src/export.map new file mode 100755 index 0000000..f3961c0 --- /dev/null +++ b/src/export.map @@ -0,0 +1 @@ +{ global: afbBindingV*; local: *; };
\ No newline at end of file diff --git a/src/hmi-debug.h b/src/hmi-debug.h new file mode 100755 index 0000000..c8e8638 --- /dev/null +++ b/src/hmi-debug.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * 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 __HMI_DEBUG_H__ +#define __HMI_DEBUG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <time.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> + +#include <sys/types.h> // gettid() +#include <unistd.h> +#include <sys/syscall.h> + + enum LOG_LEVEL{ + LOG_LEVEL_NONE = 0, + LOG_LEVEL_ERROR, + LOG_LEVEL_WARNING, + LOG_LEVEL_NOTICE, + LOG_LEVEL_INFO, + LOG_LEVEL_DEBUG, + LOG_LEVEL_MAX = LOG_LEVEL_DEBUG + }; + +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + +#define HMI_ERROR(prefix, args,...) _HMI_LOG(LOG_LEVEL_ERROR, __FILENAME__, __FUNCTION__, __LINE__, prefix, args, ##__VA_ARGS__) +#define HMI_WARNING(prefix, args,...) _HMI_LOG(LOG_LEVEL_WARNING, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_NOTICE(prefix, args,...) _HMI_LOG(LOG_LEVEL_NOTICE, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_INFO(prefix, args,...) _HMI_LOG(LOG_LEVEL_INFO, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) +#define HMI_DEBUG(prefix, args,...) _HMI_LOG(LOG_LEVEL_DEBUG, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__) + + static char ERROR_FLAG[6][20] = {"NONE", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"}; + + static void _HMI_LOG(enum LOG_LEVEL level, const char* file, const char* func, const int line, const char* prefix, const char* log, ...) + { + //SA: WARN: const int log_level = (getenv("USE_HMI_DEBUG") == NULL)?LOG_LEVEL_ERROR:atoi(getenv("USE_HMI_DEBUG")); + const int log_level = LOG_LEVEL_MAX; + if(log_level < level) + { + return; + } + + char *message; + struct timespec tp; + unsigned int time; + + clock_gettime(CLOCK_REALTIME, &tp); + time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000); + + va_list args; + va_start(args, log); + if (log == NULL || vasprintf(&message, log, args) < 0) + message = NULL; + fprintf(stderr, "[%10.3f] [%s %s] [%s, %s(), Line:%d] [thread %llx]>>> %s \n", time / 1000.0, prefix, ERROR_FLAG[level], file, func, line, (unsigned long long)syscall(__NR_gettid), message); + va_end(args); + free(message); + } + +#ifdef __cplusplus +} +#endif +#endif //__HMI_DEBUG_H__ diff --git a/src/utils.h b/src/utils.h new file mode 100755 index 0000000..9069c4a --- /dev/null +++ b/src/utils.h @@ -0,0 +1,54 @@ +/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <functional>
+#include <string>
+
+#include <afb/afb-binding.h>
+
+namespace utils
+{
+ class scope_exit
+ {
+ public:
+ explicit scope_exit(std::function<void()> func)
+ : m_f(func)
+ {}
+ ~scope_exit()
+ {
+ if (!!m_f)
+ m_f();
+ }
+
+ private:
+ std::function<void()> m_f;
+ };
+
+ std::string get_application_id(const afb_req_t request)
+ {
+ char *app_id = afb_req_get_application_id(request);
+ std::string appid;
+ if(app_id)
+ {
+ appid = std::string(app_id);
+ free(app_id);
+ }
+
+ return appid;
+ }
+}
|