/* * 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 "role.hpp" #include "jsonc_utils.hpp" #include "ahl-binding.hpp" using session_t = std::vector; inline restype_t fromstring(const std::string& str) { if (str == "" || str == "stream") return restype_t::stream; if (str == "playback") return restype_t::playback; if (str == "control") return restype_t::control; std::stringstream ss; ss << "Unknown resource type: " << str; throw std::runtime_error(ss.str()); } role_t::role_t(json_object* j) { rtype_ = restype_t::stream; jcast(uid_, j, "uid"); jcast(description_, j, "description"); jcast(priority_, j, "priority"); jcast(resource_, j, "resource"); jcast_array(interrupts_, j, "interrupts"); std::string type; jcast(type, j, "type"); rtype_ = fromstring(type); // device_uri_ = (rtype_ == restype_t::stream) ? "" : resource_; if (rtype_ == restype_t::control) { device_uri_ = resource_; hal_ = "4a-hal-csl-cm106-8ch-usb"; } opened_ = false; } role_t& role_t::operator<<(json_object* j) { rtype_ = restype_t::stream; jcast(uid_, j, "uid"); jcast(description_, j, "description"); jcast(priority_, j, "priority"); jcast(resource_, j, "resource"); jcast_array(interrupts_, j, "interrupts"); std::string type; jcast(type, j, "type"); rtype_ = fromstring(type); // device_uri_ = (rtype_ == restype_t::stream) ? "" : resource_; return *this; } std::string role_t::uid() const { return uid_; } std::string role_t::description() const { return description_; } std::string role_t::hal() const { return hal_; } std::string role_t::resource() const { return resource_; } restype_t role_t::rtype() const { return rtype_; } int role_t::priority() const { return priority_; } const std::vector& role_t::interrupts() const { return interrupts_; } std::string role_t::device_uri() const { return device_uri_; } bool role_t::opened() const { return opened_; } void role_t::uid(std::string v) { uid_ = v; } void role_t::description(std::string v) { description_ = v; } void role_t::hal(std::string v) { hal_ = v; } void role_t::resource(std::string v) { resource_ = v; } void role_t::rtype(restype_t v) { rtype_ = v; } void role_t::priority(int v) { priority_ = v; } void role_t::device_uri(std::string v) { device_uri_ = v; } int role_t::apply_policy(afb_req_t req) { return interrupts_.size() ? interrupts_[0].apply(req, *this) : 0; } void role_t::invoke(afb_req_t req) { json_object* arg = afb_req_json(req); if (arg == nullptr) { afb_req_fail(req, "No valid argument!", nullptr); return; } json_object* jaction; json_bool ret = json_object_object_get_ex(arg, "action", &jaction); if (!ret) { afb_req_fail(req, "No valid action!", nullptr); return; } std::string action = json_object_get_string(jaction); if (action.size() == 0) { afb_req_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 if (action == "mute") mute(req, arg); else if (action == "unmute") unmute(req, arg); else afb_req_fail(req, "Unknown action!", nullptr); } void role_t::open(afb_req_t r, json_object* o) { if (opened_) { afb_req_fail(r, "Already opened!", nullptr); return; } if (rtype_ != restype_t::stream) { afb_req_fail(r, "Only stream resources can be opened!", nullptr); } else if (!apply_policy(r)) { afb_req_context(r, 0, // Do not replace previous context if any [](void* arg) -> void* { return new session_t(); }, [](void* arg) { afb_api_t api = ahl_binding_t::instance().handle(); session_t* opened_roles = reinterpret_cast(arg); for(role_t* role : *opened_roles) { AFB_API_DEBUG(api, "Released role %s\n", role->uid_.c_str()); role->opened_ = false; if(role->interrupts_.size()) role->interrupts_[0].clear(); // send a mute command to the HAL. We cannot reuse the do_mute function, // because in the context here, the afb_request is no longer valid. json_object* a = json_object_new_object(); json_object_object_add(a, "mute", json_object_new_boolean(true)); afb_api_call( api, role->hal_.c_str(), role->resource_.c_str(), a, NULL, NULL); } delete opened_roles; }, nullptr ); opened_ = true; // Add the current role to the session session_t* context = reinterpret_cast(afb_req_context_get(r)); if(context) context->push_back(this); json_object* result = json_object_new_object(); json_object_object_add(result, "device_uri", json_object_new_string(device_uri_.c_str())); afb_req_success(r, result, nullptr); } } void role_t::close(afb_req_t r, json_object* o) { if (!opened_) { afb_req_success(r, nullptr, "Already closed!"); return; } session_t* context = reinterpret_cast(afb_req_context_get(r)); if(!context) { afb_req_fail(r, "Stream is opened by another client!", nullptr); return; } // Remove the current role from the session. std::string uid = uid_; std::remove_if(context->begin(), context->end(), [uid](role_t* r) { return r->uid_ == uid; }); context->push_back(this); opened_ = false; if(interrupts_.size()) interrupts_[0].clear(); afb_req_success(r, nullptr, "Stream closed!"); } void role_t::mute(afb_req_t r, json_object* o) { if (rtype_ != restype_t::stream) afb_req_fail(r, "Only stream resource can be muted!", nullptr); else do_mute(r, true); } void role_t::unmute(afb_req_t r, json_object *o) { if (rtype_ != restype_t::stream) afb_req_fail(r, "Only stream resource can be unmuted!", nullptr); else do_mute(r, false); } void role_t::do_mute(afb_req_t r, bool v) { json_object* a = json_object_new_object(); json_object_object_add(a, "mute", json_object_new_boolean(v)); afb_api_t api = ahl_binding_t::instance().handle(); afb_api_call( api, hal_.c_str(), resource_.c_str(), a, [](void* closure, json_object* result, const char* error, const char* info, afb_api_t handle) { AFB_API_DEBUG(handle, "Got the following answer: %s", json_object_to_json_string(result)); afb_req_t r = (afb_req_t)closure; json_object_get(result); if (error) afb_req_fail(r, json_object_to_json_string(result), nullptr); else afb_req_success(r, result, nullptr); afb_req_unref(r); }, afb_req_addref(r)); } struct volumeclosure { role_t* role; afb_req_t req; }; void role_t::volume(afb_req_t r, json_object* o) { afb_api_t api = ahl_binding_t::instance().handle(); if(!afb_req_has_permission(r, "urn:AGL:permission::public:4a-audio-mixer")) { if (!opened_) { afb_req_fail(r, "You have to open the stream first!", nullptr); return; } if(!afb_req_context_get(r)) { afb_req_fail(r, "Stream is opened by another client!", nullptr); return; } } else { AFB_API_DEBUG(api, "Granted special audio-mixer permission to change volume"); } json_object* value; json_bool ret = json_object_object_get_ex(o, "value", &value); if (!ret) { afb_req_fail(r, "No value given!", nullptr); return; } json_object_get(value); json_object* a = json_object_new_object(); json_object_object_add(a, (rtype_ == restype_t::control ? "value" : "volume"), value); AFB_API_DEBUG(api, "VOLUME QUERY: %s", json_object_to_json_string(a)); volumeclosure* userdata = new volumeclosure(); userdata->role = this; userdata->req = afb_req_addref(r); afb_api_call( api, hal_.c_str(), resource_.c_str(), a, [](void* closure, json_object* result, const char* error, const char* info, afb_api_t handle) { AFB_API_DEBUG(handle, "Got the following answer: %s", json_object_to_json_string(result)); volumeclosure* r = reinterpret_cast(closure); if (error) { json_object_get(result); afb_req_fail(r->req, json_object_to_json_string(result), nullptr); } else { if (r->role->rtype() == restype_t::control) { int average = 0; size_t arraylen = json_object_array_length(result); for (size_t i = 0; i < arraylen; ++i) { average += json_object_get_int(json_object_array_get_idx(result, i)); } average = average / (int)arraylen; json_object* response = json_object_new_object(); json_object* newvol = json_object_new_int(average); json_object_object_add(response, "newvol", newvol); ahl_binding_t::instance().emit_volume_changed(r->role->uid(), average); afb_req_success(r->req, response, nullptr); } else { json_object* volnew; if (json_object_object_get_ex(result, "volnew", &volnew)) { ahl_binding_t::instance().emit_volume_changed(r->role->uid(), json_object_get_int(volnew)); } json_object_get(result); afb_req_success(r->req, result, nullptr); } } afb_req_unref(r->req); delete r; }, userdata ); } void role_t::interrupt(afb_req_t r, json_object* o) { }