/* * 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); }