From a27e152d46e2635b4f30a06647af6041c8b383a2 Mon Sep 17 00:00:00 2001 From: Romain Forlot Date: Thu, 31 Aug 2017 12:19:23 +0200 Subject: Renaming high-can to high-viwi more generic. Change-Id: Id543f9f5724321f8c12c2d386340a3cc1ff0dc81 Signed-off-by: Romain Forlot --- high-can-binding/CMakeLists.txt | 37 -- high-can-binding/high-can-binding-hat.cpp | 37 -- high-can-binding/high-can-binding-hat.hpp | 16 - high-can-binding/high-can-binding.cpp | 84 ---- high-can-binding/high.cpp | 631 ----------------------------- high-can-binding/high.hpp | 59 --- high-viwi-binding/CMakeLists.txt | 38 ++ high-viwi-binding/high-viwi-apidef.h | 94 +++++ high-viwi-binding/high-viwi-apidef.json | 148 +++++++ high-viwi-binding/high-viwi-binding.cpp | 89 +++++ high-viwi-binding/high-viwi-binding.hpp | 12 + high-viwi-binding/high.cpp | 637 ++++++++++++++++++++++++++++++ high-viwi-binding/high.hpp | 61 +++ 13 files changed, 1079 insertions(+), 864 deletions(-) delete mode 100644 high-can-binding/CMakeLists.txt delete mode 100644 high-can-binding/high-can-binding-hat.cpp delete mode 100644 high-can-binding/high-can-binding-hat.hpp delete mode 100644 high-can-binding/high-can-binding.cpp delete mode 100644 high-can-binding/high.cpp delete mode 100644 high-can-binding/high.hpp create mode 100644 high-viwi-binding/CMakeLists.txt create mode 100644 high-viwi-binding/high-viwi-apidef.h create mode 100644 high-viwi-binding/high-viwi-apidef.json create mode 100644 high-viwi-binding/high-viwi-binding.cpp create mode 100644 high-viwi-binding/high-viwi-binding.hpp create mode 100644 high-viwi-binding/high.cpp create mode 100644 high-viwi-binding/high.hpp diff --git a/high-can-binding/CMakeLists.txt b/high-can-binding/CMakeLists.txt deleted file mode 100644 index a380eb8..0000000 --- a/high-can-binding/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -########################################################################### -# Copyright 2015, 2016, 2017 IoT.bzh -# -# author: Fulup Ar Foll -# contrib: Romain Forlot -# -# 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) - - # Define project Targets - add_library(${TARGET_NAME} MODULE ${TARGET_NAME}-binding.cpp high-can-binding-hat.cpp high.cpp) - - # Binder exposes a unique public entry point - SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES - PREFIX "afb-" - LABELS "BINDING" - LINK_FLAGS ${BINDINGS_LINK_FLAG} - OUTPUT_NAME ${TARGET_NAME} - ) - - # Library dependencies (include updates automatically) - TARGET_LINK_LIBRARIES(${TARGET_NAME} - ${link_libraries} - ) diff --git a/high-can-binding/high-can-binding-hat.cpp b/high-can-binding/high-can-binding-hat.cpp deleted file mode 100644 index 8c747d1..0000000 --- a/high-can-binding/high-can-binding-hat.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "high-can-binding-hat.hpp" -#include -/// Interface between the daemon and the binding - -static int init_service(); - -static const struct afb_verb_v2 verbs[]= -{ - { .verb= "subscribe", .callback= subscribe, .auth = NULL, .info = "subscribe to an ViWi object", .session = 0 }, - { .verb= "unsubscribe", .callback= unsubscribe, .auth = NULL, .info = "unsubscribe to a ViWi object", .session = 0 }, - { .verb= "load", .callback= load, .auth = NULL, .info = "Load Viwi service Configuration", .session = 0 }, - { .verb= "get", .callback= get, .auth = NULL, .info = "Get informations about a resource or element", .session = 0 }, - { .verb= NULL, .callback=NULL, .auth = NULL, .info = NULL, .session = 0 } -}; - -const struct afb_binding_v2 afbBindingV2 = { - .api = "high-can", - .specification = "", - .info = "High CAN ViWi API connected to low-can AGL service", - .verbs = verbs, - .preinit = NULL, - .init = init_service, - .onevent = onEvent, - .noconcurrency = 1 -}; - -/// @brief Initialize the binding. -/// -/// @return Exit code, zero if success. -int init_service() -{ - AFB_NOTICE("high level binding is initializing"); - afb_daemon_require_api("low-can", 1); - initHigh(); - AFB_NOTICE("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 deleted file mode 100644 index bb912c1..0000000 --- a/high-can-binding/high-can-binding-hat.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include -#include -extern "C" -{ - #define AFB_BINDING_VERSION 2 - #include -}; - - void onEvent(const char *event, struct json_object *object); - void subscribe(afb_req request); - void unsubscribe(afb_req request); - void load(afb_req request); - void get(afb_req request); - void initHigh(); - int ticked(sd_event_source *source, uint64_t t, void *data); diff --git a/high-can-binding/high-can-binding.cpp b/high-can-binding/high-can-binding.cpp deleted file mode 100644 index 27a49f2..0000000 --- a/high-can-binding/high-can-binding.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015, 2016 "IoT.bzh" - * Author "Romain Forlot" - * Author "Loic Collignon" - * - * 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 -#include "high.hpp" -High high; - -/// @brief callback for receiving message from low binding. Treatment itself is made in High class. -void onEvent(const char *event, json_object *object) -{ - high.treatMessage(object); -} -/// @brief entry point for client subscription request. Treatment itself is made in High class. -void subscribe(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(afb_req request) -{ - if(high.unsubscribe(request)) - afb_req_success(request, NULL, NULL); - else - afb_req_fail(request, "error", NULL); -} - -/// @brief verb that loads JSON configuration (old high.json file now) -void load(afb_req request) -{ - json_object* args = afb_req_json(request); - const char* confd; - - wrap_json_unpack(args, "{s:s}", "path", &confd); - high.parseConfigAndSubscribe(confd); -} - -/// @brief entry point for get requests. Treatment itself is made in High class. -void get(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, uint64_t 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() -{ -} - - diff --git a/high-can-binding/high.cpp b/high-can-binding/high.cpp deleted file mode 100644 index ff252c0..0000000 --- a/high-can-binding/high.cpp +++ /dev/null @@ -1,631 +0,0 @@ -/** -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 -#include -#include "high.hpp" -#include "high-can-binding-hat.hpp" -#include -#include -#include -/// @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 : a vector containing each individual string after the split. -using namespace std; -template -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 split(const std::string &s, char delim) -{ - std::vector 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] confd - path to configuration directory which holds the binding configuration to load -/// -void High::parseConfigAndSubscribe(const std::string* confd) -{ - char *filename; - char*fullpath; - std::vector conf_files_path; - struct json_object* conf_filesJ = ScanForConfig(confd, CTL_SCAN_FLAT, "viwi", "json"); - if (!conf_filesJ || json_object_array_length(conf_filesJ) == 0) - { - AFB_ERROR("No JSON config files found in %s", confd); - return; - } - - for(int i=0; i < json_object_array_length(conf_filesJ); i++) - { - json_object *entryJ=json_object_array_get_idx(conf_filesJ, i); - - err= wrap_json_unpack (entryJ, "{s:s, s:s !}", "fullpath", &fullpath,"filename", &filename); - if (err) { - AFB_ERROR ("OOOPs invalid config file path = %s", json_object_get_string(entryJ)); - return; - } - std::string filepath = fullpath; - filepath += filename; - conf_files_path.push_back(filepath); - } - - json_object *config = json_object_from_file("high.json"); - json_object *jvalue, *jarray1, *jarray2, *obj; - std::map> 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 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 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()) - { - AFB_WARNING("Unable to find name %s in properties", name.c_str()); - continue; - } - const std::map props = properties[name]; - std::map 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()) - { - AFB_WARNING("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 params = split(canMessage, ','); - if(params.size() != 2) - { - AFB_WARNING("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 - AFB_ERROR("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 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("low-can", "subscribe", jobj, &dummy) < 0) - AFB_ERROR("high-can subscription to low-can FAILED %s", js.c_str()); - else - AFB_NOTICE("high-can subscribed to low-can %s", js.c_str()); - json_object_put(dummy); - } - json_object_put(config); - AFB_NOTICE("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(), 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, json_object *result) -{ - AFB_NOTICE("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; - AFB_NOTICE("tick! %d %ld", interval, now); - bool hasEvents = false; - if(timedEvents.find(interval) != timedEvents.end()) - { - std::vector evts = timedEvents[interval]; - for(int i = (int)evts.size() - 1; i >= 0; --i) - { - const TimedEvent e = evts.at(i); - std::map jsons; - for(const auto &pp : registeredObjects) - { - if(startsWith(pp.first, e.name)) - { - jsons[pp.first] = generateJson(pp.first); - } - } - json_object *j = json_object_new_object(); - if(jsons.size() == 1) - { - j = jsons[0]; - } else if(jsons.size() > 1) - { - 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; - } - //AFB_NOTICE("%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 { - //AFB_NOTICE("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()) - { - AFB_ERROR("message not linked to any object %s", json_object_get_string(message)); - return; - } -// AFB_NOTICE("message received %s", json_object_get_string(message)); - const std::set objects = lowMessagesToObjects.at(messageName); - std::vector candidateMessages; - for(const std::string &uri : objects) - { - std::map 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 - AFB_ERROR("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 jsons; - for(const auto &pp : registeredObjects) - { - if(startsWith(pp.first, p.first)) - { - jsons[pp.first] = generateJson(pp.first); - } - } - json_object *j = json_object_new_object(); - if(jsons.size() == 1) - { - j = jsons[0]; - } else if(jsons.size() > 1) - { - 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 *fields) -{ - json_object *json = json_object_new_object(); - const std::map 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 { - AFB_ERROR("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(p.first.c_str()); - events[message] = event; - } - if (afb_event_is_valid(event) && afb_req_subscribe(request, event) == 0) - { - ok = true; - } - } else { - std::vector 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); - //AFB_NOTICE("subscribe with interval %s", messageName.c_str()); - afbEvent = afb_daemon_make_event(messageName.c_str()); - if (!afb_event_is_valid(afbEvent)) - { - AFB_ERROR("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 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 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 deleted file mode 100644 index 7ccc87d..0000000 --- a/high-can-binding/high.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -extern "C" -{ - #define AFB_BINDING_VERSION 2 - #include -}; - -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; - Property() { interval = 0; value_bool = false; value_double = 0.0; value_int = 0;} -}; - -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(const std::string& confd); - static bool startsWith(const std::string &s, const std::string &val); - static void callBackFromSubscribe(void *handle, int iserror, json_object *result); -private: - std::map events; - std::map> timedEvents; - std::map> registeredObjects; - std::map> lowMessagesToObjects; - std::set timers; - std::string generateId() const; - json_object *generateJson(const std::string &messageObject, std::vector *fields = nullptr); -}; diff --git a/high-viwi-binding/CMakeLists.txt b/high-viwi-binding/CMakeLists.txt new file mode 100644 index 0000000..18a4e79 --- /dev/null +++ b/high-viwi-binding/CMakeLists.txt @@ -0,0 +1,38 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll +# contrib: Romain Forlot +# +# 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-viwi) + + # Define project Targets + add_library(${TARGET_NAME} MODULE ${TARGET_NAME}-binding.cpp high.cpp) + + # Binder exposes a unique public entry point + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + PREFIX "afb-" + LABELS "BINDINGV2" + LINK_FLAGS ${BINDINGS_LINK_FLAG} + OUTPUT_NAME ${TARGET_NAME} + ) + + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + afb-utilities + ${link_libraries} + ) diff --git a/high-viwi-binding/high-viwi-apidef.h b/high-viwi-binding/high-viwi-apidef.h new file mode 100644 index 0000000..fec9d26 --- /dev/null +++ b/high-viwi-binding/high-viwi-apidef.h @@ -0,0 +1,94 @@ + +static const char _afb_description_v2_high_viwi[] = + "{\"openapi\":\"3.0.0\",\"$schema\":\"http:iot.bzh/download/openapi/schem" + "a-3.0/default-schema.json\",\"info\":{\"description\":\"\",\"title\":\"h" + "igh-level-viwi-service\",\"version\":\"4.0\",\"x-binding-c-generator\":{" + "\"api\":\"high-viwi\",\"version\":2,\"prefix\":\"\",\"postfix\":\"\",\"s" + "tart\":null,\"onevent\":\"onEvent\",\"init\":\"init_service\",\"scope\":" + "\"\",\"private\":false}},\"servers\":[{\"url\":\"ws://{host}:{port}/api/" + "monitor\",\"description\":\"High ViWi API connected to low level AGL ser" + "vices\",\"variables\":{\"host\":{\"default\":\"localhost\"},\"port\":{\"" + "default\":\"1234\"}},\"x-afb-events\":[{\"$ref\":\"#/components/schemas/" + "afb-event\"}]}],\"components\":{\"schemas\":{\"afb-reply\":{\"$ref\":\"#" + "/components/schemas/afb-reply-v2\"},\"afb-event\":{\"$ref\":\"#/componen" + "ts/schemas/afb-event-v2\"},\"afb-reply-v2\":{\"title\":\"Generic respons" + "e.\",\"type\":\"object\",\"required\":[\"jtype\",\"request\"],\"properti" + "es\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-reply\"},\"request\"" + ":{\"type\":\"object\",\"required\":[\"status\"],\"properties\":{\"status" + "\":{\"type\":\"string\"},\"info\":{\"type\":\"string\"},\"token\":{\"typ" + "e\":\"string\"},\"uuid\":{\"type\":\"string\"},\"reqid\":{\"type\":\"str" + "ing\"}}},\"response\":{\"type\":\"object\"}}},\"afb-event-v2\":{\"type\"" + ":\"object\",\"required\":[\"jtype\",\"event\"],\"properties\":{\"jtype\"" + ":{\"type\":\"string\",\"const\":\"afb-event\"},\"event\":{\"type\":\"str" + "ing\"},\"data\":{\"type\":\"object\"}}}},\"x-permissions\":{},\"response" + "s\":{\"200\":{\"description\":\"A complex object array response\",\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/afb-reply\"}}}}}},\"paths\":{\"/subscribe\":{\"description\":\"Subscrib" + "e to a ViWi object\",\"parameters\":[{\"in\":\"query\",\"name\":\"event\"" + ",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"" + "200\":{\"$ref\":\"#/components/responses/200\"}}},\"/unsubscribe\":{\"de" + "scription\":\"Unsubscribe previously suscribed ViWi objects.\",\"paramet" + "ers\":[{\"in\":\"query\",\"name\":\"event\",\"required\":false,\"schema\"" + ":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components" + "/responses/200\"}}},\"/get\":{\"description\":\"Get informations about a" + " resource or element\",\"responses\":{\"200\":{\"$ref\":\"#/components/r" + "esponses/200\"}}},\"/load\":{\"description\":\"Load config file in direc" + "tory passed as argument\",\"parameters\":[{\"in\":\"query\",\"name\":\"p" + "ath\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\"" + ":{\"200\":{\"$ref\":\"#/components/responses/200\"}}}}}" +; + + void subscribe(struct afb_req req); + void unsubscribe(struct afb_req req); + void get(struct afb_req req); + void load(struct afb_req req); + +static const struct afb_verb_v2 _afb_verbs_v2_high_viwi[] = { + { + .verb = "subscribe", + .callback = subscribe, + .auth = NULL, + .info = "Subscribe to a ViWi object", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "unsubscribe", + .callback = unsubscribe, + .auth = NULL, + .info = "Unsubscribe previously suscribed ViWi objects.", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get", + .callback = get, + .auth = NULL, + .info = "Get informations about a resource or element", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "load", + .callback = load, + .auth = NULL, + .info = "Load config file in directory passed as argument", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = NULL, + .callback = NULL, + .auth = NULL, + .info = NULL, + .session = 0 + } +}; + +const struct afb_binding_v2 afbBindingV2 = { + .api = "high-viwi", + .specification = _afb_description_v2_high_viwi, + .info = "", + .verbs = _afb_verbs_v2_high_viwi, + .preinit = NULL, + .init = init_service, + .onevent = onEvent, + .noconcurrency = 0 +}; + diff --git a/high-viwi-binding/high-viwi-apidef.json b/high-viwi-binding/high-viwi-apidef.json new file mode 100644 index 0000000..16c2d0b --- /dev/null +++ b/high-viwi-binding/high-viwi-apidef.json @@ -0,0 +1,148 @@ +{ + "openapi": "3.0.0", + "$schema": "http:iot.bzh/download/openapi/schema-3.0/default-schema.json", + "info": { + "description": "", + "title": "high-level-viwi-service", + "version": "4.0", + "x-binding-c-generator": { + "api": "high-viwi", + "version": 2, + "prefix": "", + "postfix": "", + "start": null , + "onevent": "onEvent", + "init": "init_service", + "scope": "", + "private": false + } + }, + "servers": [ + { + "url": "ws://{host}:{port}/api/monitor", + "description": "High ViWi API connected to low level AGL services", + "variables": { + "host": { + "default": "localhost" + }, + "port": { + "default": "1234" + } + }, + "x-afb-events": [ + { + "$ref": "#/components/schemas/afb-event" + } + ] + } + ], + "components": { + "schemas": { + "afb-reply": { + "$ref": "#/components/schemas/afb-reply-v2" + }, + "afb-event": { + "$ref": "#/components/schemas/afb-event-v2" + }, + "afb-reply-v2": { + "title": "Generic response.", + "type": "object", + "required": [ "jtype", "request" ], + "properties": { + "jtype": { + "type": "string", + "const": "afb-reply" + }, + "request": { + "type": "object", + "required": [ "status" ], + "properties": { + "status": { "type": "string" }, + "info": { "type": "string" }, + "token": { "type": "string" }, + "uuid": { "type": "string" }, + "reqid": { "type": "string" } + } + }, + "response": { "type": "object" } + } + }, + "afb-event-v2": { + "type": "object", + "required": [ "jtype", "event" ], + "properties": { + "jtype": { + "type": "string", + "const": "afb-event" + }, + "event": { "type": "string" }, + "data": { "type": "object" } + } + } + }, + "x-permissions": { + }, + "responses": { + "200": { + "description": "A complex object array response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/afb-reply" + } + } + } + } + } + }, + "paths": { + "/subscribe": { + "description": "Subscribe to a ViWi object", + "parameters": [ + { + "in": "query", + "name": "event", + "required": false, + "schema": { "type": "string" } + } + ], + "responses": { + "200": {"$ref": "#/components/responses/200"} + } + }, + "/unsubscribe": { + "description": "Unsubscribe previously suscribed ViWi objects.", + "parameters": [ + { + "in": "query", + "name": "event", + "required": false, + "schema": { "type": "string" } + } + ], + "responses": { + "200": {"$ref": "#/components/responses/200"} + } + }, + "/get": { + "description": "Get informations about a resource or element", + "responses": { + "200": {"$ref": "#/components/responses/200"} + } + }, + "/load": { + "description": "Load config file in directory passed as argument", + "parameters": [ + { + "in": "query", + "name": "path", + "required": true, + "schema": { "type": "string"} + } + ], + "responses": { + "200": {"$ref": "#/components/responses/200"} + } + } + } +} diff --git a/high-viwi-binding/high-viwi-binding.cpp b/high-viwi-binding/high-viwi-binding.cpp new file mode 100644 index 0000000..f86fc67 --- /dev/null +++ b/high-viwi-binding/high-viwi-binding.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015, 2016 "IoT.bzh" + * Author "Romain Forlot" + * Author "Loic Collignon" + * + * 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 + +#include "high-viwi-binding.hpp" +#include "high-viwi-apidef.h" +#include "wrap-json.h" +#include "high.hpp" + +High high; + +/// @brief callback for receiving message from low binding. Treatment itself is made in High class. +void onEvent(const char *event, json_object *object) +{ + high.treatMessage(object); +} +/// @brief entry point for client subscription request. Treatment itself is made in High class. +void subscribe(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(afb_req request) +{ + if(high.unsubscribe(request)) + afb_req_success(request, NULL, NULL); + else + afb_req_fail(request, "error", NULL); +} + +/// @brief verb that loads JSON configuration (old high.json file now) +void load(afb_req request) +{ + json_object* args = afb_req_json(request); + const char* confd; + + wrap_json_unpack(args, "{s:s}", "path", &confd); + high.parseConfigAndSubscribe(confd); +} + +/// @brief entry point for get requests. Treatment itself is made in High class. +void get(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, uint64_t t, void* data) +{ + high.tick(source, t, data); + return 0; +} + +/// @brief Initialize the binding. +/// +/// @return Exit code, zero if success. +int init_service() +{ + AFB_DEBUG("High level binding is initializing"); + AFB_NOTICE("High level binding is initialized and running"); + return 0; +} diff --git a/high-viwi-binding/high-viwi-binding.hpp b/high-viwi-binding/high-viwi-binding.hpp new file mode 100644 index 0000000..711e531 --- /dev/null +++ b/high-viwi-binding/high-viwi-binding.hpp @@ -0,0 +1,12 @@ +#pragma once +#include +#include +extern "C" +{ + #define AFB_BINDING_VERSION 2 + #include +}; + + void onEvent(const char *event, struct json_object *object); + int init_service(); + int ticked(sd_event_source *source, uint64_t t, void *data); diff --git a/high-viwi-binding/high.cpp b/high-viwi-binding/high.cpp new file mode 100644 index 0000000..a7c00cc --- /dev/null +++ b/high-viwi-binding/high.cpp @@ -0,0 +1,637 @@ +/** +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 +#include +#include +#include +#include + +#include "filescan-utils.h" +#include "wrap-json.h" +#include "high.hpp" +#include "high-viwi-binding.hpp" + +/// @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 : a vector containing each individual string after the split. +using namespace std; +template +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 split(const std::string &s, char delim) +{ + std::vector 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] confd - path to configuration directory which holds the binding configuration to load +/// +void High::parseConfigAndSubscribe(const std::string& confd) +{ + char* filename; + char* fullpath; + std::vector conf_files_path; + + // Grab all config files with 'viwi' in their names in the path provided + struct json_object* conf_filesJ = ScanForConfig(confd.c_str(), CTL_SCAN_FLAT, "viwi", "json"); + if (!conf_filesJ || json_object_array_length(conf_filesJ) == 0) + { + AFB_ERROR("No JSON config files found in %s", confd.c_str()); + return; + } + + for(int i=0; i < json_object_array_length(conf_filesJ); i++) + { + json_object *entryJ=json_object_array_get_idx(conf_filesJ, i); + + int err = wrap_json_unpack (entryJ, "{s:s, s:s !}", "fullpath", &fullpath,"filename", &filename); + if (err) { + AFB_ERROR ("OOOPs invalid config file path = %s", json_object_get_string(entryJ)); + return; + } + std::string filepath = fullpath; + filepath += filename; + conf_files_path.push_back(filepath); + } + + json_object *config = json_object_from_file("high.json"); + json_object *jvalue, *jarray1, *jarray2, *obj; + std::map> 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 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 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()) + { + AFB_WARNING("Unable to find name %s in properties", name.c_str()); + continue; + } + const std::map props = properties[name]; + std::map 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()) + { + AFB_WARNING("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 params = split(canMessage, ','); + if(params.size() != 2) + { + AFB_WARNING("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 + AFB_ERROR("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 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("low-can", "subscribe", jobj, &dummy) < 0) + AFB_ERROR("high-can subscription to low-can FAILED %s", js.c_str()); + else + AFB_NOTICE("high-can subscribed to low-can %s", js.c_str()); + json_object_put(dummy); + } + json_object_put(config); + AFB_NOTICE("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(), 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, json_object *result) +{ + AFB_NOTICE("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; + AFB_NOTICE("tick! %d %ld", interval, now); + bool hasEvents = false; + if(timedEvents.find(interval) != timedEvents.end()) + { + std::vector evts = timedEvents[interval]; + for(int i = (int)evts.size() - 1; i >= 0; --i) + { + const TimedEvent e = evts.at(i); + std::map jsons; + for(const auto &pp : registeredObjects) + { + if(startsWith(pp.first, e.name)) + { + jsons[pp.first] = generateJson(pp.first); + } + } + json_object *j = json_object_new_object(); + if(jsons.size() == 1) + { + j = jsons[0]; + } else if(jsons.size() > 1) + { + 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; + } + //AFB_NOTICE("%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 { + //AFB_NOTICE("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()) + { + AFB_ERROR("message not linked to any object %s", json_object_get_string(message)); + return; + } +// AFB_NOTICE("message received %s", json_object_get_string(message)); + const std::set objects = lowMessagesToObjects.at(messageName); + std::vector candidateMessages; + for(const std::string &uri : objects) + { + std::map 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 + AFB_ERROR("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 jsons; + for(const auto &pp : registeredObjects) + { + if(startsWith(pp.first, p.first)) + { + jsons[pp.first] = generateJson(pp.first); + } + } + json_object *j = json_object_new_object(); + if(jsons.size() == 1) + { + j = jsons[0]; + } else if(jsons.size() > 1) + { + 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 *fields) +{ + json_object *json = json_object_new_object(); + const std::map 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 { + AFB_ERROR("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(p.first.c_str()); + events[message] = event; + } + if (afb_event_is_valid(event) && afb_req_subscribe(request, event) == 0) + { + ok = true; + } + } else { + std::vector 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); + //AFB_NOTICE("subscribe with interval %s", messageName.c_str()); + afbEvent = afb_daemon_make_event(messageName.c_str()); + if (!afb_event_is_valid(afbEvent)) + { + AFB_ERROR("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 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 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-viwi-binding/high.hpp b/high-viwi-binding/high.hpp new file mode 100644 index 0000000..525f9f9 --- /dev/null +++ b/high-viwi-binding/high.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +extern "C" +{ + #define AFB_BINDING_VERSION 2 + #include +}; + +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; + Property() { interval = 0; value_bool = false; value_double = 0.0; value_int = 0;} +}; + +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(const std::string& confd); + void loadDefinition(const json_object* definitionsJ); + void loadResources(const json_object* resourcesJ); + static bool startsWith(const std::string &s, const std::string &val); + static void callBackFromSubscribe(void *handle, int iserror, json_object *result); +private: + std::map events; + std::map> timedEvents; + std::map> registeredObjects; + std::map> lowMessagesToObjects; + std::set timers; + std::string generateId() const; + json_object *generateJson(const std::string &messageObject, std::vector *fields = nullptr); +}; -- cgit