aboutsummaryrefslogtreecommitdiffstats
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.cpp428
1 files changed, 185 insertions, 243 deletions
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
@@ -21,6 +21,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.
* @return Status code, zero if success.
@@ -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<role_t> 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_;
}