diff options
author | Loïc Collignon <loic.collignon@iot.bzh> | 2018-06-07 15:05:53 +0200 |
---|---|---|
committer | Stephane Desneux <stephane.desneux@iot.bzh> | 2018-06-12 16:23:44 +0200 |
commit | 9a631c30c9c8792865ce2aa0ec06a1bb5fd16751 (patch) | |
tree | e1bd8c4ce3cc408144aedcf0f26fd873d8db8634 | |
parent | 322f8932476eda944c7d3ac65eafde12c69b2ae9 (diff) |
Add some policy emulation
Add a very simplistic policy emulation just for demo.
The real policy engine will be brought back soon.
Change-Id: I6f77c8dc58ba335eabd1a1d858354a84559d9e7f
Signed-off-by: Loïc Collignon <loic.collignon@iot.bzh>
-rw-r--r-- | afb-cpp/cjson-c.hpp | 6 | ||||
-rw-r--r-- | ahl-binding/CMakeLists.txt | 6 | ||||
-rw-r--r-- | ahl-binding/ahl-binding.cpp | 235 | ||||
-rw-r--r-- | ahl-binding/ahl-binding.hpp | 34 | ||||
-rw-r--r-- | ahl-binding/interrupt.cpp | 34 | ||||
-rw-r--r-- | ahl-binding/interrupt.hpp | 28 | ||||
-rw-r--r-- | ahl-binding/jsonc_utils.hpp | 1 | ||||
-rw-r--r-- | ahl-binding/role.cpp | 7 | ||||
-rw-r--r-- | ahl-binding/role.hpp | 5 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 10 | ||||
-rw-r--r-- | conf.d/project/policy-4a-sample1.json | 10 |
11 files changed, 298 insertions, 78 deletions
diff --git a/afb-cpp/cjson-c.hpp b/afb-cpp/cjson-c.hpp new file mode 100644 index 0000000..5266ade --- /dev/null +++ b/afb-cpp/cjson-c.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include <json-c/json.h> + + + diff --git a/ahl-binding/CMakeLists.txt b/ahl-binding/CMakeLists.txt index b36cb1e..0e5df43 100644 --- a/ahl-binding/CMakeLists.txt +++ b/ahl-binding/CMakeLists.txt @@ -24,6 +24,7 @@ PROJECT_TARGET_ADD(audiohighlevel) config_entry.cpp role.cpp ahl-4a.cpp + interrupt.cpp ahl-binding.cpp ) @@ -47,9 +48,8 @@ PROJECT_TARGET_ADD(audiohighlevel) # Find package for GLIB does not seem to export TARGET_LINK_LIBRARIES(${TARGET_NAME} #ahl-policy - #ahl-utilities - afb-utilities - #afb-helpers + + afb-helpers ctl-utilities ${GLIB_PKG_LIBRARIES} ${link_libraries} diff --git a/ahl-binding/ahl-binding.cpp b/ahl-binding/ahl-binding.cpp index 6493267..fa4c74b 100644 --- a/ahl-binding/ahl-binding.cpp +++ b/ahl-binding/ahl-binding.cpp @@ -20,24 +20,16 @@ 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) +/** + * @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); - if (!handle || handle_) return -1; - handle_ = handle; AFB_default = handle; return afb_dynapi_new_api( @@ -45,28 +37,48 @@ int ahl_binding_t::build(afb_dynapi* handle) HL_API_NAME, HL_API_INFO, 1, - [](void*, afb_dynapi* h) { - return ahl_binding_t::instance().preinit(h); - }, - nullptr); + ahl_api_create, + nullptr + ); +} + +/** + * @brief Callback to create the new api. + * @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); +} + +ahl_binding_t::ahl_binding_t() + : handle_{nullptr} +{ +} + +ahl_binding_t& ahl_binding_t::instance() +{ + static ahl_binding_t s; + return s; } int ahl_binding_t::preinit(afb_dynapi* handle) { - apihandle_ = handle; + handle_ = handle; try { load_static_verbs(); load_controller_api(); - if (afb_dynapi_on_event(handle, + 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, + if (afb_dynapi_on_init(handle_, [](afb_dynapi*) { return ahl_binding_t::instance().init(); } )) throw std::runtime_error("Failed to register init handler callback."); } @@ -83,42 +95,63 @@ int ahl_binding_t::init() { using namespace std::placeholders; - if (afb_dynapi_require_api(apihandle_, HAL_MGR_API, 1)) + if (afb_dynapi_require_api(handle_, HAL_MGR_API, 1)) { - AFB_DYNAPI_ERROR(apihandle_, "Failed to require '%s' API!", HAL_MGR_API); + AFB_DYNAPI_ERROR(handle_, "Failed to require '%s' API!", HAL_MGR_API); return -1; } - AFB_DYNAPI_NOTICE(apihandle_, "Required '%s' API found!", HAL_MGR_API); + 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!"); + // Requires corresponding API for(const auto& h : config_.hals()) { - if (afb_dynapi_require_api(apihandle_, h.c_str(), 1)) + if (afb_dynapi_require_api(handle_, h.c_str(), 1)) { - AFB_DYNAPI_ERROR(apihandle_, "Failed to require '%s' API!", h.c_str()); + AFB_DYNAPI_ERROR(handle_, "Failed to require '%s' API!", h.c_str()); return -1; } - AFB_DYNAPI_NOTICE(apihandle_, "Required '%s' API found!", h.c_str()); + AFB_DYNAPI_NOTICE(handle_, "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()); + + // 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* 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)) + 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)) { - 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)); + 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* 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)); + 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) @@ -127,31 +160,33 @@ int ahl_binding_t::init() std::string device_uri; json_object* item = json_object_array_get_idx(array, i); - jcast(stream_name, item, "name"); - jcast(device_uri, item, "cardId"); + jcast(stream_name, item, "uid"); + jcast(device_uri, item, "alsa"); config_.set_device_uri(stream_name, device_uri); } } - afb_dynapi_seal(apihandle_); - AFB_DYNAPI_NOTICE(apihandle_, "API is now sealed!"); + 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) { - AFB_DYNAPI_DEBUG(apihandle_, "Event '%s' received with the following arg: %s", name.c_str(), json_object_to_json_string(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( - apihandle_, + handle_, "get_roles", "Retrieve array of available audio roles", [](afb_request* r) { ahl_binding_t::instance().get_roles(r); }, @@ -193,10 +228,10 @@ int ahl_binding_t::load_controller_config(const std::string& path) { CtlConfigT* controller_config; - controller_config = CtlLoadMetaData(apihandle_, path.c_str()); + controller_config = CtlLoadMetaData(handle_, path.c_str()); if (!controller_config) { - AFB_DYNAPI_ERROR(apihandle_, "Failed to load controller from config file!"); + AFB_DYNAPI_ERROR(handle_, "Failed to load controller from config file!"); return -1; } @@ -219,7 +254,7 @@ int ahl_binding_t::load_controller_config(const std::string& path) {.key = nullptr, .uid = nullptr, .info = nullptr, .loadCB = nullptr, .handle = nullptr, .actions = nullptr} }; - CtlLoadSections(apihandle_, controller_config, controller_sections); + CtlLoadSections(handle_, controller_config, controller_sections); return 0; } @@ -231,10 +266,10 @@ int ahl_binding_t::load_config(CtlSectionT* section, json_object* o) // Add corresponding verbs for(const auto& r : config_.roles()) { - AFB_DYNAPI_NOTICE(apihandle_, "New audio role: %s", r.name().c_str()); + AFB_DYNAPI_NOTICE(handle_, "New audio role: %s", r.name().c_str()); if (afb_dynapi_add_verb( - apihandle_, + handle_, r.name().c_str(), r.description().c_str(), [](afb_request* r) { ahl_binding_t::instance().audiorole(r); }, @@ -251,12 +286,6 @@ int ahl_binding_t::load_config(CtlSectionT* section, json_object* o) 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); @@ -305,10 +334,10 @@ void ahl_binding_t::volume(afb_request* req, std::string role, std::string strea 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_DEBUG(handle_, "Call the HAL with the following argument: %s", json_object_to_json_string(a)); afb_dynapi_call( - apihandle_, + handle_, config_.hals()[0].c_str(), // BUG: What to do if multiple hals ? stream.c_str(), a, @@ -331,14 +360,86 @@ void ahl_binding_t::open(afb_request* req, std::string role, std::string stream, { 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); + 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_request_fail(req, "Can't open the specified role!", nullptr); } + +void ahl_binding_t::policy_open(afb_request* req, const role_t& role) +{ + 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())); + 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) +{ + 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!"); +} + +void ahl_binding_t::interrupt(afb_request* req, std::string role, std::string stream, json_object* arg) +{ + afb_request_fail(req, "Not implemented yet!", nullptr); +} diff --git a/ahl-binding/ahl-binding.hpp b/ahl-binding/ahl-binding.hpp index 34d6492..62a518e 100644 --- a/ahl-binding/ahl-binding.hpp +++ b/ahl-binding/ahl-binding.hpp @@ -22,6 +22,7 @@ #include <sstream> #include <vector> #include <map> +#include <cassert> #include "config_entry.hpp" #include "ahl-4a.hpp" @@ -37,19 +38,41 @@ extern "C" { #include <string.h> #include <ctl-config.h> - int afbBindingVdyn(afb_dynapi* handle); + int afbBindingVdyn(afb_dynapi* handle); + int ahl_api_create(void*, struct afb_dynapi*); }; +namespace ext +{ + template<class T> + typename T::iterator find(T& container, const typename T::value_type& value) + { + return std::find(std::begin(container), std::end(container), value); + } + + template<class T, class UnaryPredicate> + typename T::iterator find_if(T& container, UnaryPredicate pred) + { + return std::find_if(std::begin(container), std::end(container), pred); + } + + template<class T, class UnaryPredicate> + typename T::iterator cfind_if(T& container, UnaryPredicate pred) + { + return std::find_if(std::begin(container), std::end(container), pred); + } +} + class ahl_binding_t { using role_action = std::function<void(afb_request*, std::string, std::string, json_object*)>; -private: +private: afb_dynapi* handle_; - afb_dynapi* apihandle_; ahl_4a_t config_; std::map<std::string, role_action> actions_; + std::vector<role_t> opened_roles_; explicit ahl_binding_t(); @@ -60,10 +83,13 @@ private: void volume(afb_request* req, std::string role, std::string stream, json_object* arg); void open(afb_request* req, std::string role, std::string stream, json_object* arg); + void close(afb_request* req, std::string role, std::string stream, json_object* arg); + void interrupt(afb_request* req, std::string role, std::string stream, json_object* arg); + + void policy_open(afb_request* req, const role_t& role); public: static ahl_binding_t& instance(); - int build(afb_dynapi* handle); int preinit(afb_dynapi* handle); int init(); void event(std::string name, json_object* arg); diff --git a/ahl-binding/interrupt.cpp b/ahl-binding/interrupt.cpp new file mode 100644 index 0000000..fc54139 --- /dev/null +++ b/ahl-binding/interrupt.cpp @@ -0,0 +1,34 @@ +#include "interrupt.hpp" + +interrupt_t::interrupt_t(json_object* o) +{ + jcast(type_, o, "type"); + args_ = json_object_object_get(o, "args"); +} + +interrupt_t& interrupt_t::operator<<(json_object* o) +{ + jcast(type_, o, "type"); + args_ = json_object_object_get(o, "args"); + return *this; +} + +std::string interrupt_t::type() const +{ + return type_; +} + +json_object* interrupt_t::args() const +{ + return args_; +} + +void interrupt_t::type(std::string v) +{ + type_ = v; +} + +void interrupt_t::args(json_object* v) +{ + args_ = v; +} diff --git a/ahl-binding/interrupt.hpp b/ahl-binding/interrupt.hpp new file mode 100644 index 0000000..caf5cda --- /dev/null +++ b/ahl-binding/interrupt.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "jsonc_utils.hpp" + +class interrupt_t +{ +private: + std::string type_; + json_object* args_; + +public: + explicit interrupt_t() = default; + explicit interrupt_t(const interrupt_t&) = default; + explicit interrupt_t(interrupt_t&&) = default; + ~interrupt_t() = default; + + interrupt_t& operator=(const interrupt_t&) = default; + interrupt_t& operator=(interrupt_t&&) = default; + + explicit interrupt_t(json_object* o); + interrupt_t& operator<<(json_object* o); + + std::string type() const; + json_object* args() const; + + void type(std::string v); + void args(json_object* v); +}; diff --git a/ahl-binding/jsonc_utils.hpp b/ahl-binding/jsonc_utils.hpp index 02e113b..5e82fca 100644 --- a/ahl-binding/jsonc_utils.hpp +++ b/ahl-binding/jsonc_utils.hpp @@ -31,6 +31,7 @@ inline T& jcast(T& v, json_object* o) template<class T> inline T& jcast_array(T& v, json_object* o) { + if (o == nullptr) return v; auto sz = json_object_array_length(o); for(auto i = 0; i < sz ; ++i) { diff --git a/ahl-binding/role.cpp b/ahl-binding/role.cpp index f0f744f..9338436 100644 --- a/ahl-binding/role.cpp +++ b/ahl-binding/role.cpp @@ -34,6 +34,7 @@ role_t::role_t(json_object* j) jcast(description_, j, "description"); jcast(priority_, j, "priority"); jcast(stream_, j, "stream"); + jcast_array(interrupts_, j, "interrupts"); } role_t& role_t::operator<<(json_object* j) @@ -43,6 +44,7 @@ role_t& role_t::operator<<(json_object* j) jcast(description_, j, "description"); jcast(priority_, j, "priority"); jcast(stream_, j, "stream"); + jcast_array(interrupts_, j, "interrupts"); return *this; } @@ -105,3 +107,8 @@ void role_t::device_uri(std::string v) { device_uri_ = v; } + +const std::vector<interrupt_t>& role_t::interrupts() const +{ + return interrupts_; +} diff --git a/ahl-binding/role.hpp b/ahl-binding/role.hpp index 5b3d6b1..fa1bcd4 100644 --- a/ahl-binding/role.hpp +++ b/ahl-binding/role.hpp @@ -17,7 +17,8 @@ * limitations under the License. */ -#include "jsonc_utils.hpp" +#include <vector> +#include "interrupt.hpp" class role_t { @@ -28,6 +29,7 @@ private: std::string stream_; std::string device_uri_; int priority_; + std::vector<interrupt_t> interrupts_; public: explicit role_t() = default; @@ -50,6 +52,7 @@ public: std::string stream() const; int priority() const; std::string device_uri() const; + const std::vector<interrupt_t>& interrupts() const; void uid(std::string v); void name(std::string v); diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index 31fb7bf..4c950b8 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -37,6 +37,14 @@ set(PROJECT_SRC_DIR_PATTERN "[^_]*") # Compilation Mode (DEBUG, RELEASE) # ---------------------------------- +# Set a default build type if none was specified +set(default_build_type "Debug") +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() # Alsa does not really like libEfence set(USE_EFENCE 0) @@ -98,7 +106,7 @@ set(COMPILE_OPTIONS #set(DEBUG_COMPILE_OPTIONS -g -ggdb -Wp,-U_FORTIFY_SOURCE CACHE STRING "Compilation flags for DEBUG build type.") #set(CCOV_COMPILE_OPTIONS -g -O2 --coverage CACHE STRING "Compilation flags for CCOV build type.") #set(RELEASE_COMPILE_OPTIONS -g -O2 CACHE STRING "Compilation flags for RELEASE build type.") -include_directories(${CMAKE_SOURCE_DIR}/controller/ctl-lib ${CMAKE_SOURCE_DIR}/afb-utilities) +#include_directories(${CMAKE_SOURCE_DIR}/controller/ctl-lib ${CMAKE_SOURCE_DIR}/afb-utilities) # Print a helper message when every thing is finished # ---------------------------------------------------- diff --git a/conf.d/project/policy-4a-sample1.json b/conf.d/project/policy-4a-sample1.json index 6d13d61..d71bbe9 100644 --- a/conf.d/project/policy-4a-sample1.json +++ b/conf.d/project/policy-4a-sample1.json @@ -27,14 +27,20 @@ "name": "emergency", "description": "Safety-relevant or critical alerts/alarms", "priority": 100, - "stream": "emergency" + "stream": "emergency", + "interrupts":[ + {"type": "ramp", "args": { "uid": "ramp-slow", "volume": 30} } + ] }, { "uid": "role-navigation", "name": "navigation", "description": "Navigation instructions (GPS, turn directions, etc...)", "priority": 25, - "stream": "navigation" + "stream": "navigation", + "interrupts":[ + {"type": "ramp", "args": { "uid": "ramp-slow", "volume": 30} } + ] } ] } |