summaryrefslogtreecommitdiffstats
path: root/ahl-binding/ahl-binding.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ahl-binding/ahl-binding.cpp')
-rw-r--r--ahl-binding/ahl-binding.cpp389
1 files changed, 389 insertions, 0 deletions
diff --git a/ahl-binding/ahl-binding.cpp b/ahl-binding/ahl-binding.cpp
new file mode 100644
index 0000000..37e6847
--- /dev/null
+++ b/ahl-binding/ahl-binding.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Loïc Collignon <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include "ahl-binding.hpp"
+
+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.
+ * @return Status code, zero if success.
+ */
+int afbBindingVdyn(afb_dynapi* handle)
+{
+ using namespace std::placeholders;
+ assert(handle != nullptr);
+
+ AFB_default = handle;
+
+ return afb_dynapi_new_api(
+ handle,
+ HL_API_NAME,
+ HL_API_INFO,
+ 1,
+ ahl_api_create,
+ nullptr
+ );
+}
+
+/**
+ * @brief Callback invoked when API enter the init phase.
+ * @return Status code, zero if success.
+ */
+int ahl_api_init(afb_dynapi*)
+{
+ 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;
+
+ try
+ {
+ load_static_verbs();
+ load_controller_configs();
+
+ 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_, ahl_api_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;
+}
+
+/**
+ * @brief Initialize the API.
+ */
+int ahl_binding_t::init()
+{
+ using namespace std::placeholders;
+
+ if (afb_dynapi_require_api(handle_, HAL_MGR_API, 1))
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to require '%s' API!", HAL_MGR_API);
+ return -1;
+ }
+ AFB_DYNAPI_NOTICE(handle_, "Required '%s' API found!", HAL_MGR_API);
+
+ if (afb_dynapi_require_api(handle_, "smixer", 1))
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to require 'smixer' API!");
+ return -1;
+ }
+ AFB_DYNAPI_NOTICE(handle_, "Required 'smixer' API found!");
+
+ 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)
+ {
+ 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 call 'info' verb on '%s' API!", halname);
+ if (info) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(info));
+ return -1;
+ }
+
+ 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)
+ {
+ 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;
+}
+
+/**
+ * @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)
+ {
+ 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);
+ }
+ }
+ }
+}
+
+void ahl_binding_t::event(std::string name, json_object* arg)
+{
+ AFB_DYNAPI_DEBUG(handle_, "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(
+ handle_,
+ "get_roles",
+ "Retrieve array of available audio roles",
+ ahl_api_get_roles,
+ 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_configs()
+{
+ 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(handle_, path.c_str());
+ if (!controller_config)
+ {
+ AFB_DYNAPI_ERROR(handle_, "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 = "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);
+
+ return 0;
+}
+
+int ahl_binding_t::parse_roles_config(json_object* o)
+{
+ assert(o != nullptr);
+ assert(json_object_is_type(o, json_type_array));
+
+ if (roles_.size()) return 0; // Roles already added, ignore.
+
+ size_t count = json_object_array_length(o);
+ roles_.reserve(count);
+ for(size_t i = 0; i < count; ++i)
+ {
+ json_object* jr = json_object_array_get_idx(o, i);
+ assert(jr != nullptr);
+
+ roles_.push_back(role_t(jr));
+ role_t& r = roles_[roles_.size() - 1];
+ if(create_api_verb(&r))
+ return -1;
+ }
+
+ return 0;
+}
+
+int ahl_binding_t::create_api_verb(role_t* r)
+{
+ AFB_DYNAPI_NOTICE(handle_, "New audio role: %s", r->uid().c_str());
+
+ if (afb_dynapi_add_verb(
+ handle_,
+ r->uid().c_str(),
+ r->description().c_str(),
+ ahl_api_role,
+ r,
+ nullptr,
+ AFB_SESSION_NONE_V2))
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to add '%s' verb to the API.",
+ r->uid().c_str());
+ return -1;
+ }
+
+ return 0;
+}
+
+void ahl_binding_t::get_roles(afb_request* req)
+{
+ 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);
+}
+
+const std::vector<role_t> ahl_binding_t::roles() const
+{
+ return roles_;
+}
+
+afb_dynapi* ahl_binding_t::handle() const
+{
+ return handle_;
+}