From 322f8932476eda944c7d3ac65eafde12c69b2ae9 Mon Sep 17 00:00:00 2001 From: Loïc Collignon Date: Tue, 5 Jun 2018 10:29:47 +0200 Subject: Rewrite of the High Level API using the new HAL model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new HAL model need the High Level API to be rewritten. This is the first version of this rewrite, still in progress but should work. Change-Id: I5c94cf39d84cefae6b7a179c09d95e645673e8d4 Signed-off-by: Loïc Collignon --- ahl-binding/ahl-binding.cpp | 344 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 ahl-binding/ahl-binding.cpp (limited to 'ahl-binding/ahl-binding.cpp') diff --git a/ahl-binding/ahl-binding.cpp b/ahl-binding/ahl-binding.cpp new file mode 100644 index 0000000..6493267 --- /dev/null +++ b/ahl-binding/ahl-binding.cpp @@ -0,0 +1,344 @@ +/* + * 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 "ahl-binding.hpp" + +afb_dynapi* AFB_default; // BUG: Is it possible to get rid of this ? + +ahl_binding_t::ahl_binding_t() + : handle_{nullptr} + , apihandle_{nullptr} +{ +} + +ahl_binding_t& ahl_binding_t::instance() +{ + static ahl_binding_t s; + return s; +} + +int ahl_binding_t::build(afb_dynapi* handle) +{ + using namespace std::placeholders; + + if (!handle || handle_) return -1; + handle_ = handle; + AFB_default = handle; + + return afb_dynapi_new_api( + handle, + HL_API_NAME, + HL_API_INFO, + 1, + [](void*, afb_dynapi* h) { + return ahl_binding_t::instance().preinit(h); + }, + nullptr); +} + +int ahl_binding_t::preinit(afb_dynapi* handle) +{ + apihandle_ = handle; + + try + { + load_static_verbs(); + load_controller_api(); + + 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_init(handle, + [](afb_dynapi*) { return ahl_binding_t::instance().init(); } + )) throw std::runtime_error("Failed to register init handler callback."); + } + catch(std::exception& e) + { + AFB_DYNAPI_ERROR(handle, "%s", e.what()); + return -1; + } + + return 0; +} + +int ahl_binding_t::init() +{ + using namespace std::placeholders; + + if (afb_dynapi_require_api(apihandle_, HAL_MGR_API, 1)) + { + AFB_DYNAPI_ERROR(apihandle_, "Failed to require '%s' API!", HAL_MGR_API); + return -1; + } + AFB_DYNAPI_NOTICE(apihandle_, "Required '%s' API found!", HAL_MGR_API); + + // Requires corresponding API + for(const auto& h : config_.hals()) + { + if (afb_dynapi_require_api(apihandle_, h.c_str(), 1)) + { + AFB_DYNAPI_ERROR(apihandle_, "Failed to require '%s' API!", h.c_str()); + return -1; + } + AFB_DYNAPI_NOTICE(apihandle_, "Required '%s' API found!", h.c_str()); + json_object* result = nullptr; + if(afb_dynapi_call_sync(apihandle_, h.c_str(), "init-mixer", nullptr, &result)) + { + AFB_DYNAPI_ERROR(apihandle_, "Failed to call 'init-mixer' verb on '%s' API!", h.c_str()); + if (result) AFB_DYNAPI_NOTICE(apihandle_, "%s", json_object_to_json_string(result)); + return -1; + } + AFB_DYNAPI_NOTICE(apihandle_, "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(apihandle_, h.c_str(), "list", verbose, &response)) + { + AFB_DYNAPI_ERROR(apihandle_, "Failed to call 'list' verb on '%s' API!", h.c_str()); + if (result) AFB_DYNAPI_NOTICE(apihandle_, "%s", json_object_to_json_string(result)); + return -1; + } + json_object* streams = json_object_object_get(response, "response"); + AFB_DYNAPI_DEBUG(apihandle_, "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) + { + std::string stream_name; + std::string device_uri; + json_object* item = json_object_array_get_idx(array, i); + + jcast(stream_name, item, "name"); + jcast(device_uri, item, "cardId"); + + config_.set_device_uri(stream_name, device_uri); + } + } + + afb_dynapi_seal(apihandle_); + AFB_DYNAPI_NOTICE(apihandle_, "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); + + return 0; +} + +void ahl_binding_t::event(std::string name, json_object* arg) +{ + AFB_DYNAPI_DEBUG(apihandle_, "Event '%s' received with the following arg: %s", name.c_str(), json_object_to_json_string(arg)); +} + +void ahl_binding_t::load_static_verbs() +{ + if (afb_dynapi_add_verb( + apihandle_, + "get_roles", + "Retrieve array of available audio roles", + [](afb_request* r) { ahl_binding_t::instance().get_roles(r); }, + nullptr, + nullptr, + AFB_SESSION_NONE_V2)) + { + throw std::runtime_error("Failed to add 'get_role' verb to the API."); + } +} + +void ahl_binding_t::load_controller_api() +{ + char* dir_list = getenv("CONTROL_CONFIG_PATH"); + if (!dir_list) dir_list = strdup(CONTROL_CONFIG_PATH); + struct json_object* config_files = CtlConfigScan(dir_list, "policy"); + if (!config_files) throw std::runtime_error("No config files found!"); + + // Only one file should be found this way, but read all just in case + size_t config_files_count = json_object_array_length(config_files); + for(size_t i = 0; i < config_files_count; ++i) + { + config_entry_t file {json_object_array_get_idx(config_files, i)}; + + if(load_controller_config(file.filepath()) < 0) + { + std::stringstream ss; + ss << "Failed to load config file '" + << file.filename() + << "' from '" + << file.fullpath() + << "'!"; + throw std::runtime_error(ss.str()); + } + } +} + +int ahl_binding_t::load_controller_config(const std::string& path) +{ + CtlConfigT* controller_config; + + controller_config = CtlLoadMetaData(apihandle_, path.c_str()); + if (!controller_config) + { + AFB_DYNAPI_ERROR(apihandle_, "Failed to load controller from config file!"); + return -1; + } + + static CtlSectionT controller_sections[] = + { + {.key = "plugins", .uid = nullptr, .info = nullptr, .loadCB = PluginConfig, .handle = nullptr, .actions = nullptr}, + {.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} + }; + + CtlLoadSections(apihandle_, controller_config, controller_sections); + + return 0; +} + +int ahl_binding_t::load_config(CtlSectionT* section, json_object* o) +{ + config_ << o; + + // Add corresponding verbs + for(const auto& r : config_.roles()) + { + AFB_DYNAPI_NOTICE(apihandle_, "New audio role: %s", r.name().c_str()); + + if (afb_dynapi_add_verb( + apihandle_, + 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()); + } + } + + return 0; +} + +int afbBindingVdyn(afb_dynapi* handle) +{ + if (!handle) return -1; + return ahl_binding_t::instance().build(handle); +} + +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); +} + +void ahl_binding_t::get_roles(afb_request* req) +{ + 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); +} + +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(apihandle_, "Call the HAL with the following argument: %s", json_object_to_json_string(a)); + + afb_dynapi_call( + apihandle_, + 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()) + { + if (r.name() == role) + { + AFB_DYNAPI_DEBUG(apihandle_, "HELLO"); + + json_object* result = json_object_new_object(); + json_object_object_add(result, "device_uri", json_object_new_string(r.device_uri().c_str())); + + afb_request_success(req, result, nullptr); + return; + } + } + afb_request_fail(req, "Can't open the specified role!", nullptr); +} -- cgit 1.2.3-korg