diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .gitreview | 5 | ||||
-rw-r--r-- | CMakeLists.txt | 21 | ||||
-rw-r--r-- | LICENSE | 201 | ||||
-rw-r--r-- | README.md | 105 | ||||
-rw-r--r-- | app/CMakeLists.txt | 57 | ||||
-rw-r--r-- | app/afbclient.cpp | 236 | ||||
-rw-r--r-- | app/afbclient.h | 63 | ||||
-rw-r--r-- | app/configuration.cpp | 151 | ||||
-rw-r--r-- | app/configuration.h | 71 | ||||
-rw-r--r-- | app/event.cpp | 86 | ||||
-rw-r--r-- | app/event.h | 32 | ||||
-rw-r--r-- | app/gps.cpp | 89 | ||||
-rw-r--r-- | app/gps.h | 33 | ||||
-rw-r--r-- | app/main.cpp | 158 | ||||
-rw-r--r-- | app/mqttclient.cpp | 68 | ||||
-rw-r--r-- | app/mqttclient.h | 36 | ||||
-rw-r--r-- | app/network.cpp | 119 | ||||
-rw-r--r-- | app/network.h | 27 | ||||
-rwxr-xr-x | autobuild/agl/autobuild | 77 | ||||
-rwxr-xr-x | autobuild/linux/autobuild | 67 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 205 | ||||
-rw-r--r-- | conf.d/wgt/config.xml.in | 18 |
23 files changed, 1926 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..3a6dfb5 --- /dev/null +++ b/.gitreview @@ -0,0 +1,5 @@ +[gerrit] +host=gerrit.automotivelinux.org +port=29418 +project=apps/agl-telematics-demo-recorder +defaultbranch=master diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bf284f7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# 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. +########################################################################### + +CMAKE_MINIMUM_REQUIRED(VERSION 3.3) + +include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake) @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b11c843 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# Telematics Recorder + +## Overview + +The telematics recorder demonstation application reads the OBD-II vehicle speed and +engine speed (RPM) messages using the low-level CAN binding, and sends them as JSON +messages to a MQTT broker for clients to use. + +## Configuration + +The application has several configurable parameters, which can be configured via the +INI style file /etc/xdg/telematics-recorder.conf. The parameters are described in the +following sections. + +### General + +General configuration consists of the following parameters in the "[General]" section: +* log_level +* cellular_enabled +* gps_enabled +* check_online +* device_uuid + +#### log_level + +Sets the debug logging level, acceptable values are 1 or higher. If the parameter is not +present, logging is disabled. The default value is no debug logging. + +#### cellular_enabled + +Enables or disables cellular modem usage, acceptable values are "true" and "false" (case-insensitive). +The logic enabled by this parameter blocks on a check for cellular modem presence at start up, and +sets the modem state to online when detected. The default value is disabled ("false"). + +#### gps_enabled + +Enables or disables GPS usage, acceptable values are "true" and "false" (case-insensitive). +When enabled, the current GPS position will be populated in the events sent to the MQTT broker. +The default value is disabled ("false"). + +#### check_online + +Enables or disables checking for online status, acceptable values are "true" and "false" (case-insensitive). +When enabled, the the current network online status will be checked before attempting to send messages to +the MQTT broker to potentially prevent filling the transmit queue of the MQTT client. +The default value is disabled ("false"). + +#### device_uuid + +A string representing a unique device identifier. It is used to identify the device in the MQTT messages. +If not specified, the default value is "e4bbc0a8-f435-4326-9769-d4a2c9f3c18d". + +### MQTT + +MQTT protocol configuration consists of the following parameters in the "[MQTT]" section: +* broker +* port +* keepalive +* qos +* retain +* username +* password + +#### broker + +MQTT broker hostname. Default value is "iot.eclipse.org". + +#### port + +MQTT broker port. Default value is 1883. + +#### keepalive + +MQTT protocol keepalive time. Default value is 60 seconds. + +#### qos + +MQTT protocol quality of service (QoS) parameter, acceptable values are 0, 1, or 2. +Default value is 0. See "Quality of Service" in [https://mosquitto.org/man/mqtt-7.html](https://mosquitto.org/man/mqtt-7.html) +for a description of the QoS settings. + +#### retain + +MQTT protocol message retain parameter, acceptable values are "true" and "false" (case-insensitive). +Default value is "true". See "Retained Messages" in [https://mosquitto.org/man/mqtt-7.html](https://mosquitto.org/man/mqtt-7.html) +for a description of the message retention mechanism. + +#### username + +MQTT broker username string if required. Default value is none. + +#### password + +MQTT broker password string if required. Default value is none, and only relevant in conjunction +with the username parameter. + +### Event + +Event configuration consists of the following parameters in the "[Event]" section: +* update_period + +#### update_period + +The period in seconds for sending messages to the MQTT broker, acceptable values are 0 or higher. +A value of 0 means send a message to the broker for every CAN event that comes in. The default value is 10. diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..73470d5 --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,57 @@ +########################################################################### +# Copyright 2019 Konsulko Group +# +# Author: Scott Murray <scott.murray@konsulko.com> +# +# 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. +########################################################################### + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_CXX_STANDARD 14) + +find_package(PkgConfig REQUIRED) + +PROJECT_TARGET_ADD(telematics-recorder) + +add_executable(${TARGET_NAME} + main.cpp + configuration.cpp + afbclient.cpp + mqttclient.cpp + network.cpp + event.cpp + gps.cpp + ${RESOURCES} +) + +pkg_check_modules(LIBAFBWSC REQUIRED libafbwsc) + +include_directories( + "${LIBAFBWSC_INCLUDE_DIRS}" +) + +set_target_properties(${TARGET_NAME} PROPERTIES + LABELS "EXECUTABLE" + PREFIX "" + COMPILE_FLAGS "${EXTRAS_CFLAGS} -DFOR_AFB_BINDING" + LINK_FLAGS "${BINDINGS_LINK_FLAG}" + LINK_LIBRARIES "${EXTRAS_LIBRARIES}" + OUTPUT_NAME "${TARGET_NAME}" +) + +target_link_libraries(${TARGET_NAME} + ${LIBAFBWSC_LIBRARIES} + -lmosquitto + -lpthread +) diff --git a/app/afbclient.cpp b/app/afbclient.cpp new file mode 100644 index 0000000..a40f6c9 --- /dev/null +++ b/app/afbclient.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 "afbclient.h" +#include <string> +#include <cstring> +#include <iostream> +#include <mutex> +#include <condition_variable> + +#undef DEBUG +//#define DEBUG + +struct call_data +{ + bool sync; + std::mutex mutex; + std::condition_variable cv; + bool ready; + std::function<void(json_object*)> cb; + json_object *resp; +}; + +static void on_hangup_cb(void *closure, struct afb_wsj1 *wsj) +{ +} + +static void on_call_cb(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg) +{ +} + +static void on_reply_cb(void *closure, struct afb_wsj1_msg *msg) +{ + call_data *data = (call_data*) closure; + struct json_object* reply; + + if(!data) + goto reply_done; + + reply = afb_wsj1_msg_object_j(msg); + if(reply) { +#ifdef DEBUG + std::cerr << __FUNCTION__ << ": reply = " << \ + json_object_to_json_string_ext(reply, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) << \ + std::endl; +#endif + if(data->sync) { + data->resp = reply; + + // Increase reference count since we are going to use + // reply after this callback returns, caller must do a + // put. + json_object_get(reply); + } else if(data->cb != nullptr) { + data->cb(reply); + } + } +reply_done: + if(data->sync) { + // Signal reply is done + { + std::lock_guard<std::mutex> lk(data->mutex); + data->ready = true; + } + data->cv.notify_one(); + } +} + +// +// on_event_cb is inline in afbclient.h +// + +static void *afb_loop_thread(struct sd_event* loop) +{ + for(;;) + sd_event_run(loop, 30000000); +} + +AfbClient::AfbClient(const int port, const std::string &token) +{ + std::string uri; + + if(sd_event_new(&m_afb_loop) < 0) { + std::cerr << __FUNCTION__ << ": Failed to create event loop" << std::endl; + return; + } + + // Initialize interface for websocket + m_itf.on_hangup = on_hangup_cb; + m_itf.on_call = on_call_cb; + m_itf.on_event = on_event_cb; + + uri = "ws://localhost:" + std::to_string(port) + "/api?token=" + token; +#ifdef DEBUG + std::cerr << "Using URI: " << uri << std::endl; +#endif + m_ws = afb_ws_client_connect_wsj1(m_afb_loop, uri.c_str(), &m_itf, this); + if(m_ws) { + m_afb_thread = std::thread(afb_loop_thread, m_afb_loop); + } else { + std::cerr << __FUNCTION__ << ": Failed to create websocket connection" << std::endl; + goto error; + } + + m_valid = true; + return; +error: + if(m_afb_loop) { + sd_event_unref(m_afb_loop); + m_afb_loop = nullptr; + } + return; +} + +AfbClient::~AfbClient(void) +{ + sd_event_unref(m_afb_loop); + afb_wsj1_unref(m_ws); +} + +int AfbClient::call(const std::string &api, const std::string &verb, struct json_object *arg, callback_fn cb) +{ + if(!m_valid) + return -1; + + call_data data; + data.sync = false; + data.cb = cb; + int rc = afb_wsj1_call_j(m_ws, api.c_str(), verb.c_str(), arg, on_reply_cb, (void*) &data); + if(rc < 0) { + std::cerr << __FUNCTION__ << \ + ": Failed to call " << \ + api.c_str() << \ + "/" << \ + verb.c_str() << \ + std::endl; + } + return rc; +} + +int AfbClient::call_sync(const std::string &api, const std::string &verb, struct json_object *arg, struct json_object **resp) +{ + if(!m_valid) + return -1; + + call_data data; + data.sync = true; + data.ready = false; + data.cb = nullptr; + int rc = afb_wsj1_call_j(m_ws, api.c_str(), verb.c_str(), arg, on_reply_cb, (void*) &data); + if(rc >= 0) { + // Wait for response + std::unique_lock<std::mutex> lk(data.mutex); + data.cv.wait(lk, [&]{ return data.ready; }); + + if(resp && data.resp) + *resp = data.resp; + } else { + std::cerr << __FUNCTION__ << \ + ": Failed to call " << \ + api.c_str() << \ + "/" << \ + verb.c_str() << \ + std::endl; + } + return rc; +} + +int AfbClient::subscribe(const std::string &api, const std::string &event, const std::string &eventString, callback_fn cb, const std::string &eventValueString) +{ + if(!m_valid) + return -1; + + // For now, simply let the user over-write the callback if they + // specify the same eventString. Avoiding that and keeping track + // of the duplicate eventStrings/callbacks will complicate things + // quite a bit. + + struct json_object *j_obj = json_object_new_object(); + json_object_object_add(j_obj, eventValueString.c_str(), json_object_new_string(event.c_str())); + int rc = call_sync(api, std::string("subscribe"), j_obj); + if(rc >= 0 && cb != nullptr) { + m_event_handlers[eventString] = cb; + } + return rc; +} + +int AfbClient::unsubscribe(const std::string &api, const std::string &eventString, const std::string &eventValueString) +{ + if(!m_valid) + return -1; + + if(m_event_handlers.find(eventString) == m_event_handlers.end()) + return -1; + + struct json_object *j_obj = json_object_new_object(); + json_object_object_add(j_obj, eventValueString.c_str(), json_object_new_string(eventString.c_str())); + int rc = call_sync(api, std::string("unsubscribe"), j_obj); + if(rc >= 0) { + m_event_handlers.erase(eventString); + } + return rc; +} + +void AfbClient::on_event(const char* event, struct afb_wsj1_msg *msg) +{ +#if 0 + std::cerr << __FUNCTION__ << ": event = " << event << std::endl; +#endif + struct json_object *contents = afb_wsj1_msg_object_j(msg); +#ifdef DEBUG + std::cerr << __FUNCTION__ << ": contents = " << \ + json_object_to_json_string_ext(contents, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) << \ + std::endl; +#endif + struct json_object *data; + if(json_object_object_get_ex(contents, "data", &data)) { + auto i = m_event_handlers.find(std::string(event)); + if (i != m_event_handlers.end() && i->second != nullptr) { + i->second(data); + } + } +} diff --git a/app/afbclient.h b/app/afbclient.h new file mode 100644 index 0000000..9a36581 --- /dev/null +++ b/app/afbclient.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 AFBCLIENT_H +#define AFBCLIENT_H + +#include <string> +#include <thread> +#include <map> +#include <functional> +#include <systemd/sd-event.h> +#include <json-c/json.h> + +extern "C" +{ +#include <afb/afb-wsj1.h> +#include <afb/afb-ws-client.h> +} + +class AfbClient +{ +public: + AfbClient(int port, const std::string &token); + ~AfbClient(); + + using callback_fn = std::function<void(json_object*)>; + + int call(const std::string &api, const std::string &verb, struct json_object* arg, callback_fn cb = nullptr); + int call_sync(const std::string &api, const std::string &verb, struct json_object* arg, struct json_object **resp = NULL); + int subscribe(const std::string &api, const std::string &event, const std::string &eventString, callback_fn cb, const std::string &eventValueString = "event"); + int unsubscribe(const std::string &api, const std::string &eventString, const std::string &eventValueString = "event"); + + static void on_event_cb(void *closure, const char* event, struct afb_wsj1_msg *msg) { + if(closure) + static_cast<AfbClient*>(closure)->on_event(event, msg); + } + +private: + struct afb_wsj1 *m_ws = nullptr; + struct afb_wsj1_itf m_itf; + std::thread m_afb_thread; + sd_event *m_afb_loop = nullptr; + bool m_valid = false; + + std::map<const std::string, callback_fn> m_event_handlers; + + void on_event(const char* event, struct afb_wsj1_msg *msg); +}; + +#endif // AFBCLIENT_H diff --git a/app/configuration.cpp b/app/configuration.cpp new file mode 100644 index 0000000..0762a4a --- /dev/null +++ b/app/configuration.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 "configuration.h" +#include <glib.h> +#include <strings.h> + +Configuration::Configuration(const std::string &filename, const std::string &device_uuid): + m_filename(filename), + m_device_uuid(device_uuid) +{ + read(); +} + +void Configuration::read(void) +{ + GKeyFile* conf_file; + GError *error = NULL; + char *value_str; + int n; + + // Load settings from configuration file if it exists + conf_file = g_key_file_new(); + if(!conf_file) { + return; + } + + if(g_key_file_load_from_dirs(conf_file, + m_filename.c_str(), + (const gchar**) g_get_system_config_dirs(), + NULL, + G_KEY_FILE_KEEP_COMMENTS, + NULL) != TRUE) { + g_key_file_free(conf_file); + return; + } + + // + // General + // + + // Set log level if it is specified + error = NULL; + n = g_key_file_get_integer(conf_file, "General", "log_level", &error); + if(!error && n > 0) { + m_log_level = n; + } + + value_str = g_key_file_get_string(conf_file, "General", "cellular_enabled", NULL); + if(value_str) { + if(!strcasecmp(value_str, "true")) { + m_cellular_enabled = true; + } else if(!strcasecmp(value_str, "false")) { + m_cellular_enabled = false; + } + } + + value_str = g_key_file_get_string(conf_file, "General", "gps_enabled", NULL); + if(value_str) { + if(!strcasecmp(value_str, "true")) { + m_gps_enabled = true; + } else if(!strcasecmp(value_str, "false")) { + m_gps_enabled = false; + } + } + + value_str = g_key_file_get_string(conf_file, "General", "check_online", NULL); + if(value_str) { + if(!strcasecmp(value_str, "true")) { + m_check_online = true; + } else if(!strcasecmp(value_str, "false")) { + m_check_online = false; + } + } + + value_str = g_key_file_get_string(conf_file, "General", "device_uuid", NULL); + if(value_str) { + m_device_uuid = value_str; + } + + // + // MQTT + // + + value_str = g_key_file_get_string(conf_file, "MQTT", "broker", NULL); + if(value_str) { + m_mqtt_broker = value_str; + } + + error = NULL; + n = g_key_file_get_integer(conf_file, "MQTT", "port", &error); + if(!error && n > 0) { + m_mqtt_port = n; + } + + error = NULL; + n = g_key_file_get_integer(conf_file, "MQTT", "keepalive", &error); + if(!error && n >= 0) { + m_mqtt_keepalive = n; + } + + error = NULL; + n = g_key_file_get_integer(conf_file, "MQTT", "qos", &error); + if(!error && n >= 0 && n < 3) { + m_mqtt_qos = n; + } + + value_str = g_key_file_get_string(conf_file, "MQTT", "retain", NULL); + if(value_str) { + if(!strcasecmp(value_str, "true")) { + m_mqtt_retain = true; + } else if(!strcasecmp(value_str, "false")) { + m_mqtt_retain = false; + } + } + + value_str = g_key_file_get_string(conf_file, "MQTT", "username", NULL); + if(value_str) { + m_mqtt_username = value_str; + } + + value_str = g_key_file_get_string(conf_file, "MQTT", "password", NULL); + if(value_str) { + m_mqtt_password = value_str; + } + + // + // Event + // + + error = NULL; + n = g_key_file_get_integer(conf_file, "Event", "update_period", &error); + if(!error && n >= 0) { + m_update_period = n; + } + + g_key_file_free(conf_file); +} diff --git a/app/configuration.h b/app/configuration.h new file mode 100644 index 0000000..0e7a780 --- /dev/null +++ b/app/configuration.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 CONFIGURATION_H +#define CONFIGURATION_H + +#include <string> + +class Configuration +{ +public: + Configuration(const std::string &file, const std::string &device_uuid = ""); + ~Configuration() {}; + + uint32_t getLogLevel() { return m_log_level; } + bool isCellularEnabled() { return m_cellular_enabled; } + bool isGpsEnabled() { return m_gps_enabled; } + bool getCheckOnline() { return m_check_online; } + std::string getDeviceUUID() { return std::string(m_device_uuid); } + + std::string getMqttClientId() { return std::string(m_mqtt_client_id); } + std::string getMqttBroker() { return std::string(m_mqtt_broker); } + uint32_t getMqttPort() { return m_mqtt_port; } + uint32_t getMqttKeepalive() { return m_mqtt_keepalive; } + uint32_t getMqttQos() { return m_mqtt_qos; } + bool getMqttRetain() { return m_mqtt_retain; } + std::string getMqttUsername() { return std::string(m_mqtt_username); } + std::string getMqttPassword() { return std::string(m_mqtt_password); } + + uint32_t getUpdatePeriod() { return m_update_period; } + +private: + void read(); + + std::string m_filename; + + // General + uint32_t m_log_level = 0; + bool m_cellular_enabled = false; + bool m_gps_enabled = false; + bool m_check_online = false; + std::string m_device_uuid; + + // MQTT broker + std::string m_mqtt_client_id = ""; + std::string m_mqtt_broker = "iot.eclipse.org"; + uint32_t m_mqtt_port = 1883; + uint32_t m_mqtt_keepalive = 60; + uint32_t m_mqtt_qos = 0; + bool m_mqtt_retain = true; + std::string m_mqtt_username = ""; + std::string m_mqtt_password = ""; + + // Event + uint32_t m_update_period = 10; +}; + +#endif // CONFIGURATION_H diff --git a/app/event.cpp b/app/event.cpp new file mode 100644 index 0000000..8b2d5b6 --- /dev/null +++ b/app/event.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 "event.h" +#include "gps.h" +#include "network.h" +#include <string> +#include <iostream> +#include <json-c/json.h> +#include <time.h> + +static uint64_t last_vehicle_speed_update_usecs; +static uint64_t last_engine_speed_update_usecs; + +void process_event(Configuration &config, AfbClient &afbclient, MqttClient &mqttclient, event_data *event) +{ + std::string topic("agl-telematics-demo/"); + topic += event->name; + struct json_object *j_obj = json_object_new_object(); + struct json_object *j_device = json_object_new_string(config.getDeviceUUID().c_str()); + json_object_object_add(j_obj, "device", j_device); + struct json_object *j_val = json_object_new_int(event->value); + json_object_object_add(j_obj, "value", j_val); + struct json_object *j_ts = json_object_new_int64(event->timestamp); + json_object_object_add(j_obj, "timestamp", j_ts); + + location_data location; + struct json_object *j_location = json_object_new_object(); + if(config.isGpsEnabled() && get_location(config, afbclient, &location)) { + struct json_object *j_latitude = json_object_new_double(location.latitude); + json_object_object_add(j_location, "latitude", j_latitude); + struct json_object *j_longitude = json_object_new_double(location.longitude); + json_object_object_add(j_location, "longitude", j_longitude); + struct json_object *j_speed = json_object_new_int(location.speed); + json_object_object_add(j_location, "speed", j_speed); + struct json_object *j_track = json_object_new_int(location.track); + json_object_object_add(j_location, "track", j_track); + struct json_object *j_timestamp = json_object_new_string(location.timestamp.c_str()); + json_object_object_add(j_location, "timestamp", j_timestamp); + } + json_object_object_add(j_obj, "location", j_location); + + std::string msg(json_object_to_json_string_ext(j_obj, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY)); + if(config.getLogLevel() > 1) { + std::cerr << __FUNCTION__ << ": topic = " << topic << ", msg = " << msg << std::endl; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + uint64_t now_usecs = now.tv_sec * 1000000 + now.tv_nsec / 1000; + uint64_t *past_usecs = NULL; + if(event->name == "vehicle.speed") + past_usecs = &last_vehicle_speed_update_usecs; + else if(event->name == "engine.speed") + past_usecs = &last_engine_speed_update_usecs; + if(!config.getUpdatePeriod() || + past_usecs && + (!*past_usecs || + ((now_usecs - *past_usecs) > (config.getUpdatePeriod() * 1000000)))) { + int rc = -1; + if(config.getCheckOnline() && check_online(afbclient)) { + goto skip_update; + } + rc = mqttclient.publish(topic, msg, config.getMqttQos(), config.getMqttRetain()); + if(rc != MOSQ_ERR_SUCCESS) { + std::cerr << __FUNCTION__ << ": MQTT publish failed, rc = " << rc << std::endl; + } else if(config.getLogLevel() > 0) { + std::cerr << __FUNCTION__ << ": MQTT publish succeeded" << std::endl; + } +skip_update: + *past_usecs = now_usecs; + } +} diff --git a/app/event.h b/app/event.h new file mode 100644 index 0000000..e3d8fec --- /dev/null +++ b/app/event.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 EVENT_H +#define EVENT_H + +#include "configuration.h" +#include "afbclient.h" +#include "mqttclient.h" + +struct event_data { + std::string name; + int32_t value; + uint64_t timestamp; +}; + +void process_event(Configuration &config, AfbClient &afbclient, MqttClient &mqttclient, event_data *event); + +#endif // EVENT_H diff --git a/app/gps.cpp b/app/gps.cpp new file mode 100644 index 0000000..79e97b4 --- /dev/null +++ b/app/gps.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 "gps.h" +#include <string> +#include <iostream> +#include <json-c/json.h> + +bool get_location(Configuration &config, AfbClient &afbclient, location_data *location) +{ + bool rc; + struct json_object *j_resp = NULL; + std::string status; + + if(!location) + return false; + + if(afbclient.call_sync(std::string("gps"), std::string("location"), NULL, &j_resp) < 0) + return false; + + if(config.getLogLevel() > 1) { + std::cerr << __FUNCTION__ << ": j_resp = " << \ + json_object_to_json_string_ext(j_resp, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) << \ + std::endl; + } + + // Check status + rc = false; + struct json_object *j_request; + if(!json_object_object_get_ex(j_resp, "request", &j_request)) + goto location_error; + + struct json_object *j_status; + if(!json_object_object_get_ex(j_request, "status", &j_status)) + goto location_error; + + status = json_object_get_string(j_status); + if(status == "failed") + goto location_error; + + // Get location data + struct json_object *j_response; + if(!json_object_object_get_ex(j_resp, "response", &j_response)) + goto location_error; + + struct json_object *j_latitude; + if(!json_object_object_get_ex(j_response, "latitude", &j_latitude)) + goto location_error; + + struct json_object *j_longitude; + if(!json_object_object_get_ex(j_response, "longitude", &j_longitude)) + goto location_error; + + struct json_object *j_speed; + if(!json_object_object_get_ex(j_response, "speed", &j_speed)) + goto location_error; + + struct json_object *j_track; + if(!json_object_object_get_ex(j_response, "track", &j_track)) + goto location_error; + + struct json_object *j_timestamp; + if(!json_object_object_get_ex(j_response, "timestamp", &j_timestamp)) + goto location_error; + + location->latitude = json_object_get_double(j_latitude); + location->longitude = json_object_get_double(j_longitude); + location->speed = json_object_get_int(j_speed); + location->track = json_object_get_int(j_track); + location->timestamp = json_object_get_string(j_timestamp); + rc = true; + +location_error: + json_object_put(j_resp); + return rc; +} diff --git a/app/gps.h b/app/gps.h new file mode 100644 index 0000000..4d1badd --- /dev/null +++ b/app/gps.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 GPS_H +#define GPS_H + +#include "configuration.h" +#include "afbclient.h" + +struct location_data { + double latitude; + double longitude; + int32_t speed; + int32_t track; + std::string timestamp; +}; + +bool get_location(Configuration &config, AfbClient &afbclient, location_data *location); + +#endif // GPS_H diff --git a/app/main.cpp b/app/main.cpp new file mode 100644 index 0000000..2b07cc1 --- /dev/null +++ b/app/main.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 <string> +#include <iostream> +#include <cstring> +#include <deque> +#include <mutex> +#include <condition_variable> +#include <json-c/json.h> +#include "configuration.h" +#include "afbclient.h" +#include "mqttclient.h" +#include "network.h" +#include "event.h" + +#define CONFIGURATION_FILE "telematics-recorder.conf" +#define DEVICE_UUID "e4bbc0a8-f435-4326-9769-d4a2c9f3c18d" + +static std::deque<event_data*> g_event_queue; +static std::mutex g_event_queue_mutex; +static bool g_event_queue_ready = false; +static std::condition_variable g_event_queue_cv; + +static Configuration g_config(CONFIGURATION_FILE, DEVICE_UUID); + +void diagnostic_message_cb(json_object *data) +{ + if(!data) + return; + + if(g_config.getLogLevel() > 2) { + std::cerr << __FUNCTION__ << ": data = " << \ + json_object_to_json_string_ext(data, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) << \ + std::endl; + } + + struct json_object *j_name; + if(!json_object_object_get_ex(data, "name", &j_name)) + return; + struct json_object *j_value; + if(!json_object_object_get_ex(data, "value", &j_value)) + return; + struct json_object *j_timestamp; + if(!json_object_object_get_ex(data, "timestamp", &j_timestamp)) + return; + uint64_t timestamp = json_object_get_int64(j_timestamp); + + std::string name(json_object_get_string(j_name)); + int32_t value = json_object_get_int(j_value); + if(name == "diagnostic_messages.vehicle.speed" || + name == "diagnostic_messages.engine.speed") { + if(g_config.getLogLevel() > 1) { + std::cerr << __FUNCTION__ << ": " << name << \ + ", value = " << value << \ + ", timestamp = " << timestamp << \ + std::endl; + } + + name.erase(0, 20); + event_data* event = new event_data{ name, value, timestamp }; + { + std::lock_guard<std::mutex> lk(g_event_queue_mutex); + g_event_queue.push_back(event); + g_event_queue_ready = true; + } + g_event_queue_cv.notify_one(); + } +} + +int main(int argc, char *argv[]) +{ + int port = 0; + std::string token; + + try { + port = std::stol(argv[1]); + token = argv[2]; + } catch (const std::invalid_argument& e) { + std::cerr << "Invalid argument" << std::endl; + exit(1); + } catch (const std::out_of_range& e) { + std::cerr << "Port out of range" << std::endl; + exit(1); + } + + AfbClient afbclient(port, token); + + if(g_config.isCellularEnabled()) { + // Wait for modem to appear, and enable it if not already + enable_modem(g_config, afbclient); + } + + std::string client_id = g_config.getMqttClientId(); + if(client_id.empty()) + client_id = std::string("AGL-telematics-recorder") + DEVICE_UUID; + MqttClient mqttclient(client_id, + g_config.getMqttBroker(), + g_config.getMqttPort(), + g_config.getMqttKeepalive(), + g_config.getMqttUsername(), + g_config.getMqttPassword()); + + afbclient.subscribe(std::string("low-can"), + std::string("diagnostic_messages.vehicle.speed"), + std::string("low-can/diagnostic_messages"), + diagnostic_message_cb); + afbclient.subscribe(std::string("low-can"), + std::string("diagnostic_messages.engine.speed"), + std::string("low-can/diagnostic_messages"), + diagnostic_message_cb); + + std::deque<event_data*> event_queue; + while(true) { + // Wait until event callback sends data + std::unique_lock<std::mutex> lk(g_event_queue_mutex); + g_event_queue_cv.wait(lk, []{ return g_event_queue_ready; }); + if(!g_event_queue.empty()) { + // copy out the events + event_queue = g_event_queue; + g_event_queue.clear(); + } + g_event_queue_ready = false; + lk.unlock(); + + for(event_data *event : event_queue) { + if(!event) + continue; + + if(g_config.getLogLevel() > 0) { + std::cerr << __FUNCTION__ << ": " << \ + event->name << ", value = " << event->value << \ + ", timestamp = " << event->timestamp << \ + std::endl; + } + + process_event(g_config, afbclient, mqttclient, event); + + delete event; + } + // Clear out processed events + event_queue.clear(); + } + return 0; +} diff --git a/app/mqttclient.cpp b/app/mqttclient.cpp new file mode 100644 index 0000000..f072013 --- /dev/null +++ b/app/mqttclient.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 "mqttclient.h" +#include <iostream> + +#undef DEBUG + +#ifdef DEBUG +static void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + std::cerr << " MQTT Connected, rc = " << rc << std::endl; +} + +static void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + std::cerr << " MQTT Disconnected, rc = " << rc << std::endl; +} +#endif + +MqttClient::MqttClient(const std::string &id, const std::string &host, const int port, const int keepalive, const std::string &username, const std::string &password) +{ + mosquitto_lib_init(); + m_mosq = mosquitto_new(id.c_str(), true, NULL); + +#ifdef DEBUG + mosquitto_connect_callback_set(m_mosq, on_connect); + mosquitto_disconnect_callback_set(m_mosq, on_disconnect); +#endif + + if(username.length()) + mosquitto_username_pw_set(m_mosq, username.c_str(), password.c_str()); + + if(mosquitto_connect_async(m_mosq, host.c_str(), port, keepalive)) { + std::cerr << __FUNCTION__ << ": Unable to connect to " << host << std::endl; + } + + int loop = mosquitto_loop_start(m_mosq); + if(loop != MOSQ_ERR_SUCCESS){ + std::cerr << __FUNCTION__ << ": Unable to start loop, error = " << loop << std::endl; + } +} + +MqttClient::~MqttClient(void) +{ + mosquitto_disconnect(m_mosq); + mosquitto_loop_stop(m_mosq, true); + mosquitto_destroy(m_mosq); + mosquitto_lib_cleanup(); +} + +int MqttClient::publish(const std::string &topic, const std::string &msg, const int qos, const bool retain) +{ + return mosquitto_publish(m_mosq, NULL, topic.c_str(), msg.length(), msg.c_str(), qos, retain); +} diff --git a/app/mqttclient.h b/app/mqttclient.h new file mode 100644 index 0000000..b7b893e --- /dev/null +++ b/app/mqttclient.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 MQTTCLIENT_H +#define MQTTCLIENT_H + +#include <string> +#include <mosquitto.h> +#include <json-c/json.h> + +class MqttClient +{ +public: + MqttClient(const std::string &id, const std::string &host, const int port, const int keepalive = 60, const std::string &username = "", const std::string &password = ""); + ~MqttClient(); + + int publish(const std::string &topic, const std::string &msg, const int qos, const bool retain); + +private: + mosquitto *m_mosq; +}; + +#endif // MQTTCLIENT_H diff --git a/app/network.cpp b/app/network.cpp new file mode 100644 index 0000000..d163ef5 --- /dev/null +++ b/app/network.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 "network.h" +#include <string> +#include <iostream> +#include <unistd.h> +#include <json-c/json.h> + +int enable_modem(Configuration &config, AfbClient &afbclient) +{ + int rc; + struct json_object *j_resp = NULL; + + bool cellular_enabled = false; + bool cellular_found = false; + while(!cellular_found) { + // Check current state + rc = afbclient.call_sync(std::string("network-manager"), std::string("technologies"), NULL, &j_resp); + if(rc < 0 || !j_resp) + continue; + if(config.getLogLevel() > 1) { + std::cerr << __FUNCTION__ << ": j_resp = " << \ + json_object_to_json_string_ext(j_resp, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) << \ + std::endl; + } + + struct json_object *j_response; + if(!json_object_object_get_ex(j_resp, "response", &j_response)) + continue; + + struct json_object *j_values; + if(!json_object_object_get_ex(j_response, "values", &j_values)) + continue; + + for(int i = 0; i < json_object_array_length(j_values); i++) { + struct json_object *j_value = json_object_array_get_idx(j_values, i); + if(!j_value) + break; + + struct json_object *j_technology; + if(!json_object_object_get_ex(j_value, "technology", &j_technology)) + break; + + std::string technology(json_object_get_string(j_technology)); + if(technology == "cellular") { + struct json_object *j_properties; + if(!json_object_object_get_ex(j_value, "properties", &j_properties)) + break; + + struct json_object *j_powered; + if(!json_object_object_get_ex(j_properties, "powered", &j_powered)) + break; + + if(json_object_get_boolean(j_powered)) { + if(config.getLogLevel() > 0) { + std::cerr << __FUNCTION__ << ": cellular enabled!" << std::endl; + cellular_enabled = true; + } + } + + std::cerr << __FUNCTION__ << ": cellular found!" << std::endl; + cellular_found = true; + rc = 0; + break; + } + + } + json_object_put(j_resp); + + sleep(1); + } + + if(!cellular_enabled) { + if(config.getLogLevel() > 0) { + std::cerr << __FUNCTION__ << ": enabling cellular" << std::endl; + } + struct json_object *j_obj = json_object_new_object(); + struct json_object *j_val = json_object_new_string("cellular"); + json_object_object_add(j_obj, "technology", j_val); + rc = afbclient.call_sync(std::string("network-manager"), std::string("enable_technology"), j_obj); + } + return rc; +} + +bool check_online(AfbClient &afbclient) +{ + int rc; + struct json_object *j_resp = NULL; + + // Check current state + rc = afbclient.call_sync(std::string("network-manager"), std::string("state"), NULL, &j_resp); + if(rc < 0 || !j_resp) + return false; + + struct json_object *j_response; + if(!json_object_object_get_ex(j_resp, "response", &j_response)) + return false; + + bool online = false; + std::string response(json_object_get_string(j_response)); + if(response == "online") + online = true; + json_object_put(j_resp); + return online; +} diff --git a/app/network.h b/app/network.h new file mode 100644 index 0000000..65d3330 --- /dev/null +++ b/app/network.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 Konsulko Group + * + * 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 NETWORK_H +#define NETWORK_H + +#include "configuration.h" +#include "afbclient.h" + +int enable_modem(Configuration &config, AfbClient &afbclient); + +bool check_online(AfbClient &afbclient); + +#endif // NETWORK_H diff --git a/autobuild/agl/autobuild b/autobuild/agl/autobuild new file mode 100755 index 0000000..85ddaec --- /dev/null +++ b/autobuild/agl/autobuild @@ -0,0 +1,77 @@ +#!/usr/bin/make -f +# Copyright (C) 2015 - 2018 "IoT.bzh" +# 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)) +BUILD_DIR := $(abspath $(dir $(THISFILE))/../../build) +DEST := ${BUILD_DIR} + +.PHONY: all clean distclean configure build package help update + +all: help + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @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" + +update: configure + @cmake --build ${BUILD_DIR} --target autobuild + +clean: + @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} ${CLEAN_ARGS} clean) || echo Nothing to clean + +distclean: + @rm -rf ${BUILD_DIR} + +configure: + @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} + @[ -f ${BUILD_DIR}/Makefile -o -f ${BUILD_DIR}/build.ninja ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) + +build: configure + @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all + +package: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/var + @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget + @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ + fi + +package-test: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/var + @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget + @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target test_widget + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST} + +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..83097ab --- /dev/null +++ b/autobuild/linux/autobuild @@ -0,0 +1,67 @@ +#!/usr/bin/make -f +# Copyright (C) 2015, 2016 "IoT.bzh" +# 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)) +BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build) +DEST := ${BUILD_DIR}/target + +.PHONY: all clean distclean configure build package help update + +all: help + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @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: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +update: configure + @cmake --build ${BUILD_DIR} --target autobuild + +clean: + @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean + +distclean: + @rm -rf ${BUILD_DIR} + +configure: ${BUILD_DIR}/Makefile + +build: configure + @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all + +package: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/var + @cmake --build ${BUILD_DIR} --target widget + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST} + +install: build + @cmake --build ${BUILD_DIR} --target install + +${BUILD_DIR}/Makefile: + @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} + @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake new file mode 100644 index 0000000..00ec204 --- /dev/null +++ b/conf.d/cmake/config.cmake @@ -0,0 +1,205 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# Copyright 2019 Konsulko Group +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# telematics-recorder: Scott Murray <scott.murray@konsulko.com> +# +# 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 Info +# ------------------ +set(PROJECT_NAME telematics-recorder) +set(PROJECT_PRETTY_NAME "Telematics Recorder Demo") +set(PROJECT_DESCRIPTION "Telematics recorder demo application") +set(PROJECT_URL "https://github.com/konsulko/agl-telematics-recorder") +set(PROJECT_VERSION "1.0") +set(PROJECT_ICON "icon.png") +set(PROJECT_AUTHOR "Scott Murray") +set(PROJECT_AUTHOR_MAIL "scott.murray@konsulko.com") +set(PROJECT_LICENSE "APL2.0") +set(PROJECT_LANGUAGES "CXX") + +# Where are stored the project configuration files +set(PROJECT_APP_TEMPLATES_DIR "conf.d") + +# Where are stored your external libraries for your project. This is 3rd party library that you don't maintain +# but used and must be built and linked. +# set(PROJECT_LIBDIR "libs") + +# Which directories inspect to find CMakeLists.txt target files +# set(PROJECT_SRC_DIR_PATTERN "*") + +# Compilation Mode (DEBUG, RELEASE) +# ---------------------------------- +#set(BUILD_TYPE "DEBUG") +set(BUILD_TYPE "RELEASE") + +#set(USE_EFENCE 1) + +# Kernel selection if needed. You can choose between a +# mandatory version to impose a minimal version. +# Or check Kernel minimal version and just print a Warning +# about missing features and define a preprocessor variable +# to be used as preprocessor condition in code to disable +# incompatibles features. Preprocessor define is named +# KERNEL_MINIMAL_VERSION_OK. +# +# NOTE*** FOR NOW IT CHECKS KERNEL Yocto environment and +# Yocto SDK Kernel version. +# ----------------------------------------------- +#set (kernel_mandatory_version 4.8) +#set (kernel_minimal_version 4.8) + +# Compiler selection if needed. Impose a minimal version. +# ----------------------------------------------- +set (gcc_minimal_version 4.9) + +# PKG_CONFIG required packages +# ----------------------------- +set (PKG_REQUIRED_LIST + json-c + afb-daemon + glib-2.0 + gobject-2.0 +) + +# You can also consider to include libsystemd +# ----------------------------------- +#list (APPEND PKG_REQUIRED_LIST libsystemd>=222) + +# Prefix path where will be installed the files +# Default: /usr/local (need root permission to write in) +# ------------------------------------------------------ +#set(INSTALL_PREFIX /opt/AGL CACHE PATH "INSTALL PREFIX PATH") + +# Customize link option +# ----------------------------- +#list(APPEND link_libraries -an-option) + +# Compilation options definition +# Use CMake generator expressions to specify only for a specific language +# Values are prefilled with default options that is currently used. +# Either separate options with ";", or each options must be quoted separately +# DO NOT PUT ALL OPTION QUOTED AT ONCE , COMPILATION COULD FAILED ! +# ---------------------------------------------------------------------------- +#set(COMPILE_OPTIONS +# -Wall +# -Wextra +# -Wconversion +# -Wno-unused-parameter +# -Wno-sign-compare +# -Wno-sign-conversion +# -Werror=maybe-uninitialized +# -Werror=implicit-function-declaration +# -ffunction-sections +# -fdata-sections +# -fPIC +# CACHE STRING "Compilation flags") +#set(C_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C language.") +#set(CXX_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C++ language.") +#set(PROFILING_COMPILE_OPTIONS +# -g +# -O0 +# -pg +# -Wp,-U_FORTIFY_SOURCE +# CACHE STRING "Compilation flags for PROFILING build type.") +#set(DEBUG_COMPILE_OPTIONS +# -g +# -ggdb +# -D_FORTIFY_SOURCE=2 +# CACHE STRING "Compilation flags for DEBUG build type.") +#set(CCOV_COMPILE_OPTIONS +# -g +# -O2 +# --coverage +# CACHE STRING "Compilation flags for CCOV build type.") +#set(RELEASE_COMPILE_OPTIONS +# -g +# -O2 +# -D_FORTIFY_SOURCE=2 +# CACHE STRING "Compilation flags for RELEASE build type.") + +# Optional location for config.xml.in +# ----------------------------------- +#set(WIDGET_ICON "\"conf.d/wgt/${PROJECT_ICON}\"" CACHE PATH "Path to the widget icon") +set(WIDGET_CONFIG_TEMPLATE "${CMAKE_CURRENT_SOURCE_DIR}/conf.d/wgt/config.xml.in" CACHE PATH "Path to widget config file template (config.xml.in)") + +# Mandatory widget Mimetype specification of the main unit +# -------------------------------------------------------------------------- +# Choose between : +#- text/html : HTML application, +# content.src designates the home page of the application +# +#- application/vnd.agl.native : AGL compatible native, +# content.src designates the relative path of the binary. +# +# - application/vnd.agl.service: AGL service, content.src is not used. +# +#- ***application/x-executable***: Native application, +# content.src designates the relative path of the binary. +# For such application, only security setup is made. +# +set(WIDGET_TYPE application/vnd.agl.native) + +# Mandatory Widget entry point file of the main unit +# -------------------------------------------------------------- +# This is the file that will be executed, loaded, +# at launch time by the application framework. +# +set(WIDGET_ENTRY_POINT bin/telematics-recorder) + +# Optional dependencies order +# --------------------------- +#set(EXTRA_DEPENDENCIES_ORDER) + +# Optional Extra global include path +# ----------------------------------- +#set(EXTRA_INCLUDE_DIRS) + +# Optional extra libraries +# ------------------------- +#set(EXTRA_LINK_LIBRARIES) + +# Optional force binding Linking flag +# ------------------------------------ +# set(BINDINGS_LINK_FLAG LinkOptions ) + +# Optional force package prefix generation, like widget +# ----------------------------------------------------- +# set(PKG_PREFIX DestinationPath) + +# Optional Application Framework security token +# and port use for remote debugging. +#------------------------------------------------------------ +set(AFB_TOKEN "" CACHE PATH "Default binder security token") +set(AFB_REMPORT "1234" CACHE PATH "Default binder listening port") + +# Print a helper message when every thing is finished +# ---------------------------------------------------- +#set(CLOSING_MESSAGE "Typical binding launch: cd ${CMAKE_BINARY_DIR}/package && afb-daemon --port=${AFB_REMPORT} --workdir=. --ldpaths=lib --roothttp=htdocs --token=\"${AFB_TOKEN}\" --tracereq=common --verbose") +set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt") + +# Optional schema validator about now only XML, LUA and JSON +# are supported +#------------------------------------------------------------ +#set(LUA_CHECKER "luac" "-p" CACHE STRING "LUA compiler") +#set(XML_CHECKER "xmllint" CACHE STRING "XML linter") +#set(JSON_CHECKER "json_verify" CACHE STRING "JSON linter") + +# +# This CMake module could be found at the following url: +# https://gerrit.automotivelinux.org/gerrit/#/admin/projects/src/cmake-apps-module +# ----------------------------------------------------------- +include(CMakeAfbTemplates) diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in new file mode 100644 index 0000000..ce45e87 --- /dev/null +++ b/conf.d/wgt/config.xml.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<widget xmlns="http://www.w3.org/ns/widgets" id="@PROJECT_NAME@" version="@PROJECT_VERSION@"> + <name>@PROJECT_NAME@</name> + <icon src="@PROJECT_ICON@"/> + <content src="@WIDGET_ENTRY_POINT@" type="@WIDGET_TYPE@"/> + <description>@PROJECT_DESCRIPTION@</description> + <author>@PROJECT_AUTHOR@ <@PROJECT_AUTHOR_MAIL@></author> + <license>@PROJECT_LICENSE@</license> + <feature name="urn:AGL:widget:required-api"> + <param name="low-can" value="ws" /> + <param name="gps" value="ws" /> + <param name="network-manager" value="ws" /> + </feature> + <feature name="urn:AGL:widget:required-permission"> + <param name="urn:AGL:permission::system:run-by-default" value="required" /> + <param name="urn:AGL:permission::public:no-htdocs" value="required" /> + </feature> +</widget> |