aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRomain Forlot <romain.forlot@iot.bzh>2017-06-05 19:16:27 +0200
committerRomain Forlot <romain.forlot@iot.bzh>2017-06-05 19:16:27 +0200
commit13b637356a92d68e5dfed6990443e6937f1ed9c6 (patch)
tree7510c9fd267a8957b581eea810916c05e1a7a287
Initial Commit
Change-Id: I4a832208f3db9f5fece82f44cc957c6c1bb91d6c Signed-off-by: Romain Forlot <romain.forlot@iot.bzh>
-rw-r--r--.gitignore17
-rw-r--r--.gitmodules4
-rw-r--r--CMakeLists.txt31
-rw-r--r--README.md111
-rw-r--r--conf.d/config.cmake126
m---------conf.d/templates0
-rw-r--r--high-can-binding/CMakeLists.txt43
-rw-r--r--high-can-binding/high-can-binding-hat.cpp47
-rw-r--r--high-can-binding/high-can-binding-hat.hpp17
-rw-r--r--high-can-binding/high-can-binding.cpp74
-rw-r--r--high-can-binding/high.cpp543
-rw-r--r--high-can-binding/high.hpp57
-rw-r--r--high.json115
13 files changed, 1185 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..df894fc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+.vscode
+.kdev4
+*.kdev4
+*.swp
+*.kate-swp
+.vs
+*.sln
+*.vcxproj
+*.user
+obj
+build
+bin
+src/configuration-generated.cpp
+docs_doxygen
+node_modules/
+packaging/
+_book/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c87a462
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,4 @@
+[submodule "conf.d/templates"]
+ path = conf.d/templates
+ url = https://gerrit.automotivelinux.org/gerrit/apps/app-templates
+ branch = reference
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..1902466
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,31 @@
+###########################################################################
+# 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/config.cmake)
+include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/templates/cmake/common.cmake)
+
+# Bindings to compile
+# --------------------
+project_subdirs_add()
+
+project_targets_populate()
+project_package_build()
+
+project_closing_msg()
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c007d2e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,111 @@
+# High level binding
+
+CAN binder, based upon ViWi definition.
+
+This binding is intended to act between the low-level binding and clients. It collects resources as defined in a json configuration file, and
+implements subscribe/unsubscribe/get verbs for the clients.
+
+# build
+```bash
+mkdir build;cd build; cmake ..; make
+```
+# launching
+natively under linux you can launch afb-daemon with the low-level and high-level bindings with a command like:
+
+```bash
+afb-daemon --rootdir=<path_to_low_binding>/CAN-binder/build/package --ldpaths=<path_to_low_binding>/CAN-binder/build/package/lib:<path_to_high_binding>/build/high-can-binding --port=1234 --tracereq=common --token=1 --verbose --verbose --verbose
+```
+
+#json configuration file
+json configuration file (high.json) must be placed in the directory where you will launch afb-dameon
+The json configuration file consists in 2 sections:
+
+## definitions section
+This section describes each resources defined in the high-level binding. Each resource is composed with different properties having a name, a type and a description.
+Type can be boolean, double, string, or int. Properties "id", "uri" and "name" are compulsory.
+For instance:
+```json
+{
+ "name": "/car/demoboard/",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "identifier"
+ },
+ "uri": {
+ "type": "string",
+ "description": "object uri"
+ },
+ "name": {
+ "type": "string",
+ "description": "name"
+ },
+ "unit": {
+ "type": "string",
+ "description": "units"
+ },
+ "speed": {
+ "type": "double",
+ "description": "vehicle centerpoint speed as shown by the instrument cluster"
+ },
+ "rpm": {
+ "type": "double",
+ "description": "engine rotations per minute"
+ },
+ "level": {
+ "type": "double",
+ "description": "level of tankage"
+ },
+ "load": {
+ "type": "double",
+ "description": "engine load"
+ }
+ }
+}
+```
+## resources section
+This section defines which values should be assigned to resource's properties as defined in the definitions section.
+The link to the definitions section is made through the name of the resource.
+Some values are static, some are linked to low-level requests.
+In case a value is linked to a low-level request, the value will start with "${" and end with "}". In that case the value will consist in the name of the low-level signal, followed
+with the frequency of the signal in ms. -1 in the frequency means that high level binding should subscribe to low level binding for all changes, without specifying a frequency.
+For instance:
+```json
+{
+ "name": "/car/demoboard/",
+ "values": [{
+ "name": "vehicleSpeed",
+ "unit": "km/h",
+ "speed": "${diagnostic_messages.vehicle.speed,1000}"
+ }, {
+ "name": "engineSpeed",
+ "unit": "rpm",
+ "rpm": "${diagnostic_messages.engine.speed,1000}"
+ }, {
+ "name": "fuelLevel",
+ "unit": "litre",
+ "level": "${diagnostic_messages.fuel.level,1000}"
+ }, {
+ "name": "engineLoad",
+ "unit": "Nm",
+ "load": "${diagnostic_messages.engine.load,1000}"
+ }]
+}
+```
+# Running and testing
+You can use afb-client-demo to test high level binding.
+For instance, once daemon has been launched with the 2 bindings:
+```bash
+afb-client-demo ws://localhost:1234/api?token=1
+```
+You can then use commands such as:
+```bash
+high-can subscribe {"name":"/car/doors/","interval":10000}
+high-can unsubscribe {"name":"/car/doors/","interval":10000}
+high-can get {"name":"/car/demoboard/"}
+high-can get {"name":"/car/demoboard/","fields":["fuelLevel","engineLoad"]}
+```
+You can also inject some data in CAN bus using canplayer (example of data can be find in low-level binding example directory)
+```bash
+canplayer -I highwaycomplete
+```
diff --git a/conf.d/config.cmake b/conf.d/config.cmake
new file mode 100644
index 0000000..6d7ea03
--- /dev/null
+++ b/conf.d/config.cmake
@@ -0,0 +1,126 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@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.
+###########################################################################
+
+# Project Info
+# ------------------
+set(NAME high-can-project)
+set(VERSION "1.0")
+set(PRETTY_NAME "High level CAN binding")
+set(DESCRIPTION "Expose CAN Low Level APIs through AGL Framework")
+set(URL "https://github.com/iotbzh/CAN_signaling")
+set(PROJECT_ICON "icon.png")
+set(PROJECT_AUTHOR "Last Name, First Name")
+set(PROJECT_AUTHOR_MAIL "example.man@bigouden.bzh")
+set(PROJECT_LICENCE "APL2.0")
+set(PROJECT_LANGUAGES,"C")
+
+# Where are stored default templates files from submodule or subtree app-templates in your project tree
+# relative to the root project directory
+set(PROJECT_APP_TEMPLATES_DIR "conf.d/templates")
+
+# Where are stored config.xml.in and icon.png.in files. Template available at :
+# https://gerrit.automotivelinux.org/gerrit/#/admin/projects/apps/app-templates
+# set(PROJECT_WGT_DIR "packaging/wgt")
+
+# 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")
+
+# Where are stored data for your application. Pictures, static resources must be placed in that folder.
+# set(PROJECT_RESOURCES "data")
+
+# Compilation Mode (DEBUG, RELEASE)
+# ----------------------------------
+set(CMAKE_BUILD_TYPE "DEBUG")
+
+# Compiler selection if needed. Impose a minimal version.
+# -----------------------------------------------
+set (gcc_minimal_version 4.9)
+
+# PKG_CONFIG required packages
+# -----------------------------
+set (PKG_REQUIRED_LIST
+ json-c
+ libsystemd
+ afb-daemon
+)
+
+# Static constante definition
+# -----------------------------
+add_compile_options()
+
+# LANG Specific compile flags set for all build types
+set(CMAKE_C_FLAGS "")
+set(CMAKE_CXX_FLAGS "-std=c++11")
+
+# Print a helper message when every thing is finished
+# ----------------------------------------------------
+#set(CLOSING_MESSAGE "")
+#set(WIDGET_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt")
+
+# (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable]
+# ---------------------------------------------------------------------
+set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt)
+set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig)
+set(LD_LIBRARY_PATH ${CMAKE_INSTALL_PREFIX}/lib64 ${CMAKE_INSTALL_PREFIX}/lib)
+
+# Optional dependencies order
+# ---------------------------
+#set(EXTRA_DEPENDENCIES_ORDER)
+
+# Optional Extra global include path
+# -----------------------------------
+#set(EXTRA_INCLUDE_DIRS)
+
+# Optional extra libraries
+# -------------------------
+#set(EXTRA_LINK_LIBRARIES boost_system)
+
+# Optional force binding installation
+# ------------------------------------
+# set(BINDINGS_INSTALL_PREFIX PrefixPath )
+
+# Optional force widget prefix generation
+# ------------------------------------------------
+# set(WIDGET_PREFIX DestinationPath)
+
+# Optional Widget entry point file.
+# ---------------------------------------------------------
+ # This is the file that will be executed, loaded,...
+# at launch time by the application framework
+
+# set(WIDGET_ENTRY_POINT EntryPoint_Path)
+
+# Optional Widget Mimetype specification
+# --------------------------------------------------
+# Choose between :
+# - application/x-executable
+# - application/vnd.agl.url
+# - application/vnd.agl.service
+# - application/vnd.agl.native
+# - text/vnd.qt.qml
+# - text/html
+# - application/vnd.agl.qml
+# - application/vnd.agl.qml.hybrid
+# - application/vnd.agl.html.hybrid
+#
+# set(WIDGET_TYPE MimeType)
+
+# Optional force binding Linking flag
+# ------------------------------------
+# set(BINDINGS_LINK_FLAG LinkOptions )
diff --git a/conf.d/templates b/conf.d/templates
new file mode 160000
+Subproject d63a072def64647022cff067aff65e957281bf4
diff --git a/high-can-binding/CMakeLists.txt b/high-can-binding/CMakeLists.txt
new file mode 100644
index 0000000..e607bfe
--- /dev/null
+++ b/high-can-binding/CMakeLists.txt
@@ -0,0 +1,43 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+# contrib: 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.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(high-can-binding)
+
+ # Define project Targets
+ add_library(${TARGET_NAME} MODULE ${TARGET_NAME}.cpp high-can-binding-hat.cpp high.cpp)
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ PREFIX ""
+ LABELS "BINDING"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ ${link_libraries}
+ )
+
+ # installation directory
+ INSTALL(TARGETS ${TARGET_NAME}
+ LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR})
+
+ #build_widget("BINDING")
diff --git a/high-can-binding/high-can-binding-hat.cpp b/high-can-binding/high-can-binding-hat.cpp
new file mode 100644
index 0000000..01f6ab1
--- /dev/null
+++ b/high-can-binding/high-can-binding-hat.cpp
@@ -0,0 +1,47 @@
+#include "high-can-binding-hat.hpp"
+#include <cstddef>
+/// Interface between the daemon and the binding
+const struct afb_binding_interface *binder_interface;
+extern "C"
+{
+ #include <afb/afb-service-itf.h>
+ struct afb_service srvitf;
+};
+static const struct afb_verb_desc_v1 verbs[]=
+{
+ { .name= "subscribe", .session= AFB_SESSION_NONE, .callback= subscribe, .info= "subscribe to notification of CAN bus messages." },
+ { .name= "unsubscribe", .session= AFB_SESSION_NONE, .callback= unsubscribe, .info= "unsubscribe a previous subscription." },
+ { .name= "get", .session= AFB_SESSION_NONE, .callback= get, .info= "high can get viwi request." },
+};
+
+static const struct afb_binding binding_desc {
+ AFB_BINDING_VERSION_1,
+ {
+ "High level CAN bus service",
+ "high-can",
+ verbs
+ }
+};
+
+const struct afb_binding *afbBindingV1Register (const struct afb_binding_interface *itf)
+{
+ binder_interface = itf;
+ NOTICE(binder_interface, "high level afbBindingV1Register");
+
+ return &binding_desc;
+}
+/// @brief Initialize the binding.
+///
+/// @param[in] service Structure which represent the Application Framework Binder.
+///
+/// @return Exit code, zero if success.
+int afbBindingV1ServiceInit(struct afb_service service)
+{
+ srvitf = service;
+ //NOTICE(binder_interface, "before afb_daemon_require_api");
+ //afb_daemon_require_api(binder_interface->daemon, "low-can", 1);
+ NOTICE(binder_interface, "high level binding is initializing");
+ initHigh(service);
+ NOTICE(binder_interface, "high level binding is initialized and running");
+ return 0;
+}
diff --git a/high-can-binding/high-can-binding-hat.hpp b/high-can-binding/high-can-binding-hat.hpp
new file mode 100644
index 0000000..b91f083
--- /dev/null
+++ b/high-can-binding/high-can-binding-hat.hpp
@@ -0,0 +1,17 @@
+#pragma once
+#include <cstddef>
+#include <systemd/sd-event.h>
+extern "C"
+{
+ #define AFB_BINDING_VERSION 1
+ #include <afb/afb-binding.h>
+};
+
+extern "C" struct afb_binding_interface;
+extern "C" struct afb_service srvitf;
+extern const struct afb_binding_interface *binder_interface;
+ void subscribe(struct afb_req request);
+ void unsubscribe(struct afb_req request);
+ void get(struct afb_req request);
+ void initHigh(afb_service service);
+ int ticked(sd_event_source *source, unsigned long t, void *data);
diff --git a/high-can-binding/high-can-binding.cpp b/high-can-binding/high-can-binding.cpp
new file mode 100644
index 0000000..bbcc941
--- /dev/null
+++ b/high-can-binding/high-can-binding.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015, 2016 "IoT.bzh"
+ * Author "Romain Forlot" <romain.forlot@iot.bzh>
+ * Author "Loic Collignon" <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "high-can-binding-hat.hpp"
+#include <json-c/json.h>
+#include "high.hpp"
+High high;
+
+/// @brief callback for receiving message from low binding. Treatment itself is made in High class.
+extern void afbBindingV1ServiceEvent(const char *event, struct json_object *object)
+{
+ high.treatMessage(object);
+}
+/// @brief entry point for client subscription request. Treatment itself is made in High class.
+void subscribe(struct afb_req request)
+{
+ if(high.subscribe(request))
+ afb_req_success(request, NULL, NULL);
+ else
+ afb_req_fail(request, "error", NULL);
+}
+
+/// @brief entry point for client un-subscription request. Treatment itself is made in High class.
+void unsubscribe(struct afb_req request)
+{
+ if(high.unsubscribe(request))
+ afb_req_success(request, NULL, NULL);
+ else
+ afb_req_fail(request, "error", NULL);
+}
+
+/// @brief entry point for get requests. Treatment itself is made in High class.
+void get(struct afb_req request)
+{
+ json_object *jobj;
+ if(high.get(request, &jobj)) {
+ afb_req_success(request, jobj, NULL);
+ } else {
+ afb_req_fail(request, "error", NULL);
+ }
+}
+
+/// @brief entry point for systemD timers. Treatment itself is made in High class.
+/// @param[in] source: systemD timer, t: time of tick, data: interval (ms).
+int ticked(sd_event_source *source, long unsigned int t, void* data)
+{
+ high.tick(source, t, data);
+ return 0;
+}
+
+/// @brief Initialize the binding.
+///
+/// @param[in] service Structure which represent the Application Framework Binder.
+void initHigh(struct afb_service service)
+{
+ high.parseConfigAndSubscribe(service);
+}
+
+
diff --git a/high-can-binding/high.cpp b/high-can-binding/high.cpp
new file mode 100644
index 0000000..82f95cb
--- /dev/null
+++ b/high-can-binding/high.cpp
@@ -0,0 +1,543 @@
+/**
+TYPICAL COMMANDS:
+high-can start
+high-can get {"name":"/car/doors/"}
+high-can get {"name":"/car/doors/6078d4-23b89b-8faaa9-8bd0f3"}
+high-can get {"name":"/car/doors/","fields":["isDoorOpen"]}
+high-can subscribe {"name":"/car/doors/6078d4-23b89b-8faaa9-8bd0f3"} note: fields parameter on subscribe is not implemented yet
+high-can unsubscribe {"name":"/car/doors/6078d4-23b89b-8faaa9-8bd0f3"}
+high-can subscribe {"name":"/car/doors/"}
+high-can subscribe {"name":"/car/doors/6078d4-23b89b-8faaa9-8bd0f3","interval":5000}
+high-can subscribe {"name":"/car/doors/","interval":5000}
+high-can unsubscribe {"name":"/car/doors/","interval":5000}
+*/
+
+#include <json-c/json.h>
+#include <algorithm>
+#include "high.hpp"
+#include "high-can-binding-hat.hpp"
+#include <time.h>
+#include <sstream>
+#include <iterator>
+/// @brief Split a std::string in several string, based on a delimeter
+///
+/// @param[in] string: the string to be splitted, delim: the delimeter to use for splitting.
+///
+/// @return std::vector<std::string> : a vector containing each individual string after the split.
+using namespace std;
+template<typename Out>
+void split(const std::string &s, char delim, Out result) {
+ std::stringstream ss;
+ ss.str(s);
+ std::string item;
+ while (std::getline(ss, item, delim)) {
+ *(result++) = item;
+ }
+}
+std::vector<std::string> split(const std::string &s, char delim) {
+ std::vector<std::string> elems;
+ split(s, delim, std::back_inserter(elems));
+ return elems;
+}
+
+/// @brief Main high binding class: maintains resources status, subcriptions and timers
+High::High()
+{
+}
+
+/// @brief Reads the json configuration and generates accordingly the resources container. An UID is generated for each resource.
+/// Makes necessary subscriptions to low-level, eventually with a frequency.
+///
+/// @param[in] afb_service: the service to call for subscriptions.
+///
+void High::parseConfigAndSubscribe(struct afb_service service)
+{
+ json_object *config = json_object_from_file("high.json");
+ json_object *jvalue, *jarray1, *jarray2, *obj;
+ std::map<std::string, std::map<std::string, Property>> properties;
+
+ json_object_object_get_ex(config, "definitions", &jarray1);
+ int arraylen1 = json_object_array_length(jarray1);
+ for(int n = 0; n < arraylen1; ++n) {
+ obj = json_object_array_get_idx(jarray1, n);
+ json_object_object_get_ex(obj, "name", &jvalue);
+ const std::string name = json_object_get_string(jvalue);
+ json_object_object_get_ex(obj, "properties", &jarray2);
+ std::map<std::string, Property> props;
+ json_object_object_foreach(jarray2, key, val) {
+ Property p;
+ json_object_object_get_ex(val, "type", &jvalue);
+ p.type = json_object_get_string(jvalue);
+ json_object_object_get_ex(val, "description", &jvalue);
+ p.description = json_object_get_string(jvalue);
+ props[key] = p;
+ }
+ properties[name] = props;
+ }
+ json_object_object_get_ex(config, "resources", &jarray1);
+ arraylen1 = json_object_array_length(jarray1);
+ std::map<std::string, int> toSubscribe;
+ for(int n = 0; n < arraylen1; ++n) {
+ obj = json_object_array_get_idx(jarray1, n);
+ json_object_object_get_ex(obj, "name", &jvalue);
+ const std::string name = json_object_get_string(jvalue);
+ json_object_object_get_ex(obj, "values", &jarray2);
+ const int arraylen2 = json_object_array_length(jarray2);
+ for(int i = 0; i < arraylen2; ++i) {
+ const std::string id = generateId();
+ const std::string uri = name + id;
+ jvalue = json_object_array_get_idx(jarray2, i);
+ if(properties.find(name) == properties.end()) {
+ NOTICE(binder_interface, "Unable to find name %s in properties", name.c_str());
+ continue;
+ }
+ const std::map<std::string, Property> props = properties[name];
+ std::map<std::string, Property> localProps; //note that local props can have less members than defined.
+ localProps["id"] = props.at("id");
+ localProps["name"] = props.at("name");
+ localProps["uri"] = props.at("uri");
+ localProps["id"].value_string = std::string(id);
+ localProps["uri"].value_string = std::string(uri);
+ json_object_object_foreach(jvalue, key, val) {
+ const std::string value = json_object_get_string(val);
+ if(props.find(key) == props.end()) {
+ NOTICE(binder_interface, "Unable to find key %s in properties", value.c_str());
+ continue;
+ }
+ Property prop = props.at(key);
+ if(startsWith(value, "${")) {
+ const std::string canMessage = value.substr(2, value.size() - 1);
+ const std::vector<std::string> params = split(canMessage, ',');
+ if(params.size() != 2) {
+ NOTICE(binder_interface, "Invalid CAN message definition %s", value.c_str());
+ continue;
+ }
+ prop.lowMessageName = params.at(0);
+ prop.interval = stoi(params.at(1));
+ if(toSubscribe.find(prop.lowMessageName) != toSubscribe.end()) {
+ if(toSubscribe.at(prop.lowMessageName) > prop.interval)
+ toSubscribe[prop.lowMessageName] = prop.interval;
+ } else {
+ toSubscribe[prop.lowMessageName] = prop.interval;
+ }
+ if(prop.type == "string")
+ prop.value_string = std::string("nul");
+ else if(prop.type == "boolean")
+ prop.value_bool = false;
+ else if(prop.type == "double")
+ prop.value_double = 0.0;
+ else if(prop.type == "int")
+ prop.value_int = 0;
+ else
+ NOTICE(binder_interface, "ERROR 2! unexpected type in parseConfig %s %s", prop.description.c_str(), prop.type.c_str());
+ } else {
+ prop.value_string= std::string(value);
+ }
+ localProps[key] = prop;
+ }
+ registeredObjects[uri] = localProps;
+ for(const auto &p : localProps) {
+ if(p.second.lowMessageName.size() > 0) {
+ std::set<std::string> objectList;
+ if(lowMessagesToObjects.find(p.second.lowMessageName) != lowMessagesToObjects.end())
+ objectList = lowMessagesToObjects.at(p.second.lowMessageName);
+ objectList.insert(uri);
+ lowMessagesToObjects[p.second.lowMessageName] = objectList;
+ }
+ }
+ }
+ }
+ for(const auto &p : toSubscribe) {
+ json_object *jobj = json_object_new_object();
+ json_object_object_add(jobj,"event", json_object_new_string(p.first.c_str()));
+ if(p.second > 0) {
+ json_object *filter = json_object_new_object();
+ json_object_object_add(filter, "frequency", json_object_new_double(1000.0 / (double)p.second));
+ json_object_object_add(jobj, "filter", filter);
+ }
+ json_object *dummy;
+ const std::string js = json_object_get_string(jobj);
+ if(afb_service_call_sync(service, "low-can", "subscribe", jobj, &dummy) != 1)
+ NOTICE(binder_interface, "high-can subscription to low-can FAILED %s", js.c_str());
+ else
+ NOTICE(binder_interface, "high-can subscribed to low-can %s", js.c_str());
+ json_object_put(dummy);
+ }
+ json_object_put(config);
+ NOTICE(binder_interface, "configuration loaded");
+}
+
+/// @brief Create and start a systemD timer. Only one timer is created per frequency.
+///
+/// @param[in] t: interval in ms.
+///
+void High::startTimer(const int &t)
+{
+ if(timers.find(t) != timers.end())
+ return;
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ sd_event_add_time(afb_daemon_get_event_loop(binder_interface->daemon), NULL, CLOCK_MONOTONIC, (ts.tv_sec + 1) * 1000000, 0, &ticked, new int(t));
+}
+High::~High()
+{
+ timers.clear();
+}
+
+/// @brief callback called after subscription to low-level binding.
+///
+void High::callBackFromSubscribe(void *handle, int iserror, struct json_object *result)
+{
+ NOTICE(binder_interface, "high level callBackFromSubscribe method called %s", json_object_get_string(result));
+}
+
+/// @brief Entry point for all timer events. Treats all requests linked to the specific timer frequency.
+/// Restarts the timer, or cancels it if no requests are anymore linked to it.
+///
+/// @param[in] source: systemD timer, now: tick timestamp, interv: specific timer interval in ms.
+///
+void High::tick(sd_event_source *source, const long &now, void *interv)
+{
+ const int interval = *(int*)interv;
+ NOTICE(binder_interface, "tick! %d %ld", interval, now);
+ bool hasEvents = false;
+ if(timedEvents.find(interval) != timedEvents.end()) {
+ std::vector<TimedEvent> evts = timedEvents[interval];
+ for(int i = (int)evts.size() - 1; i >= 0; --i) {
+ const TimedEvent e = evts.at(i);
+ std::map<std::string, json_object*> jsons;
+ for(const auto &pp : registeredObjects) {
+ if(startsWith(pp.first, e.name)) {
+ jsons[pp.first] = generateJson(pp.first);
+ }
+ }
+ json_object *j;
+ if(jsons.size() == 1) {
+ j = jsons[0];
+ } else if(jsons.size() > 1) {
+ j = json_object_new_object();
+ for(const auto &pp : jsons)
+ json_object_object_add(j, pp.first.c_str(), pp.second);
+ }
+ const int nbSubscribers = afb_event_push(e.event, j);
+ if(nbSubscribers == 0) {
+ afb_event_drop(e.event);
+ evts.erase(evts.begin() + i);
+ timedEvents[interval] = evts;
+ }
+ //NOTICE(binder_interface, "%s event pushed to %d subscribers", e.eventName.c_str(), nbSubscribers);
+ }
+ if(evts.size() > 0)
+ hasEvents = true;
+ }
+ if(hasEvents) {
+ sd_event_source_set_time(source, now + interval * 1000);
+ sd_event_source_set_enabled(source, SD_EVENT_ON);
+ } else {
+ //NOTICE(binder_interface, "timer removed %d", interval);
+ delete (int*)interv;
+ if(timers.find(interval) != timers.end()) {
+ timers.erase(interval);
+ }
+ sd_event_source_unref(source);
+ }
+}
+
+/// @brief Entry point for low-binding events. Updates all resources linked to this event and eventually
+/// sends back events to subscribers, if any.
+///
+/// @param[in] message: json low-level message.
+///
+void High::treatMessage(json_object *message)
+{
+ json_object *nameJson, *jvalue;
+ json_object_object_get_ex(message, "name", &nameJson);
+ json_object_object_get_ex(message, "value", &jvalue);
+ const std::string messageName(json_object_get_string(nameJson));
+ if(lowMessagesToObjects.find(messageName) == lowMessagesToObjects.end()) {
+ NOTICE(binder_interface, "message not linked to any object %s", json_object_get_string(message));
+ return;
+ }
+// NOTICE(binder_interface, "message received %s", json_object_get_string(message));
+ const std::set<std::string> objects = lowMessagesToObjects.at(messageName);
+ std::vector<std::string> candidateMessages;
+ for(const std::string &uri : objects) {
+ std::map<std::string, Property> properties = registeredObjects.at(uri);
+ std::string foundProperty;
+ for(const auto &p : properties) {
+ if(p.second.lowMessageName != messageName)
+ continue;
+ foundProperty = p.first;
+ candidateMessages.push_back(uri);
+ break;
+ }
+
+ if(foundProperty.size() > 0) {
+ Property property = properties.at(foundProperty);
+ if(property.type == "boolean")
+ property.value_bool = json_object_get_boolean(jvalue);
+ else if(property.type == "string")
+ property.value_string = std::string(json_object_get_string(jvalue));
+ else if(property.type == "double")
+ property.value_double = json_object_get_double(jvalue);
+ else if(property.type == "int")
+ property.value_int = json_object_get_int(jvalue);
+ else
+ NOTICE(binder_interface, "ERROR 3! unexpected type %s %s", property.description.c_str(), property.type.c_str());
+ properties[foundProperty] = property;
+ registeredObjects[uri] = properties;
+ }
+ }
+/** at that point all objects have been updated. Now lets see if we should also send back messages to our subscribers. */
+ for(const std::string &m : candidateMessages) {
+ for(const auto &p : events) {
+ if(startsWith(m, p.first)) {
+ std::map<std::string, json_object*> jsons;
+ for(const auto &pp : registeredObjects) {
+ if(startsWith(pp.first, p.first)) {
+ jsons[pp.first] = generateJson(pp.first);
+ }
+ }
+ json_object *j;
+ if(jsons.size() == 1) {
+ j = jsons[0];
+ } else if(jsons.size() > 1) {
+ j = json_object_new_object();
+ for(const auto &pp : jsons)
+ json_object_object_add(j, pp.first.c_str(), pp.second);
+ }
+ const int nbSubscribers = afb_event_push(p.second, j);
+ if(nbSubscribers == 0) {
+ afb_event_drop(p.second);
+ events.erase(p.first);
+ }
+ }
+ }
+ }
+}
+
+/// @brief Generate json message for a resource, in ViWi format. Based on resource definition extracted from json
+/// configuration file. If vector "fields" is not empty, will included only properties present in the vector.
+///
+/// @param[in] messageObject: resource's name, fields: list of properties to be included (NULL = all).
+///
+/// @return jsonObject containing the resource status for this resource's name.
+json_object *High::generateJson(const std::string &messageObject, std::vector<std::string> *fields)
+{
+ json_object *json = json_object_new_object();
+ const std::map<std::string, Property> props = registeredObjects.at(messageObject);
+ for(const auto &p : props) {
+ if(fields && fields->size() > 0 && p.first != "id" && p.first != "uri" && p.first != "name") {
+ if(std::find(fields->begin(), fields->end(), p.first) == fields->end())
+ continue;
+ }
+ if(p.second.type == "string") {
+ const std::string value = p.second.value_string;
+ json_object_object_add(json, p.first.c_str(), json_object_new_string(value.c_str()));
+ } else if(p.second.type == "boolean") {
+ const bool value = p.second.value_bool;
+ json_object_object_add(json, p.first.c_str(), json_object_new_boolean(value));
+ } else if(p.second.type == "double") {
+ const double value = p.second.value_double;
+ json_object_object_add(json, p.first.c_str(), json_object_new_double(value));
+ } else if(p.second.type == "int") {
+ const int value = p.second.value_int;
+ json_object_object_add(json, p.first.c_str(), json_object_new_int(value));
+ } else {
+ NOTICE(binder_interface, "ERROR 1! unexpected type %s %s %s", p.first.c_str(), p.second.description.c_str(), p.second.type.c_str());
+ }
+ }
+ return json;
+}
+
+/// @brief Generates a random UID
+///
+/// @return string containing the generated UID.
+std::string High::generateId() const
+{
+ char id[50];
+ sprintf(id, "%x-%x-%x-%x", (rand()%(int)1e7 + 1), (rand()%(int)1e7 + 1), (rand()%(int)1e7 + 1), (rand()%(int)1e7 + 1));
+ return std::string(id);
+}
+
+/// @brief Entry point for subscribing to a resource. Can optionnally include a time interval in ms.
+///
+/// @param[in] request: afb-request containing a json request, for instance {"name":"/car/demoboard/", "interval":1000}
+///
+/// @return true if subscribed succeeded, false otherwise.
+bool High::subscribe(afb_req request)
+{
+ /** /car/doors/3901a278-ba17-44d6-9aef-f7ca67c04840 */
+ bool ok = false;
+ json_object *args = afb_req_json(request);
+ json_object *nameJson = NULL;
+ json_object *intervalJson = NULL;
+ if(!json_object_object_get_ex(args, "name", &nameJson))
+ return false;
+ json_object_object_get_ex(args, "interval", &intervalJson);
+ int ms = -1;
+ if(intervalJson)
+ ms = json_object_get_int(intervalJson);
+ std::string message(json_object_get_string(nameJson));
+ if(message.size() == 0)
+ return ok;
+ for(const auto &p : registeredObjects) {
+ if(startsWith(p.first, message)) {
+ afb_event event;
+ if(ms <= 0) {
+ if(events.find(message) != events.end()) {
+ event = events.at(message);
+ } else {
+ event = afb_daemon_make_event(binder_interface->daemon, p.first.c_str());
+ events[message] = event;
+ }
+ if (afb_event_is_valid(event) && afb_req_subscribe(request, event) == 0) {
+ ok = true;
+ }
+ } else {
+ std::vector<TimedEvent> evts;
+ if(timedEvents.find(ms) != timedEvents.end())
+ evts = timedEvents.at(ms);
+ afb_event afbEvent;
+ bool found = false;
+ for(const auto & e : evts) {
+ if(e.name == message) {
+ afbEvent = e.event;
+ found = true;
+ break;
+ }
+ }
+ if(!found) {
+ char ext[20];
+ sprintf(ext, "_%d", ms);
+ std::string messageName = message + std::string(ext);
+ //NOTICE(binder_interface, "subscribe with interval %s", messageName.c_str());
+ afbEvent = afb_daemon_make_event(binder_interface->daemon, messageName.c_str());
+ if (!afb_event_is_valid(afbEvent)) {
+ NOTICE(binder_interface, "unable to create event");
+ return false;
+ }
+ TimedEvent e;
+ e.name = message;
+ e.eventName = messageName;
+ e.event = afbEvent;
+ e.interval = ms;
+ evts.push_back(e);
+ timedEvents[ms] = evts;
+ }
+ if(afb_req_subscribe(request, afbEvent) == 0) {
+ ok = true;
+ } else {
+ if(!found) {
+ evts.erase(evts.end() - 1);
+ timedEvents[ms] = evts;
+ }
+ }
+ if(timedEvents.size() == 0) {
+ timers.clear();
+ } else if(ok) {
+ startTimer(ms);
+ }
+ }
+ break;
+ }
+ }
+ return ok;
+}
+
+/// @brief Entry point for unsubscribing to a resource. Can optionnally include a time interval in ms.
+///
+/// @param[in] request: afb-request containing a json request, for instance {"name":"/car/demoboard/", "interval":1000}
+///
+/// @return true if unsubscription succeeded, false otherwise.
+bool High::unsubscribe(afb_req request)
+{
+ json_object *args = afb_req_json(request);
+ json_object *nameJson = NULL;
+ json_object *intervalJson = NULL;
+ if(!json_object_object_get_ex(args, "name", &nameJson))
+ return false;
+ json_object_object_get_ex(args, "interval", &intervalJson);
+ int ms = -1;
+ if(intervalJson)
+ ms = json_object_get_int(intervalJson);
+ std::string message(json_object_get_string(nameJson));
+ if(message.size() == 0)
+ return false;
+ if(ms <= 0) {
+ if(events.find(message) != events.end()) {
+ if(afb_req_unsubscribe(request, events.at(message)) == 0)
+ return true;
+ }
+ } else {
+ if(timedEvents.find(ms) == timedEvents.end())
+ return false;
+ const auto evts = timedEvents.at(ms);
+ afb_event afbEvent;
+ bool found = false;
+ for(const auto & e : evts) {
+ if(e.name == message) {
+ afbEvent = e.event;
+ found = true;
+ break;
+ }
+ }
+ if(!found)
+ return false;
+ if(afb_req_unsubscribe(request, afbEvent) == 0)
+ return true;
+ }
+ return false;
+}
+
+/// @brief entry point for get requests. Accepts an optional list of properties to be included.
+///
+/// @param[in] request: afb-request containing a json request, for instance {"name":"/car/demoboard/", "fields":["vehicleSpeed"]},
+/// **json: a pointer to a json object to be used for the reply.
+///
+/// @return true if get succeeded, false otherwise, and **json object generated with the reply.
+bool High::get(afb_req request, json_object **json)
+{
+ json_object *args = afb_req_json(request);
+ json_object *nameJson;
+ json_object *fieldsJson;
+ if(!json_object_object_get_ex(args, "name", &nameJson))
+ return false;
+ bool hasFields = json_object_object_get_ex(args, "fields", &fieldsJson);
+ std::vector<std::string> fields;
+ if(hasFields) {
+ int arraylen = json_object_array_length(fieldsJson);
+ json_object * jvalue;
+ for(int i = 0; i < arraylen; ++i) {
+ jvalue = json_object_array_get_idx(fieldsJson, i);
+ fields.push_back(json_object_get_string(jvalue));
+ }
+ }
+ const std::string name(json_object_get_string(nameJson));
+ std::map<std::string, json_object*> jsons;
+ for(const auto &p : registeredObjects) {
+ if(startsWith(p.first, name))
+ jsons[p.first] = generateJson(p.first, &fields);
+ }
+ if(jsons.size() == 0) {
+ return false;
+ }
+ json_object *j = json_object_new_object();
+ for(const auto &p : jsons) {
+ json_object_object_add(j, p.first.c_str(), p.second);
+ }
+ *json = j;
+ return true;
+}
+
+/// @brief Sub-routine (static) to check whether a string starts with another string
+///
+/// @param[in] s: string to scan, val: string to start with.
+///
+/// @return true if s starts with val, false otherwise.
+bool High::startsWith(const std::string &s, const std::string &val)
+{
+ if(val.size() > s.size())
+ return false;
+ return s.substr(0, val.size()) == val;
+}
diff --git a/high-can-binding/high.hpp b/high-can-binding/high.hpp
new file mode 100644
index 0000000..c015f4a
--- /dev/null
+++ b/high-can-binding/high.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <cstddef>
+#include <string>
+#include <vector>
+#include <set>
+#include <map>
+#include <json-c/json.h>
+#include <systemd/sd-event.h>
+extern "C"
+{
+ #include <afb/afb-binding.h>
+};
+
+struct TimedEvent {
+ int interval;
+ afb_event event;
+ std::string name;
+ std::string eventName;
+};
+struct Property {
+ /**
+ * alternatively, instead of a value per type, we could use boost::any, or in c++17 variant.
+ */
+ std::string type;
+ std::string description;
+ std::string lowMessageName;
+ int interval;
+ bool value_bool;
+ std::string value_string;
+ double value_double;
+ int value_int;
+};
+
+class High
+{
+public:
+ High();
+ void treatMessage(json_object *message);
+ bool subscribe(afb_req request);
+ bool unsubscribe(afb_req request);
+ bool get(afb_req request, json_object **json);
+ void tick(sd_event_source *source, const long &now, void *interv);
+ void startTimer(const int &t);
+ ~High();
+ void parseConfigAndSubscribe(afb_service service);
+ static bool startsWith(const std::string &s, const std::string &val);
+ static void callBackFromSubscribe(void *handle, int iserror, json_object *result);
+private:
+ std::map<std::string, afb_event> events;
+ std::map<int, std::vector<TimedEvent>> timedEvents;
+ std::map<std::string, std::map<std::string, Property>> registeredObjects;
+ std::map<std::string, std::set<std::string>> lowMessagesToObjects;
+ std::set<int> timers;
+ std::string generateId() const;
+ json_object *generateJson(const std::string &messageObject, std::vector<std::__cxx11::string> *fields = NULL);
+};
diff --git a/high.json b/high.json
new file mode 100644
index 0000000..fe91cc5
--- /dev/null
+++ b/high.json
@@ -0,0 +1,115 @@
+{
+ "resources": [{
+ "name": "/car/doors/",
+ "values": [{
+ "name": "front_left",
+ "position": "front_left",
+ "isDoorOpen": "${messages.doors.front_left.open,-1}",
+ "isWindowOpen": "${messages.windows.front_left.open,-1}"
+ },
+ {
+ "name": "front_right",
+ "position": "front_right",
+ "isDoorOpen": "${messages.doors.front_right.open,-1}",
+ "isWindowOpen": "${messages.windows.front_right.open,-1}"
+ },
+ {
+ "name": "rear_left",
+ "position": "rear_left",
+ "isDoorOpen": "${messages.doors.rear_left.open,-1}",
+ "isWindowOpen": "${messages.windows.rear_left.open,-1}"
+ },
+ {
+ "name": "rear_right",
+ "position": "rear_right",
+ "isDoorOpen": "${messages.doors.rear_right.open,-1}",
+ "isWindowOpen": "${messages.windows.rear_right.open,-1}"
+ }
+ ]
+ }, {
+ "name": "/car/demoboard/",
+ "values": [{
+ "name": "vehicleSpeed",
+ "unit": "km/h",
+ "speed": "${diagnostic_messages.vehicle.speed,10000}"
+ }, {
+ "name": "engineSpeed",
+ "unit": "rpm",
+ "rpm": "${diagnostic_messages.engine.speed,10000}"
+ }, {
+ "name": "fuelLevel",
+ "unit": "litre",
+ "level": "${diagnostic_messages.fuel.level,10000}"
+ }, {
+ "name": "engineLoad",
+ "unit": "Nm",
+ "load": "${diagnostic_messages.engine.load,10000}"
+ }]
+ }],
+
+ "definitions": [{
+ "name": "/car/doors/",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "identifier"
+ },
+ "uri": {
+ "type": "string",
+ "description": "object uri"
+ },
+ "name": {
+ "type": "string",
+ "description": "name"
+ },
+ "position": {
+ "type": "string",
+ "description": "the vehicle door position"
+ },
+ "isDoorOpen": {
+ "type": "boolean",
+ "description": "the door state; set to 'true' if the door is open"
+ },
+ "isWindowOpen": {
+ "type": "boolean",
+ "description": "the window state of this door; set to 'true' if window is open; set to JSON-undefined if window state is unknown or invalid"
+ }
+ }
+ }, {
+ "name": "/car/demoboard/",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "identifier"
+ },
+ "uri": {
+ "type": "string",
+ "description": "object uri"
+ },
+ "name": {
+ "type": "string",
+ "description": "name"
+ },
+ "unit": {
+ "type": "string",
+ "description": "units"
+ },
+ "speed": {
+ "type": "double",
+ "description": "vehicle centerpoint speed as shown by the instrument cluster"
+ },
+ "rpm": {
+ "type": "double",
+ "description": "engine rotations per minute"
+ },
+ "level": {
+ "type": "double",
+ "description": "level of tankage"
+ },
+ "load": {
+ "type": "double",
+ "description": "engine load"
+ }
+ }
+ }]
+}