diff options
-rwxr-xr-x | autobuild/agl/autobuild | 131 | ||||
-rwxr-xr-x | autobuild/linux/autobuild | 131 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 2 | ||||
-rw-r--r-- | conf.d/project/etc/vshl-core-api.json | 10 | ||||
-rw-r--r-- | conf.d/wgt/config.xml.in | 3 | ||||
-rw-r--r-- | src/plugins/VshlCoreApi.cpp | 209 | ||||
-rw-r--r-- | src/plugins/VshlCoreApi.h | 5 | ||||
-rw-r--r-- | src/plugins/core/VRRequestProcessor.h | 6 | ||||
-rw-r--r-- | src/plugins/core/VRRequestProcessorImpl.cpp | 14 | ||||
-rw-r--r-- | src/plugins/core/include/VRRequest.h | 6 | ||||
-rw-r--r-- | src/plugins/core/include/VRRequestProcessorDelegate.h | 4 | ||||
-rw-r--r-- | src/plugins/core/src/VRRequestImpl.cpp | 16 | ||||
-rw-r--r-- | src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp | 26 | ||||
-rw-r--r-- | src/plugins/voiceagents/VoiceAgentEventNames.h | 5 | ||||
-rw-r--r-- | src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp | 14 |
15 files changed, 492 insertions, 90 deletions
diff --git a/autobuild/agl/autobuild b/autobuild/agl/autobuild index db00c1a..16181b8 100755 --- a/autobuild/agl/autobuild +++ b/autobuild/agl/autobuild @@ -1,5 +1,6 @@ #!/usr/bin/make -f # Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group # Author "Romain Forlot" <romain.forlot@iot.bzh> # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,65 +16,113 @@ # limitations under the License. THISFILE := $(lastword $(MAKEFILE_LIST)) -BUILD_DIR := $(abspath $(dir $(THISFILE))/../../build) -DEST := ${BUILD_DIR} +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) -.PHONY: all clean distclean configure build package help update +# Build directories +# Note that the debug/test/coverage directories are defined in relation +# to the release directory (BUILD_DIR), this needs to be kept in mind +# if over-riding it and building those widget types, the specific widget +# type variable (e.g. BUILD_DIR_DEBUG) may also need to be specified +# to yield the desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) +BUILD_DIR_TEST = $(abspath $(BUILD_DIR)/../build-test) +BUILD_DIR_COVERAGE = $(abspath $(BUILD_DIR)/../build-coverage) -all: help +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release/test builds +BUILD_TYPE = RELEASE + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-test clean-coverage clean-all +.PHONY: configure configure-release configure-debug configure-test configure-coverage +.PHONY: build build-release build-debug build-test build-coverage build-all +.PHONY: package package-release package-debug package-test package-coverage package-all help: @echo "List of targets available:" @echo "" @echo "- all" + @echo "- help" @echo "- clean" @echo "- distclean" @echo "- configure" @echo "- build: compilation, link and prepare files for package into a widget" @echo "- package: output a widget file '*.wgt'" - @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory" + @echo "- install: install in your $(CMAKE_INSTALL_DIR) directory" @echo "" @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" @echo "Don't use your build dir as DEST as wgt file is generated at this location" -update: configure - @cmake --build ${BUILD_DIR} --target autobuild - -clean: - @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} ${CLEAN_ARGS} clean) || echo Nothing to clean - -distclean: - @rm -rf ${BUILD_DIR} - -configure: - @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} - @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) - -build: configure - @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all - -package: build - @mkdir -p ${BUILD_DIR}/$@/bin - @mkdir -p ${BUILD_DIR}/$@/etc - @mkdir -p ${BUILD_DIR}/$@/lib - @mkdir -p ${BUILD_DIR}/$@/htdocs - @mkdir -p ${BUILD_DIR}/$@/var - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget - @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ - mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-test build-test package-test clean-test: OUTPUT_DIR = $(BUILD_DIR_TEST) + +configure-coverage build-coverage package-coverage clean-coverage: OUTPUT_DIR = $(BUILD_DIR_COVERAGE) +configure-coverage build-coverage package-coverage: BUILD_TYPE = COVERAGE + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = DEBUG + +clean-release clean-test clean-debug clean-coverage: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ fi -package-test: build - @mkdir -p ${BUILD_DIR}/$@/bin - @mkdir -p ${BUILD_DIR}/$@/etc - @mkdir -p ${BUILD_DIR}/$@/lib - @mkdir -p ${BUILD_DIR}/$@/htdocs - @mkdir -p ${BUILD_DIR}/$@/var - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target test_widget - @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ - mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ +clean: clean-release + +clean-all: clean-release clean-test clean-debug clean-coverage + +distclean: clean-all + +configure-release configure-test configure-debug configure-coverage: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && cmake -S $(ROOT_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CONFIGURE_ARGS)); \ fi +configure: configure-release + +build-release build-debug build-coverage: build-%: configure-% + @cmake --build $(OUTPUT_DIR) $(BUILD_ARGS) --target all + +# Kept for consistency, empty to avoid building everything for test widget +build-test: configure-test + +build: build-release + +build-all: build-release build-debug build-test build-coverage + +package-release package-debug package-coverage: package-%: build-% + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package-test: build-test + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target test_widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package: package-release + +package-all: package-release package-test package-coverage package-debug + +update: configure + @cmake --build $(BUILD_DIR) --target autobuild + install: build - @cmake --build ${BUILD_DIR} ${INSTALL_ARGS} --target install + @cmake --build $(BUILD_DIR) $(INSTALL_ARGS) --target install diff --git a/autobuild/linux/autobuild b/autobuild/linux/autobuild index db00c1a..16181b8 100755 --- a/autobuild/linux/autobuild +++ b/autobuild/linux/autobuild @@ -1,5 +1,6 @@ #!/usr/bin/make -f # Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group # Author "Romain Forlot" <romain.forlot@iot.bzh> # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,65 +16,113 @@ # limitations under the License. THISFILE := $(lastword $(MAKEFILE_LIST)) -BUILD_DIR := $(abspath $(dir $(THISFILE))/../../build) -DEST := ${BUILD_DIR} +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) -.PHONY: all clean distclean configure build package help update +# Build directories +# Note that the debug/test/coverage directories are defined in relation +# to the release directory (BUILD_DIR), this needs to be kept in mind +# if over-riding it and building those widget types, the specific widget +# type variable (e.g. BUILD_DIR_DEBUG) may also need to be specified +# to yield the desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) +BUILD_DIR_TEST = $(abspath $(BUILD_DIR)/../build-test) +BUILD_DIR_COVERAGE = $(abspath $(BUILD_DIR)/../build-coverage) -all: help +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release/test builds +BUILD_TYPE = RELEASE + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-test clean-coverage clean-all +.PHONY: configure configure-release configure-debug configure-test configure-coverage +.PHONY: build build-release build-debug build-test build-coverage build-all +.PHONY: package package-release package-debug package-test package-coverage package-all help: @echo "List of targets available:" @echo "" @echo "- all" + @echo "- help" @echo "- clean" @echo "- distclean" @echo "- configure" @echo "- build: compilation, link and prepare files for package into a widget" @echo "- package: output a widget file '*.wgt'" - @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory" + @echo "- install: install in your $(CMAKE_INSTALL_DIR) directory" @echo "" @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" @echo "Don't use your build dir as DEST as wgt file is generated at this location" -update: configure - @cmake --build ${BUILD_DIR} --target autobuild - -clean: - @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} ${CLEAN_ARGS} clean) || echo Nothing to clean - -distclean: - @rm -rf ${BUILD_DIR} - -configure: - @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} - @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) - -build: configure - @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all - -package: build - @mkdir -p ${BUILD_DIR}/$@/bin - @mkdir -p ${BUILD_DIR}/$@/etc - @mkdir -p ${BUILD_DIR}/$@/lib - @mkdir -p ${BUILD_DIR}/$@/htdocs - @mkdir -p ${BUILD_DIR}/$@/var - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget - @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ - mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-test build-test package-test clean-test: OUTPUT_DIR = $(BUILD_DIR_TEST) + +configure-coverage build-coverage package-coverage clean-coverage: OUTPUT_DIR = $(BUILD_DIR_COVERAGE) +configure-coverage build-coverage package-coverage: BUILD_TYPE = COVERAGE + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = DEBUG + +clean-release clean-test clean-debug clean-coverage: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ fi -package-test: build - @mkdir -p ${BUILD_DIR}/$@/bin - @mkdir -p ${BUILD_DIR}/$@/etc - @mkdir -p ${BUILD_DIR}/$@/lib - @mkdir -p ${BUILD_DIR}/$@/htdocs - @mkdir -p ${BUILD_DIR}/$@/var - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target test_widget - @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ - mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ +clean: clean-release + +clean-all: clean-release clean-test clean-debug clean-coverage + +distclean: clean-all + +configure-release configure-test configure-debug configure-coverage: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && cmake -S $(ROOT_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CONFIGURE_ARGS)); \ fi +configure: configure-release + +build-release build-debug build-coverage: build-%: configure-% + @cmake --build $(OUTPUT_DIR) $(BUILD_ARGS) --target all + +# Kept for consistency, empty to avoid building everything for test widget +build-test: configure-test + +build: build-release + +build-all: build-release build-debug build-test build-coverage + +package-release package-debug package-coverage: package-%: build-% + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package-test: build-test + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target test_widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package: package-release + +package-all: package-release package-test package-coverage package-debug + +update: configure + @cmake --build $(BUILD_DIR) --target autobuild + install: build - @cmake --build ${BUILD_DIR} ${INSTALL_ARGS} --target install + @cmake --build $(BUILD_DIR) $(INSTALL_ARGS) --target install diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index 099662c..d410d8b 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -43,7 +43,7 @@ set(PROJECT_CMAKE_CONF_DIR "conf.d") # Compilation Mode (DEBUG, RELEASE) # ---------------------------------- -set(CMAKE_BUILD_TYPE "RELEASE") +set(BUILD_TYPE "RELEASE") #set(USE_EFENCE 1) # Helpers Submodule parameters diff --git a/conf.d/project/etc/vshl-core-api.json b/conf.d/project/etc/vshl-core-api.json index 44d578a..001ac42 100644 --- a/conf.d/project/etc/vshl-core-api.json +++ b/conf.d/project/etc/vshl-core-api.json @@ -4,13 +4,18 @@ "uid": "vshl-core", "version": "1.0", "api": "vshl-core", - "info": "High Level Voice Service Core APIs" + "info": "High Level Voice Service Core APIs", + "require": ["signal-composer"] }, "onload": [{ "uid": "loadVoiceAgentsConfig", "info": "Loading the information about voice agents managed by the high level voice service.", "action": "plugin://vshl-core#loadVoiceAgentsConfig", + }, { + "uid": "subscribeVoiceTriggerEvents", + "info": "Subscribe to voice recognition triggering events.", + "action": "plugin://vshl-core#subscribeVoiceTriggerEvents", }], "plugins": [{ @@ -38,5 +43,8 @@ "uid": "setDefaultVoiceAgent", "privileges": "urn:AGL:permission:vshl-core:voiceagents:public", "action": "plugin://vshl-core#setDefaultVoiceAgent" + }, { + "uid": "subscribeToLoginEvents", + "action": "plugin://vshl-core#subscribeToLoginEvents" }] } diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in index 3c0b0a0..ee70db0 100644 --- a/conf.d/wgt/config.xml.in +++ b/conf.d/wgt/config.xml.in @@ -17,4 +17,7 @@ <param name="urn:AGL:permission::public:hidden" value="required" /> <param name="urn:AGL:permission::platform:apis:auto-ws" value="required" /> </feature> + <feature name="urn:AGL:widget:required-api"> + <param name="signal-composer" value="ws" /> +</feature> </widget> diff --git a/src/plugins/VshlCoreApi.cpp b/src/plugins/VshlCoreApi.cpp index 4c32391..b14f949 100644 --- a/src/plugins/VshlCoreApi.cpp +++ b/src/plugins/VshlCoreApi.cpp @@ -48,6 +48,7 @@ static std::string VA_JSON_ATTR_DESCRIPTION = "description"; static std::string VA_JSON_ATTR_VENDOR = "vendor"; static std::string STARTLISTENING_JSON_ATTR_REQUEST = "request_id"; +static std::string SUBSCRIBE2LOGINEVENTS_JSON_ATTR_REQUEST = "request_id"; static std::string EVENTS_JSON_ATTR_VA_ID = "va_id"; static std::string EVENTS_JSON_ATTR_EVENTS = "events"; @@ -65,6 +66,11 @@ static std::unique_ptr<vshlcore::utilities::events::EventRouter> sEventRouter; using json = nlohmann::json; using Level = vshlcore::utilities::logging::Logger::Level; +static const char *signalcomposer_events[] = { + "event.voice", + NULL, +}; + CTLP_ONLOAD(plugin, ret) { if (plugin->api == nullptr) { return -1; @@ -158,6 +164,65 @@ CTLP_CAPI(onDialogStateEvent, source, argsJ, eventJ) { return 0; } +CTLP_CAPI(onLoginPairReceivedEvent, source, argsJ, eventJ) { + if (sEventRouter == nullptr) { + return -1; + } + + string eventName = vshlcore::voiceagents::VSHL_EVENT_LOGINPAIR_RECEIVED_EVENT; + json eventJson = json::parse(json_object_to_json_string(eventJ)); + if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) { + sLogger->log(Level::ERROR, TAG, "onLoginPairReceivedEvent: No voiceagent id found."); + return -1; + } + std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get<string>()); + + sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ)); + + return 0; +} + +CTLP_CAPI(onLoginPairExpiredEvent, source, argsJ, eventJ) { + if (sEventRouter == nullptr) { + return -1; + } + + string eventName = vshlcore::voiceagents::VSHL_EVENT_LOGINPAIR_EXPIRED_EVENT; + json eventJson = json::parse(json_object_to_json_string(eventJ)); + if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) { + sLogger->log(Level::ERROR, TAG, "onLoginPairExpiredEvent: No voiceagent id found."); + return -1; + } + std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get<string>()); + + sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ)); + + return 0; +} + +CTLP_CAPI(onVoiceTriggerEvent, source, argsJ, eventJ) { + if (sVRRequestProcessor == nullptr) { + return -1; + } + + json eventJson = json::parse(json_object_to_json_string(eventJ)); + if (eventJson.find("uid") == eventJson.end()) { + sLogger->log(Level::ERROR, TAG, "onVoiceTriggerEvent: No uid found."); + return -1; + } + if (eventJson.find("value") == eventJson.end()) { + sLogger->log(Level::ERROR, TAG, "onVoiceTriggerEvent: No value found."); + return -1; + } + std::string uid(eventJson["uid"].get<std::string>()); + bool value(eventJson["value"].get<bool>()); + if (uid == "event.voice" && value == true) { + sVRRequestProcessor->startListening(); + } + + return 0; +} + // Helper function to add events for voice agent to controller // configuration. It relies on the controller configuration being // available via the userdata of the API pointer. @@ -208,6 +273,22 @@ static int AddVoiceAgentEvents(std::string &agentApi) { json_object_object_add(eventJ, "action", actionJ); json_object_array_add(eventsJ, eventJ); + eventJ = json_object_new_object(); + uid = agentApi + "/voice_cbl_codepair_received_event"; + uidJ = json_object_new_string(uid.c_str()); + json_object_object_add(eventJ, "uid", uidJ); + actionJ = json_object_new_string("plugin://vshl-core#onLoginPairReceivedEvent"); + json_object_object_add(eventJ, "action", actionJ); + json_object_array_add(eventsJ, eventJ); + + eventJ = json_object_new_object(); + uid = agentApi + "/voice_cbl_codepair_expired_event"; + uidJ = json_object_new_string(uid.c_str()); + json_object_object_add(eventJ, "uid", uidJ); + actionJ = json_object_new_string("plugin://vshl-core#onLoginPairExpiredEvent"); + json_object_object_add(eventJ, "action", actionJ); + json_object_array_add(eventsJ, eventJ); + // Call into controller to add event actions // NOTE: AFAICT the JSON objects end up reused by the controller data // structures, so eventsJ should not be freed with a put after @@ -299,7 +380,7 @@ CTLP_CAPI(loadVoiceAgentsConfig, source, argsJ, eventJ) { return -1; } - string defaultVoiceAgent; + std::string defaultVoiceAgent; if (argsJ != nullptr) { // Parse any agent configs from controller arguments @@ -353,6 +434,86 @@ CTLP_CAPI(loadVoiceAgentsConfig, source, argsJ, eventJ) { return 0; } +// Helper function to add signal composer events for voice triggers to +// controller configuration. It relies on the controller configuration +// being available via the userdata of the API pointer. +static int AddVoiceTriggerEvents(std::string &agentApi, std::string &uuid) { + // Retrieve section config from api handle + CtlConfigT *ctrlConfig = (CtlConfigT*) afb_api_get_userdata(sAfbApi->getApi()); + if (ctrlConfig == nullptr) { + sLogger->log(Level::ERROR, TAG, "AddVoiceAgentEvents: ctrlConfig == nullptr"); + return -1; + } + + CtlSectionT* section = nullptr; + for (int idx = 0; ctrlConfig->sections[idx].key != NULL; ++idx) { + if (!strcasecmp(ctrlConfig->sections[idx].key, "events")) { + section = &(ctrlConfig->sections[idx]); + break; + } + } + if (section == nullptr) { + sLogger->log(Level::ERROR, TAG, "AddVoiceTriggerEvents: events section not found"); + return -1; + } + + // Fill out events JSON array to pass to the controller + json_object* eventsJ = json_object_new_array(); + + json_object *eventJ = json_object_new_object(); + std::string uid = agentApi + "/" + uuid; + json_object *uidJ = json_object_new_string(uid.c_str()); + json_object_object_add(eventJ, "uid", uidJ); + json_object *actionJ = json_object_new_string("plugin://vshl-core#onVoiceTriggerEvent"); + json_object_object_add(eventJ, "action", actionJ); + json_object_array_add(eventsJ, eventJ); + + // Call into controller to add event actions + // NOTE: AFAICT the JSON objects end up reused by the controller data + // structures, so eventsJ should not be freed with a put after + // this call. + int rc = AddActionsToSection(sAfbApi->getApi(), section, eventsJ, 0); + if (rc != 0) { + stringstream message; + message << "AddVoiceTriggerEvents: AddActionsToSection rc = " << rc; + sLogger->log(Level::WARNING, TAG, message.str().c_str()); + } + return rc; +} + +CTLP_CAPI(subscribeVoiceTriggerEvents, source, argsJ, eventJ) { + // Subscribe to signal-composer voice/PTT events + const char **tmp = signalcomposer_events; + json_object *args = json_object_new_object(); + json_object *signals = json_object_new_array(); + + while (*tmp) { + json_object_array_add(signals, json_object_new_string(*tmp++)); + } + json_object_object_add(args, "signal", signals); + if (json_object_array_length(signals)) { + struct json_object *responseJ; + int rc = afb_api_call_sync(sAfbApi->getApi(), "signal-composer", + "subscribe", args, &responseJ, NULL, NULL); + if (rc == 0 && responseJ) { + struct json_object *uuidJ; + json_object_object_get_ex(responseJ, "uuid", &uuidJ); + if (uuidJ) { + std::string uuid(json_object_get_string(uuidJ)); + std::string api("signal-composer"); + AddVoiceTriggerEvents(api, uuid); + } + } + if (responseJ) + json_object_put(responseJ); + } else { + json_object_put(args); + } + + // Always return zero so service will start + return 0; +} + CTLP_CAPI(startListening, source, argsJ, eventJ) { if (sVoiceAgentsDataManager == nullptr) { return -1; @@ -363,7 +524,7 @@ CTLP_CAPI(startListening, source, argsJ, eventJ) { } int result = 0; - string requestId = sVRRequestProcessor->startListening(); + std::string requestId = sVRRequestProcessor->startListening(); if (!requestId.empty()) { json responseJson; @@ -384,7 +545,7 @@ CTLP_CAPI(enumerateVoiceAgents, source, argsJ, eventJ) { if (sVoiceAgentsDataManager == nullptr) { return -1; } - + auto agents = sVoiceAgentsDataManager->getAllVoiceAgents(); std::string defaultAgentId(sVoiceAgentsDataManager->getDefaultVoiceAgent()); @@ -442,7 +603,7 @@ CTLP_CAPI(subscribe, source, argsJ, eventJ) { sLogger->log(Level::ERROR, TAG, "subscribe: No events array found in subscribe json"); return -1; } - list<string> events(subscribeJson[EVENTS_JSON_ATTR_EVENTS].get<list<string>>()); + std::list<std::string> events(subscribeJson[EVENTS_JSON_ATTR_EVENTS].get<list<string>>()); // Subscribe this client for the listed events. auto request = vshlcore::afb::AFBRequestImpl::create(source->request); @@ -483,3 +644,43 @@ CTLP_CAPI(setDefaultVoiceAgent, source, argsJ, eventJ) { afb_req_success(source->request, NULL, NULL); return 0; } + +CTLP_CAPI(subscribeToLoginEvents, source, argsJ, eventJ) { + if (sVoiceAgentsDataManager == nullptr) { + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "subscribeToLoginEvents: No arguments supplied."); + return -1; + } + + if (sVRRequestProcessor == nullptr) { + return -1; + } + + json subscribeJson = json::parse(json_object_to_json_string(eventJ)); + if (subscribeJson.find(EVENTS_JSON_ATTR_VA_ID) == subscribeJson.end()) { + sLogger->log(Level::ERROR, TAG, "subscribeToLoginEvents: No voiceagent id found in subscribe json"); + return -1; + } + std::string voiceAgentId(subscribeJson[EVENTS_JSON_ATTR_VA_ID].get<string>()); + if (subscribeJson.find(EVENTS_JSON_ATTR_EVENTS) == subscribeJson.end()) { + sLogger->log(Level::ERROR, TAG, "subscribeToLoginEvents: No events array found in subscribe json"); + return -1; + } + std::list<std::string> events(subscribeJson[EVENTS_JSON_ATTR_EVENTS].get<list<string>>()); + + int result = 0; + std::string requestId = sVRRequestProcessor->subscribeToLoginEvents(voiceAgentId, &events); + + if (!requestId.empty()) { + json responseJson; + responseJson[SUBSCRIBE2LOGINEVENTS_JSON_ATTR_REQUEST] = requestId; + afb_req_success(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL); + } else { + afb_req_fail(source->request, NULL, "Failed to subscribeToLoginEvents..."); + } + + return 0; +} diff --git a/src/plugins/VshlCoreApi.h b/src/plugins/VshlCoreApi.h index 5e56b75..7c5bf69 100644 --- a/src/plugins/VshlCoreApi.h +++ b/src/plugins/VshlCoreApi.h @@ -27,12 +27,17 @@ CTLP_INIT(plugin, ret); int onAuthStateEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ); int onConnectionStateEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ); int onDialogStateEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int onLoginPairReceivedEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int onLoginPairExpiredEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int onVoiceTriggerEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ); int loadVoiceAgentsConfig(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int subscribeVoiceTriggerEvents(CtlSourceT* source, json_object* argsJ, json_object* queryJ); int startListening(CtlSourceT* source, json_object* argsJ, json_object* queryJ); int cancelListening(CtlSourceT* source, json_object* argsJ, json_object* queryJ); int enumerateVoiceAgents(CtlSourceT* source, json_object* argsJ, json_object* queryJ); int subscribe(CtlSourceT* source, json_object* argsJ, json_object* queryJ); int setDefaultVoiceAgent(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int subscribeToLoginEvents(CtlSourceT* source, json_object* argsJ, json_object* queryJ); #ifdef __cplusplus } diff --git a/src/plugins/core/VRRequestProcessor.h b/src/plugins/core/VRRequestProcessor.h index 97e277a..037b27f 100644 --- a/src/plugins/core/VRRequestProcessor.h +++ b/src/plugins/core/VRRequestProcessor.h @@ -17,6 +17,7 @@ #include <memory> #include <unordered_map> +#include <list> #include "core/include/VRRequest.h" #include "core/include/VRRequestProcessorDelegate.h" @@ -43,6 +44,11 @@ public: // is returned. string startListening(); + // Triggers a voiceagent to generate events to provide info to allow + // the user to complete the authorization process (if required). + // Returns the request ID. An empty request ID is returned on failure. + string subscribeToLoginEvents(string va_id, list<string> *args); + // Cancels all the active requests void cancel(); diff --git a/src/plugins/core/VRRequestProcessorImpl.cpp b/src/plugins/core/VRRequestProcessorImpl.cpp index c07f745..d9fa846 100644 --- a/src/plugins/core/VRRequestProcessorImpl.cpp +++ b/src/plugins/core/VRRequestProcessorImpl.cpp @@ -57,6 +57,20 @@ string VRRequestProcessor::startListening() { return mDelegate->startRequestForVoiceAgent(defaultVA); } +string VRRequestProcessor::subscribeToLoginEvents(std::string va_id, std::list<std::string> *args) { + // Currently simply send the request to the default voice agent, ignoring va_id + shared_ptr<vshlcore::common::interfaces::IVoiceAgent> defaultVA = mDelegate->getDefaultVoiceAgent(); + if (!defaultVA) { + mLogger->log(Level::ERROR, TAG, "Failed to subscribeToLoginEvents. No default voiceagent found."); + return ""; + } + + // If the requests container is not empty, then clear the + // existing requests in flight and create a new request. + mDelegate->cancelAllRequests(); + return mDelegate->loginEventsRequestForVoiceAgent(defaultVA, args); +} + void VRRequestProcessor::cancel() { // Cancel all pending requests mDelegate->cancelAllRequests(); diff --git a/src/plugins/core/include/VRRequest.h b/src/plugins/core/include/VRRequest.h index 8b9e842..feeac6d 100644 --- a/src/plugins/core/include/VRRequest.h +++ b/src/plugins/core/include/VRRequest.h @@ -16,6 +16,7 @@ #define VSHL_CORE_INCLUDE_VR_REQUEST_H_ #include <memory> +#include <list> #include "interfaces/afb/IAFBApi.h" #include "interfaces/utilities/logging/ILogger.h" @@ -32,6 +33,7 @@ class VRRequest { public: // API Verbs static std::string VA_VERB_STARTLISTENING; + static std::string VA_VERB_SUBSCRIBETOCBLEVENTS; static std::string VA_VERB_CANCEL; // Create a VRRequest. @@ -48,6 +50,10 @@ public: // Returns true if started successfully. False otherwise. bool startListening(); + // Invokes the underlying voiceagent's subscribe to login events API. + // Returns true if successful, false otherwise. + bool subscribeToLoginEvents(std::list<std::string> *args); + // Cancels the voice recognition in the unlerlying voiceagent. // Returns true if canceled successfully. False otherwise. bool cancel(); diff --git a/src/plugins/core/include/VRRequestProcessorDelegate.h b/src/plugins/core/include/VRRequestProcessorDelegate.h index 2c36d38..2ada2fb 100644 --- a/src/plugins/core/include/VRRequestProcessorDelegate.h +++ b/src/plugins/core/include/VRRequestProcessorDelegate.h @@ -17,6 +17,7 @@ #include <memory> #include <unordered_map> +#include <list> #include "core/include/VRRequest.h" #include "interfaces/afb/IAFBApi.h" @@ -55,6 +56,9 @@ public: // voiceagent is called. string startRequestForVoiceAgent(shared_ptr<vshlcore::common::interfaces::IVoiceAgent> voiceAgent); + string loginEventsRequestForVoiceAgent(shared_ptr<vshlcore::common::interfaces::IVoiceAgent> voiceAgent, + std::list<std::string> *args); + // Cancel all requests void cancelAllRequests(); diff --git a/src/plugins/core/src/VRRequestImpl.cpp b/src/plugins/core/src/VRRequestImpl.cpp index 63302d8..d1a18ac 100644 --- a/src/plugins/core/src/VRRequestImpl.cpp +++ b/src/plugins/core/src/VRRequestImpl.cpp @@ -32,6 +32,7 @@ namespace core { string VRRequest::VA_VERB_STARTLISTENING = "startListening"; string VRRequest::VA_VERB_CANCEL = "cancel"; +string VRRequest::VA_VERB_SUBSCRIBETOCBLEVENTS = "subscribeToCBLEvents"; unique_ptr<VRRequest> VRRequest::create( shared_ptr<vshlcore::common::interfaces::ILogger> logger, @@ -76,6 +77,21 @@ bool VRRequest::startListening() { return true; } + bool VRRequest::subscribeToLoginEvents(std::list<std::string> *args) { + json_object* argsJ = json_object_new_object(); + json_object* evJ = json_object_new_array(); + json_object* resultJ; + std::string error, info; + bool result = true; + + json_object_object_add(argsJ, "events", evJ); + int rc = mApi->callSync(mVoiceAgent->getApi(), VA_VERB_SUBSCRIBETOCBLEVENTS, argsJ, &resultJ, error, info); + + FREEIF(resultJ); + + return true; +} + bool VRRequest::cancel() { json_object* object = NULL; std::string error, info; diff --git a/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp b/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp index 78ef10a..57fc592 100644 --- a/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp +++ b/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp @@ -62,6 +62,32 @@ string VRRequestProcessorDelegate::startRequestForVoiceAgent( return newReqId; } +string VRRequestProcessorDelegate::loginEventsRequestForVoiceAgent( + shared_ptr<vshlcore::common::interfaces::IVoiceAgent> voiceAgent, + std::list<std::string> *args) { + if (!mApi) { + mLogger->log(Level::ERROR, TAG, "Failed to loginEventsRequestForVoiceAgent: " + voiceAgent->getId() + ", No API."); + return ""; + } + + // Generate a new request ID. + string newReqId = vshlcore::utilities::uuid::generateUUID(); + + // Create a new request and start listening. + shared_ptr<VRRequest> newRequest = VRRequest::create(mLogger, mApi, newReqId, voiceAgent); + + mLogger->log(Level::DEBUG, TAG, "Starting login request with ID: " + newReqId); + if (!newRequest->subscribeToLoginEvents(args)) { + mLogger->log(Level::ERROR, TAG, "Failed to subscribe to login events."); + return ""; + } + + // Insert only if its started successfully. + mVRRequests.insert(make_pair(voiceAgent->getId(), newRequest)); + + return newReqId; +} + void VRRequestProcessorDelegate::cancelAllRequests() { // Cancel Pending requests if (!mVRRequests.empty()) { diff --git a/src/plugins/voiceagents/VoiceAgentEventNames.h b/src/plugins/voiceagents/VoiceAgentEventNames.h index 92cc8dc..abee50a 100644 --- a/src/plugins/voiceagents/VoiceAgentEventNames.h +++ b/src/plugins/voiceagents/VoiceAgentEventNames.h @@ -25,10 +25,13 @@ namespace voiceagents { static string VSHL_EVENT_AUTH_STATE_EVENT = "voice_authstate_event"; static string VSHL_EVENT_CONNECTION_STATE_EVENT = "voice_connectionstate_event"; static string VSHL_EVENT_DIALOG_STATE_EVENT = "voice_dialogstate_event"; +static string VSHL_EVENT_LOGINPAIR_RECEIVED_EVENT = "voice_cbl_codepair_received_event"; +static string VSHL_EVENT_LOGINPAIR_EXPIRED_EVENT = "voice_cbl_codepair_expired_event"; static list<string> VSHL_EVENTS = { VSHL_EVENT_AUTH_STATE_EVENT, VSHL_EVENT_CONNECTION_STATE_EVENT, - VSHL_EVENT_DIALOG_STATE_EVENT, + VSHL_EVENT_DIALOG_STATE_EVENT, VSHL_EVENT_LOGINPAIR_RECEIVED_EVENT, + VSHL_EVENT_LOGINPAIR_EXPIRED_EVENT, }; } // namespace voiceagents diff --git a/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp b/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp index 3b55505..bff9d8c 100644 --- a/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp +++ b/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp @@ -14,6 +14,8 @@ */ #include "voiceagents/include/VoiceAgentEventsHandler.h" +#include <json-c/json.h> + static string TAG = "vshlcore::voiceagents::VoiceAgentEventsHandler"; static string VA_VERB_SUBSCRIBE = "subscribe"; @@ -101,7 +103,17 @@ bool VoiceAgentEventsHandler::onIncomingEvent(const string eventName, const stri string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId); auto it = mEventsMap.find(eventNameWithVAId); if (it != mEventsMap.end()) { - return it->second->publishEvent(json_object_new_string(payload.c_str())); + json_object* payloadJ = NULL; + if(payload.length()) { + payloadJ = json_tokener_parse(payload.c_str()); + } else { + payloadJ = json_object_new_string(""); + } + if(!payloadJ) { + mLogger->log(Level::ERROR, TAG, "Unable to parse payload JSON: " + payload); + return false; + } + return it->second->publishEvent(payloadJ); } return true; |