From 22c2633ac833ae78ff84beafce19cec0f05758a2 Mon Sep 17 00:00:00 2001 From: Loïc Collignon Date: Tue, 12 Jun 2018 11:00:25 +0200 Subject: Update to last softmixer and hal, plus some fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the last 4a-softmixer and 4a-hal-generic versions. Fix some potential crash and performance bottleneck. Now support sessions to disallow multiple opening or closing by not owner. Change-Id: I95b020a3fab03b1e1058812adae1d8d5986f282b Signed-off-by: Loïc Collignon --- ahl-binding/CMakeLists.txt | 1 - ahl-binding/ahl-4a.cpp | 49 ---- ahl-binding/ahl-4a.hpp | 47 ---- ahl-binding/ahl-binding.cpp | 428 +++++++++++++++------------------- ahl-binding/ahl-binding.hpp | 52 ++--- ahl-binding/role.cpp | 200 ++++++++++++++-- ahl-binding/role.hpp | 30 ++- conf.d/project/policy-4a-sample1.json | 37 ++- 8 files changed, 435 insertions(+), 409 deletions(-) delete mode 100644 ahl-binding/ahl-4a.cpp delete mode 100644 ahl-binding/ahl-4a.hpp diff --git a/ahl-binding/CMakeLists.txt b/ahl-binding/CMakeLists.txt index 0e5df43..bd2be0c 100644 --- a/ahl-binding/CMakeLists.txt +++ b/ahl-binding/CMakeLists.txt @@ -23,7 +23,6 @@ PROJECT_TARGET_ADD(audiohighlevel) ADD_LIBRARY(${TARGET_NAME} MODULE config_entry.cpp role.cpp - ahl-4a.cpp interrupt.cpp ahl-binding.cpp ) diff --git a/ahl-binding/ahl-4a.cpp b/ahl-binding/ahl-4a.cpp deleted file mode 100644 index b5fdc16..0000000 --- a/ahl-binding/ahl-4a.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Loïc 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 "ahl-4a.hpp" - -ahl_4a_t::ahl_4a_t(json_object* j) -{ - jcast_array(hals_, j, "hals"); - jcast_array(roles_, j, "roles"); -} - -ahl_4a_t& ahl_4a_t::operator<<(json_object* j) -{ - jcast_array(hals_, j, "hals"); - jcast_array(roles_, j, "roles"); - return *this; -} - -const std::vector& ahl_4a_t::hals() const -{ - return hals_; -} - -const std::vector& ahl_4a_t::roles() const -{ - return roles_; -} - -void ahl_4a_t::set_device_uri(std::string stream, std::string device_uri) -{ - for(auto& r : roles_) - { - if (r.stream() == stream) r.device_uri(device_uri); - } -} diff --git a/ahl-binding/ahl-4a.hpp b/ahl-binding/ahl-4a.hpp deleted file mode 100644 index acab71e..0000000 --- a/ahl-binding/ahl-4a.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Loïc 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 "jsonc_utils.hpp" -#include "role.hpp" - -class ahl_4a_t -{ -private: - std::vector hals_; - std::vector roles_; - -public: - explicit ahl_4a_t() = default; - explicit ahl_4a_t(const ahl_4a_t&) = default; - explicit ahl_4a_t(ahl_4a_t&&) = default; - - ~ahl_4a_t() = default; - - ahl_4a_t& operator=(const ahl_4a_t&) = default; - ahl_4a_t& operator=(ahl_4a_t&&) = default; - - explicit ahl_4a_t(json_object* j); - ahl_4a_t& operator<<(json_object* j); - - const std::vector& hals() const; - const std::vector& roles() const; - void set_device_uri(std::string stream, std::string device_uri); -}; - diff --git a/ahl-binding/ahl-binding.cpp b/ahl-binding/ahl-binding.cpp index fa4c74b..2bf1664 100644 --- a/ahl-binding/ahl-binding.cpp +++ b/ahl-binding/ahl-binding.cpp @@ -20,6 +20,16 @@ afb_dynapi* AFB_default; // BUG: Is it possible to get rid of this ? +/** + * @brief Callback invoked on new api creation. + * @param[in] handle Handle to the new api. + * @return Status code, zero if success. + */ +int ahl_api_create(void*, struct afb_dynapi* handle) +{ + return ahl_binding_t::instance().preinit(handle); +} + /** * @brief Entry point for dynamic API. * @param[in] handle Handle to start with for API creation. @@ -43,26 +53,80 @@ int afbBindingVdyn(afb_dynapi* handle) } /** - * @brief Callback to create the new api. - * @param[in] handle Handle to the new api. + * @brief Callback invoked when API enter the init phase. * @return Status code, zero if success. */ -int ahl_api_create(void*, struct afb_dynapi* handle) +int ahl_api_init(afb_dynapi*) { - return ahl_binding_t::instance().preinit(handle); + return ahl_binding_t::instance().init(); } +/** + * @brief Callback invoked when an event is received. + * @param[in] e Event's name. + * @param[in] o Event's args. + */ +void ahl_api_on_event(afb_dynapi*, const char* e, struct json_object* o) +{ + ahl_binding_t::instance().event(e, o); +} + +/** + * @brief Callback invoked when a 'roles' section is found in config file. + * @param[in] o Config section to handle. + * @return Status code, zero if success. + */ +int ahl_api_config_roles(afb_dynapi*, CtlSectionT*, json_object* o) +{ + return ahl_binding_t::instance().parse_roles_config(o); +} + +/** + * @brief Callback invoked when clients call the verb 'get_roles'. + * @param[in] req Request to handle. + */ +void ahl_api_get_roles(afb_request* req) +{ + ahl_binding_t::instance().get_roles(req); +} + +/** + * @brief Callback invoked when clients call a 'role' verb. + * @param[in] req Request to handle. + * + * Handle dynamic verbs based on role name ('multimedia', 'navigation', ...) + */ +void ahl_api_role(afb_request* req) +{ + role_t* role = (role_t*)req->vcbdata; + assert(role != nullptr); + + role->invoke(req); +} + +/** + * @brief Default constructor. + */ ahl_binding_t::ahl_binding_t() : handle_{nullptr} { } +/** + * @brief Get the singleton instance. + * @return The unique instance. + */ ahl_binding_t& ahl_binding_t::instance() { static ahl_binding_t s; return s; } +/** + * @brief This method is called during the pre-init phase of loading the binding. + * @param[in] handle Handle to the api. + * @return Status code, zero if success. + */ int ahl_binding_t::preinit(afb_dynapi* handle) { handle_ = handle; @@ -70,17 +134,13 @@ int ahl_binding_t::preinit(afb_dynapi* handle) try { load_static_verbs(); - load_controller_api(); + load_controller_configs(); - if (afb_dynapi_on_event(handle_, - [](afb_dynapi*, const char* e, struct json_object* o) - { ahl_binding_t::instance().event(e, o); } - ) - ) throw std::runtime_error("Failed to register event handler callback."); + if (afb_dynapi_on_event(handle_, ahl_api_on_event)) + throw std::runtime_error("Failed to register event handler callback."); - if (afb_dynapi_on_init(handle_, - [](afb_dynapi*) { return ahl_binding_t::instance().init(); } - )) throw std::runtime_error("Failed to register init handler callback."); + if (afb_dynapi_on_init(handle_, ahl_api_init)) + throw std::runtime_error("Failed to register init handler callback."); } catch(std::exception& e) { @@ -91,6 +151,9 @@ int ahl_binding_t::preinit(afb_dynapi* handle) return 0; } +/** + * @brief Initialize the API. + */ int ahl_binding_t::init() { using namespace std::placeholders; @@ -109,73 +172,86 @@ int ahl_binding_t::init() } AFB_DYNAPI_NOTICE(handle_, "Required 'smixer' API found!"); - // Requires corresponding API - for(const auto& h : config_.hals()) + afb_dynapi_seal(handle_); + AFB_DYNAPI_NOTICE(handle_, "API is now sealed!"); + + if (update_streams()) return -1; + return 0; +} + +/** + * @brief Update audio roles definition by binding to streams. + * @return Status code, zero if success. + */ +int ahl_binding_t::update_streams() +{ + json_object* loaded = nullptr; + json_object* response = nullptr; + size_t i = 0, j = 0; + size_t hals_count = 0, streams_count = 0; + + if (afb_dynapi_call_sync(handle_, "4a-hal-manager", "loaded", json_object_new_object(), &loaded)) + { + AFB_DYNAPI_ERROR(handle_, "Failed to call 'loaded' verb on '4a-hal-manager' API!"); + if (loaded) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(loaded)); + return -1; + } + response = json_object_object_get(loaded, "response"); + hals_count = json_object_array_length(response); + + for(i = 0; i < hals_count; ++i) { - if (afb_dynapi_require_api(handle_, h.c_str(), 1)) + json_object* info = nullptr; + json_object* streams = nullptr; + const char* halname = json_object_get_string(json_object_array_get_idx(response, i)); + AFB_DYNAPI_DEBUG(handle_, "Found an active HAL: %s", halname); + + if (afb_dynapi_call_sync(handle_, halname, "info", json_object_new_object(), &info)) { - AFB_DYNAPI_ERROR(handle_, "Failed to require '%s' API!", h.c_str()); + AFB_DYNAPI_ERROR(handle_, "Failed to call 'info' verb on '%s' API!", halname); + if (info) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(info)); return -1; } - AFB_DYNAPI_NOTICE(handle_, "Required '%s' API found!", h.c_str()); - json_object* result = nullptr; - - // if(afb_dynapi_call_sync(handle_, h.c_str(), "init-mixer", nullptr, &result)) - // { - // AFB_DYNAPI_ERROR(handle_, "Failed to call 'init-mixer' verb on '%s' API!", h.c_str()); - // if (result) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(result)); - // return -1; - // } - // AFB_DYNAPI_NOTICE(handle_, "Mixer initialized using '%s' API.", h.c_str()); - - // json_object* response = nullptr; - // json_object* verbose = json_object_new_object(); - // json_object_object_add(verbose, "verbose", json_object_new_int(1)); - // if (afb_dynapi_call_sync(handle_, h.c_str(), "list", verbose, &response)) - // { - // AFB_DYNAPI_ERROR(handle_, "Failed to call 'list' verb on '%s' API!", h.c_str()); - // if (result) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(result)); - // return -1; - // } - - json_object* response = nullptr; - json_object* arg = json_object_new_object(); - json_object_object_add(arg, "streams", json_object_new_boolean(TRUE)); - - if (afb_dynapi_call_sync(handle_, "smixer", "info", arg, &response)) + + streams = json_object_object_get(json_object_object_get(info, "response"), "streams"); + streams_count = json_object_array_length(streams); + for(j = 0; j < streams_count; ++j) { - AFB_DYNAPI_ERROR(handle_, "Failed to call 'list' verb on '%s' API!", h.c_str()); - if (result) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(result)); - return -1; - } + update_stream( + halname, + json_object_get_string(json_object_object_get(json_object_array_get_idx(streams, j), "name")), + json_object_get_string(json_object_object_get(json_object_array_get_idx(streams, j), "cardId")) + ); + } + json_object_put(info); + } + json_object_put(loaded); + + return 0; +} - json_object* streams = json_object_object_get(response, "response"); - AFB_DYNAPI_DEBUG(handle_, "Called 'list' verb on '%s' API: %s", h.c_str(), json_object_to_json_string(streams)); - json_object* array = json_object_object_get(streams, "streams"); - size_t streams_count = json_object_array_length(array); - for(size_t i = 0; i < streams_count; ++i) +/** + * @brief Update the stream info for audio roles. + * @param[in] halname The hal on which the stream is. + * @param[in] stream The name of the stream. + * @param[in] deviceid The device ID to return when opening an audio role. + */ +void ahl_binding_t::update_stream(std::string halname, std::string stream, std::string deviceid) +{ + for(auto& r : roles_) + { + if(r.stream() == stream) { - std::string stream_name; - std::string device_uri; - json_object* item = json_object_array_get_idx(array, i); - - jcast(stream_name, item, "uid"); - jcast(device_uri, item, "alsa"); - - config_.set_device_uri(stream_name, device_uri); + if (r.device_uri().size()) + AFB_DYNAPI_WARNING(handle_, "Multiple stream with same name: '%s'.", stream.c_str()); + else + { + r.device_uri(deviceid); + r.hal(halname); + } } } - - afb_dynapi_seal(handle_); - AFB_DYNAPI_NOTICE(handle_, "API is now sealed!"); - - actions_["volume"] = std::bind(&ahl_binding_t::volume, this, _1, _2, _3, _4); - actions_["open"] = std::bind(&ahl_binding_t::open, this, _1, _2, _3, _4); - actions_["close"] = std::bind(&ahl_binding_t::close, this, _1, _2, _3, _4); - actions_["interrupt"] = std::bind(&ahl_binding_t::interrupt, this, _1, _2, _3, _4); - - return 0; } void ahl_binding_t::event(std::string name, json_object* arg) @@ -189,7 +265,7 @@ void ahl_binding_t::load_static_verbs() handle_, "get_roles", "Retrieve array of available audio roles", - [](afb_request* r) { ahl_binding_t::instance().get_roles(r); }, + ahl_api_get_roles, nullptr, nullptr, AFB_SESSION_NONE_V2)) @@ -198,7 +274,7 @@ void ahl_binding_t::load_static_verbs() } } -void ahl_binding_t::load_controller_api() +void ahl_binding_t::load_controller_configs() { char* dir_list = getenv("CONTROL_CONFIG_PATH"); if (!dir_list) dir_list = strdup(CONTROL_CONFIG_PATH); @@ -241,17 +317,8 @@ int ahl_binding_t::load_controller_config(const std::string& path) {.key = "onload", .uid = nullptr, .info = nullptr, .loadCB = OnloadConfig, .handle = nullptr, .actions = nullptr}, {.key = "controls", .uid = nullptr, .info = nullptr, .loadCB = ControlConfig, .handle = nullptr, .actions = nullptr}, {.key = "events", .uid = nullptr, .info = nullptr, .loadCB = EventConfig, .handle = nullptr, .actions = nullptr}, - { - .key = "ahl-4a", - .uid = nullptr, - .info = nullptr, - .loadCB = [](afb_dynapi*, CtlSectionT* s, json_object* o){ - return ahl_binding_t::instance().load_config(s, o); - }, - .handle = nullptr, - .actions = nullptr - }, - {.key = nullptr, .uid = nullptr, .info = nullptr, .loadCB = nullptr, .handle = nullptr, .actions = nullptr} + {.key = "roles", .uid = nullptr, .info = nullptr, .loadCB = ahl_api_config_roles, .handle = nullptr, .actions = nullptr }, + {.key = nullptr, .uid = nullptr, .info = nullptr, .loadCB = nullptr, .handle = nullptr, .actions = nullptr} }; CtlLoadSections(handle_, controller_config, controller_sections); @@ -259,187 +326,62 @@ int ahl_binding_t::load_controller_config(const std::string& path) return 0; } -int ahl_binding_t::load_config(CtlSectionT* section, json_object* o) +int ahl_binding_t::parse_roles_config(json_object* o) { - config_ << o; + assert(o != nullptr); + assert(json_object_is_type(o, json_type_array)); - // Add corresponding verbs - for(const auto& r : config_.roles()) + size_t count = json_object_array_length(o); + roles_.reserve(count); + for(size_t i = 0; i < count; ++i) { - AFB_DYNAPI_NOTICE(handle_, "New audio role: %s", r.name().c_str()); + json_object* jr = json_object_array_get_idx(o, i); + assert(jr != nullptr); - if (afb_dynapi_add_verb( - handle_, - r.name().c_str(), - r.description().c_str(), - [](afb_request* r) { ahl_binding_t::instance().audiorole(r); }, - nullptr, - nullptr, - AFB_SESSION_NONE_V2)) - { - std::stringstream ss; - ss << "Failed to add '" << r.name() << "' verb to the API."; - throw std::runtime_error(ss.str()); - } + roles_.push_back(role_t(jr)); + role_t& r = roles_[roles_.size() - 1]; + if(create_api_verb(&r)) + return -1; } - - return 0; -} -void ahl_binding_t::audiorole(afb_request* req) -{ - std::string verb = afb_request_get_verb(req); - const auto& roles = config_.roles(); - auto r = std::find_if(roles.cbegin(), roles.cend(), [&verb](const role_t& r) { return r.name() == verb; }); - - if (r == roles.cend()) - { - afb_request_fail(req, "The requested role doesn't exist!", nullptr); - return; - } - - json_object* query = json_object_get(afb_request_json(req)); - - std::string action; - jcast(action, query, "action"); - std::transform(action.begin(), action.end(), action.begin(), ::tolower); - - auto a = actions_.find(action); - if (a == actions_.end()) - { - afb_request_fail(req, "The requested action doesn't exist!", nullptr); - return; - } - a->second(req, verb, r->stream(), query); + return 0; } -void ahl_binding_t::get_roles(afb_request* req) +int ahl_binding_t::create_api_verb(role_t* r) { - json_object* result = json_object_new_array(); - for(const auto& r : config_.roles()) - json_object_array_add(result, json_object_new_string(r.name().c_str())); - afb_request_success(req, result, nullptr); -} + AFB_DYNAPI_NOTICE(handle_, "New audio role: %s", r->uid().c_str()); -void ahl_binding_t::volume(afb_request* req, std::string role, std::string stream, json_object* arg) -{ - json_object* value = json_object_object_get(arg, "value"); - json_object_get(value); - - if (!value) - { - afb_request_fail(req, "No value given!", nullptr); - return; - } - - json_object* a = json_object_new_object(); - json_object_object_add(a, "volume", value); - AFB_DYNAPI_DEBUG(handle_, "Call the HAL with the following argument: %s", json_object_to_json_string(a)); - - afb_dynapi_call( + if (afb_dynapi_add_verb( handle_, - config_.hals()[0].c_str(), // BUG: What to do if multiple hals ? - stream.c_str(), - a, - [](void* closure, int status, json_object* result, afb_dynapi* handle) - { - AFB_DYNAPI_DEBUG(handle, "Got the following answer: %s", json_object_to_json_string(result)); - afb_request* r = (afb_request*)closure; - - json_object_get(result); - if (status) afb_request_fail(r, json_object_to_json_string(result), nullptr); - else afb_request_success(r, result, nullptr); - afb_request_unref(r); - }, - afb_request_addref(req)); -} - -void ahl_binding_t::open(afb_request* req, std::string role, std::string stream, json_object* arg) -{ - for(const auto& r : config_.roles()) + r->uid().c_str(), + r->description().c_str(), + ahl_api_role, + r, + nullptr, + AFB_SESSION_NONE_V2)) { - if (r.name() == role) - { - if ( - ext::cfind_if(opened_roles_, - [&role](const role_t& r){ return r.name() == role;}) != opened_roles_.end() - ) - { - afb_request_fail(req, "This role is already opened!", nullptr); - return; - } - // Execute policy for current asked role - policy_open(req, r); - return; - } + AFB_DYNAPI_ERROR(handle_, "Failed to add '%s' verb to the API.", + r->uid().c_str()); + return -1; } - afb_request_fail(req, "Can't open the specified role!", nullptr); + + return 0; } -void ahl_binding_t::policy_open(afb_request* req, const role_t& role) +void ahl_binding_t::get_roles(afb_request* req) { - if(role.interrupts().size()) - { - const interrupt_t& i = role.interrupts()[0]; - /*if (i.type() == "mute") - { - } - else if (i.type() == "continue") - { - } - else if (i.type() == "cancel") - { - } - else */if (i.type() == "ramp") - { - for(const auto& r: opened_roles_) - { - if (role.priority() > r.priority()) - { - // { "ramp" : { "uid" : "ramp-slow", "volume" : 30 } } - json_object* arg = json_object_new_object(); - json_object_object_add(arg, "ramp", i.args()); - json_object_get(i.args()); - json_object* result = nullptr; - - AFB_DYNAPI_NOTICE(handle_, "Call 'smixer'/'%s' '%s", r.stream().c_str(), json_object_to_json_string(arg)); - - if(afb_dynapi_call_sync(handle_, "smixer", r.stream().c_str(), arg, &result)) - { - afb_request_fail(req, "Failed to call 'ramp' action on stream", nullptr); - return; - } - AFB_DYNAPI_NOTICE(handle_, "POLICY: Applying a ramp to '%s' stream because '%s' is opened and have higher priority!", r.stream().c_str(), role.stream().c_str()); - } - } - } - else - { - afb_request_fail(req, "Unkown interrupt uid!", nullptr); - return; - } - } - - json_object* result = json_object_new_object(); - json_object_object_add(result, "device_uri", json_object_new_string(role.device_uri().c_str())); + json_object* result = json_object_new_array(); + for(const auto& r : roles_) + json_object_array_add(result, json_object_new_string(r.uid().c_str())); afb_request_success(req, result, nullptr); - opened_roles_.push_back(role); } -void ahl_binding_t::close(afb_request* req, std::string role, std::string stream, json_object* arg) +const std::vector ahl_binding_t::roles() const { - AFB_DYNAPI_DEBUG(handle_, "Got this arg: %s", json_object_to_json_string(arg)); - auto it = ext::cfind_if(opened_roles_, [&role](const role_t& r) { return r.name() == role; }); - if (it == opened_roles_.cend()) - { - afb_request_fail(req, "This role is already closed!", nullptr); - return; - } - opened_roles_.erase(it); - afb_request_success(req, nullptr, "Role closed!"); + return roles_; } -void ahl_binding_t::interrupt(afb_request* req, std::string role, std::string stream, json_object* arg) +afb_dynapi* ahl_binding_t::handle() const { - afb_request_fail(req, "Not implemented yet!", nullptr); + return handle_; } diff --git a/ahl-binding/ahl-binding.hpp b/ahl-binding/ahl-binding.hpp index 62a518e..66fce84 100644 --- a/ahl-binding/ahl-binding.hpp +++ b/ahl-binding/ahl-binding.hpp @@ -21,11 +21,12 @@ #include #include #include +#include #include #include #include "config_entry.hpp" -#include "ahl-4a.hpp" +#include "role.hpp" #define HL_API_NAME "ahl-4a" #define HL_API_INFO "Audio high level API for AGL applications" @@ -37,55 +38,27 @@ extern "C" { #include #include #include - - int afbBindingVdyn(afb_dynapi* handle); - int ahl_api_create(void*, struct afb_dynapi*); }; -namespace ext -{ - template - typename T::iterator find(T& container, const typename T::value_type& value) - { - return std::find(std::begin(container), std::end(container), value); - } - - template - typename T::iterator find_if(T& container, UnaryPredicate pred) - { - return std::find_if(std::begin(container), std::end(container), pred); - } - - template - typename T::iterator cfind_if(T& container, UnaryPredicate pred) - { - return std::find_if(std::begin(container), std::end(container), pred); - } -} - class ahl_binding_t { using role_action = std::function; private: afb_dynapi* handle_; - - ahl_4a_t config_; - std::map actions_; - std::vector opened_roles_; + std::vector roles_; explicit ahl_binding_t(); void load_static_verbs(); - void load_controller_api(); + + + void load_controller_configs(); int load_controller_config(const std::string& path); - int load_config(CtlSectionT* section, json_object* o); + int update_streams(); + void update_stream(std::string hal, std::string stream, std::string deviceuri); + int create_api_verb(role_t* r); - void volume(afb_request* req, std::string role, std::string stream, json_object* arg); - void open(afb_request* req, std::string role, std::string stream, json_object* arg); - void close(afb_request* req, std::string role, std::string stream, json_object* arg); - void interrupt(afb_request* req, std::string role, std::string stream, json_object* arg); - void policy_open(afb_request* req, const role_t& role); public: @@ -93,8 +66,13 @@ public: int preinit(afb_dynapi* handle); int init(); void event(std::string name, json_object* arg); - void audiorole(afb_request* req); void get_roles(afb_request* req); + + const std::vector roles() const; + afb_dynapi* handle() const; + + void audiorole(afb_request* req); + int parse_roles_config(json_object* o); }; diff --git a/ahl-binding/role.cpp b/ahl-binding/role.cpp index 9338436..b6a9c1f 100644 --- a/ahl-binding/role.cpp +++ b/ahl-binding/role.cpp @@ -17,30 +17,21 @@ #include "role.hpp" #include "jsonc_utils.hpp" - -role_t::role_t(std::string uid, std::string name, std::string description, std::string stream, int priority) - : uid_{uid} - , name_{name} - , description_{description} - , stream_{stream} - , priority_{priority} -{ -} +#include "ahl-binding.hpp" role_t::role_t(json_object* j) { jcast(uid_, j, "uid"); - jcast(name_, j, "name"); jcast(description_, j, "description"); jcast(priority_, j, "priority"); jcast(stream_, j, "stream"); jcast_array(interrupts_, j, "interrupts"); + opened_ = false; } role_t& role_t::operator<<(json_object* j) { jcast(uid_, j, "uid"); - jcast(name_, j, "name"); jcast(description_, j, "description"); jcast(priority_, j, "priority"); jcast(stream_, j, "stream"); @@ -53,14 +44,14 @@ std::string role_t::uid() const return uid_; } -std::string role_t::name() const +std::string role_t::description() const { - return name_; + return description_; } -std::string role_t::description() const +std::string role_t::hal() const { - return description_; + return hal_; } std::string role_t::stream() const @@ -78,14 +69,14 @@ std::string role_t::device_uri() const return device_uri_; } -void role_t::uid(std::string v) +bool role_t::opened() const { - uid_ = v; + return opened_; } -void role_t::name(std::string v) +void role_t::uid(std::string v) { - name_ = v; + uid_ = v; } void role_t::description(std::string v) @@ -93,6 +84,11 @@ void role_t::description(std::string v) description_ = v; } +void role_t::hal(std::string v) +{ + hal_ = v; +} + void role_t::stream(std::string v) { stream_ = v; @@ -112,3 +108,169 @@ const std::vector& role_t::interrupts() const { return interrupts_; } + +int role_t::apply_policy(afb_request* req) +{ + if(interrupts_.size()) + { + const interrupt_t& i = interrupts_[0]; + /*if (i.type() == "mute") + { + } + else if (i.type() == "continue") + { + } + else if (i.type() == "cancel") + { + } + else */if (i.type() == "ramp") + { + for(const auto& r: ahl_binding_t::instance().roles()) + { + if (r.opened() && priority_ > r.priority()) + { + // { "ramp" : { "uid" : "ramp-slow", "volume" : 30 } } + json_object* arg = json_object_new_object(); + json_object_object_add(arg, "ramp", i.args()); + json_object_get(i.args()); + json_object* result = nullptr; + + AFB_DYNAPI_NOTICE(ahl_binding_t::instance().handle(), + "Call '%s'/'%s' '%s", + r.hal().c_str(), r.stream().c_str(), json_object_to_json_string(arg)); + + if(afb_dynapi_call_sync(ahl_binding_t::instance().handle(), r.hal().c_str(), r.stream().c_str(), arg, &result)) + { + afb_request_fail(req, "Failed to call 'ramp' action on stream", nullptr); + return -1; + } + AFB_DYNAPI_NOTICE(ahl_binding_t::instance().handle(), + "POLICY: Applying a ramp to '%s' stream because '%s' is opened and have higher priority!", + r.stream().c_str(), stream_.c_str()); + } + } + } + else + { + afb_request_fail(req, "Unkown interrupt uid!", nullptr); + return -1; + } + } + return 0; +} + +void role_t::invoke(afb_request* req) +{ + json_object* arg = afb_request_json(req); + if (arg == nullptr) + { + afb_request_fail(req, "No valid argument!", nullptr); + return; + } + + json_object* jaction = json_object_object_get(arg, "action"); + if (jaction == nullptr) + { + afb_request_fail(req, "No valid action!", nullptr); + return; + } + + std::string action = json_object_get_string(jaction); + if (action.size() == 0) + { + afb_request_fail(req, "No valid action!", nullptr); + return; + } + + if (action == "open") open(req, arg); + else if (action == "close") close(req, arg); + else if (action == "volume") volume(req, arg); + else if (action == "interrupt") interrupt(req, arg); + else afb_request_fail(req, "Unknown action!", nullptr); +} + +void role_t::open(afb_request* r, json_object* o) +{ + if (opened_) + { + afb_request_fail(r, "Already opened!", nullptr); + return; + } + + if (!apply_policy(r)) + { + afb_request_context_set(r, this, nullptr); + opened_ = true; + + json_object* result = json_object_new_object(); + json_object_object_add(result, "device_uri", json_object_new_string(device_uri_.c_str())); + + afb_request_success(r, result, nullptr); + } +} + +void role_t::close(afb_request* r, json_object* o) +{ + if (!opened_) + { + afb_request_success(r, nullptr, "Already closed!"); + return; + } + + if(!afb_request_context_get(r)) + { + afb_request_fail(r, "Stream is opened by another client!", nullptr); + return; + } + + opened_ = false; + afb_request_success(r, nullptr, "Stream closed!"); +} + +void role_t::volume(afb_request* r, json_object* o) +{ + if (!opened_) + { + afb_request_fail(r, "You have to open the stream first!", nullptr); + return; + } + + if(!afb_request_context_get(r)) + { + afb_request_fail(r, "Stream is opened by another client!", nullptr); + return; + } + + json_object* value = json_object_object_get(o, "value"); + if(!value) + { + afb_request_fail(r, "No value given!", nullptr); + return; + } + + json_object_get(value); + + json_object* a = json_object_new_object(); + json_object_object_add(a, "volume", value); + + afb_dynapi_call( + r->dynapi, + hal_.c_str(), + stream_.c_str(), + a, + [](void* closure, int status, json_object* result, afb_dynapi* handle) + { + AFB_DYNAPI_DEBUG(handle, "Got the following answer: %s", json_object_to_json_string(result)); + afb_request* r = (afb_request*)closure; + + json_object_get(result); + if (status) afb_request_fail(r, json_object_to_json_string(result), nullptr); + else afb_request_success(r, result, nullptr); + afb_request_unref(r); + }, + afb_request_addref(r)); +} + +void role_t::interrupt(afb_request* r, json_object* o) +{ +} diff --git a/ahl-binding/role.hpp b/ahl-binding/role.hpp index fa1bcd4..1469b68 100644 --- a/ahl-binding/role.hpp +++ b/ahl-binding/role.hpp @@ -20,17 +20,24 @@ #include #include "interrupt.hpp" +struct afb_request; + class role_t { private: + // Members filled by config std::string uid_; - std::string name_; std::string description_; + std::string hal_; std::string stream_; - std::string device_uri_; int priority_; std::vector interrupts_; + std::string device_uri_; + bool opened_; + + int apply_policy(afb_request* req); + public: explicit role_t() = default; explicit role_t(const role_t&) = default; @@ -41,23 +48,32 @@ public: role_t& operator=(const role_t&) = default; role_t& operator=(role_t&&) = default; - explicit role_t(std::string uid, std::string name, std::string description, std::string stream, int priority); + static role_t from_json(json_object* o); + explicit role_t(json_object* j); role_t& operator<<(json_object* j); std::string uid() const; - std::string name() const; std::string description() const; + std::string hal() const; std::string stream() const; int priority() const; - std::string device_uri() const; const std::vector& interrupts() const; + std::string device_uri() const; + bool opened() const; void uid(std::string v); - void name(std::string v); void description(std::string v); + void hal(std::string v); void stream(std::string v); - void priority(int v); void device_uri(std::string v); + void priority(int v); + + void invoke(afb_request* r); + + void open(afb_request* r, json_object* o); + void close(afb_request* r, json_object* o); + void volume(afb_request* r, json_object* o); + void interrupt(afb_request* r, json_object* o); }; diff --git a/conf.d/project/policy-4a-sample1.json b/conf.d/project/policy-4a-sample1.json index d71bbe9..8aa7a03 100644 --- a/conf.d/project/policy-4a-sample1.json +++ b/conf.d/project/policy-4a-sample1.json @@ -16,15 +16,13 @@ "hals": ["4a-hal-ctxnop-laptop"], "roles": [ { - "uid": "role-multimedia", - "name": "multimedia", + "uid": "multimedia", "description": "Multimedia content (e.g. tuner, media player, etc.)", "priority": 0, "stream": "multimedia" }, { - "uid": "role-emergency", - "name": "emergency", + "uid": "emergency", "description": "Safety-relevant or critical alerts/alarms", "priority": 100, "stream": "emergency", @@ -33,7 +31,35 @@ ] }, { - "uid": "role-navigation", + "uid": "navigation", + "name": "navigation", + "description": "Navigation instructions (GPS, turn directions, etc...)", + "priority": 25, + "stream": "navigation", + "interrupts":[ + {"type": "ramp", "args": { "uid": "ramp-slow", "volume": 30} } + ] + } + ] + }, + "roles":[ + { + "uid": "multimedia", + "description": "Multimedia content (e.g. tuner, media player, etc.)", + "priority": 0, + "stream": "multimedia" + }, + { + "uid": "emergency", + "description": "Safety-relevant or critical alerts/alarms", + "priority": 100, + "stream": "emergency", + "interrupts":[ + {"type": "ramp", "args": { "uid": "ramp-slow", "volume": 30} } + ] + }, + { + "uid": "navigation", "name": "navigation", "description": "Navigation instructions (GPS, turn directions, etc...)", "priority": 25, @@ -43,5 +69,4 @@ ] } ] - } } -- cgit 1.2.3-korg