From b6abca2edcb36c0c0848d1cd8dc291f23293aa80 Mon Sep 17 00:00:00 2001 From: Naveen Bobbili Date: Mon, 12 Nov 2018 16:12:38 -0800 Subject: SPEC-1924: AGL Speech Framework's Voice Service High Level 1.0 Release. Details: 1) Control plugin implementation for VSHL 1.0 2) Exposed APIs that are documented in the confluence page https://confluence.automotivelinux.org/display/SPE/Speech+EG+Architecture 3) Implemented 39 unit tests based on GTest framework to test all the low level components of VSHL binding. 4) Implemented a HTML5 based VSHL API tester application to test VSHL APIs. API specification: https://confluence.automotivelinux.org/display/SPE/Speech+EG+Architecture#SpeechEGArchitecture-HighLevelVoiceService Test performed: 1) Tested AGL service running Alexa Auto SDK https://github.com/alexa/aac-sdk on Ubuntu 16.04 and Renesas R-Car M3 board. License: Apache 2.0 Developers/Owners: Naveen Bobbili (nbobbili@amazon.com) Prakash Buddhiraja (buddhip@amazon.com) Shotaro Uchida (shotaru@amazon.co.jp) Change-Id: I3370f4ad65aff030f24f4ad571fb02d525bbfbca Signed-off-by: Naveen Bobbili --- src/CMakeLists.txt | 44 ++ src/plugins/CMakeLists.txt | 182 ++++++ src/plugins/TestMain.cpp | 20 + src/plugins/VshlApi.cpp | 607 +++++++++++++++++++++ src/plugins/VshlApi.h | 46 ++ src/plugins/afb/AFBApiImpl.cpp | 87 +++ src/plugins/afb/AFBApiImpl.h | 61 +++ src/plugins/afb/AFBRequestImpl.cpp | 37 ++ src/plugins/afb/AFBRequestImpl.h | 49 ++ src/plugins/afb/include/AFBEventImpl.h | 77 +++ src/plugins/afb/src/AFBEventImpl.cpp | 89 +++ src/plugins/agreement.sh | 43 ++ src/plugins/capabilities/CapabilitiesFactory.cpp | 57 ++ src/plugins/capabilities/CapabilitiesFactory.h | 63 +++ .../capabilities/CapabilityMessagingService.cpp | 117 ++++ .../capabilities/CapabilityMessagingService.h | 82 +++ .../communication/include/PhoneControlCapability.h | 52 ++ .../communication/include/PhoneControlMessages.h | 128 +++++ .../communication/src/PhoneControlCapability.cpp | 42 ++ .../capabilities/core/include/MessageChannel.h | 68 +++ .../capabilities/core/include/PublisherForwarder.h | 73 +++ .../core/include/SubscriberForwarder.h | 84 +++ .../capabilities/core/src/MessageChannel.cpp | 51 ++ .../capabilities/core/src/PublisherForwarder.cpp | 69 +++ .../capabilities/core/src/SubscriberForwarder.cpp | 139 +++++ .../guimetadata/include/GuiMetadataCapability.h | 52 ++ .../guimetadata/include/GuiMetadataMessages.h | 50 ++ .../guimetadata/src/GuiMetadataCapability.cpp | 42 ++ .../navigation/include/NavigationCapability.h | 52 ++ .../navigation/include/NavigationMessages.h | 48 ++ .../navigation/src/NavigationCapability.cpp | 42 ++ .../test/CapabilityMessagingServiceTest.cpp | 96 ++++ .../capabilities/test/PublisherForwarderTest.cpp | 116 ++++ .../capabilities/test/SubscriberForwarderTest.cpp | 262 +++++++++ src/plugins/cmake/gtest.cmake | 44 ++ src/plugins/core/VRRequestProcessor.h | 75 +++ src/plugins/core/VRRequestProcessorImpl.cpp | 72 +++ src/plugins/core/include/VRAgentsObserver.h | 67 +++ src/plugins/core/include/VRRequest.h | 80 +++ .../core/include/VRRequestProcessorDelegate.h | 88 +++ src/plugins/core/src/VRAgentsObserverImpl.cpp | 54 ++ src/plugins/core/src/VRRequestImpl.cpp | 92 ++++ .../core/src/VRRequestProcessorDelegateImpl.cpp | 93 ++++ src/plugins/core/test/VRRequestProcessorTest.cpp | 213 ++++++++ src/plugins/core/test/VRRequestTest.cpp | 101 ++++ src/plugins/interfaces/afb/IAFBApi.h | 98 ++++ src/plugins/interfaces/capabilities/ICapability.h | 58 ++ .../interfaces/utilities/events/IEventFilter.h | 46 ++ src/plugins/interfaces/utilities/logging/ILogger.h | 42 ++ src/plugins/interfaces/voiceagents/IVoiceAgent.h | 95 ++++ .../voiceagents/IVoiceAgentsChangeObserver.h | 80 +++ src/plugins/test/common/ConsoleLogger.cpp | 31 ++ src/plugins/test/common/ConsoleLogger.h | 37 ++ src/plugins/test/mocks/AFBApiMock.h | 41 ++ src/plugins/test/mocks/AFBEventMock.h | 47 ++ src/plugins/test/mocks/AFBRequestMock.h | 33 ++ src/plugins/test/mocks/CapabilityMock.h | 35 ++ .../test/mocks/VoiceAgentsChangeObserverMock.h | 38 ++ src/plugins/utilities/events/EventRouter.cpp | 68 +++ src/plugins/utilities/events/EventRouter.h | 66 +++ src/plugins/utilities/logging/Logger.cpp | 56 ++ src/plugins/utilities/logging/Logger.h | 53 ++ src/plugins/utilities/uuid/UUIDGeneration.cpp | 137 +++++ src/plugins/utilities/uuid/UUIDGeneration.h | 42 ++ src/plugins/voiceagents/VoiceAgentEventNames.h | 38 ++ src/plugins/voiceagents/VoiceAgentsDataManager.h | 135 +++++ .../voiceagents/VoiceAgentsDataManagerImpl.cpp | 272 +++++++++ src/plugins/voiceagents/include/VoiceAgent.h | 95 ++++ .../voiceagents/include/VoiceAgentEventsHandler.h | 95 ++++ .../voiceagents/src/VoiceAgentEventsHandler.cpp | 139 +++++ src/plugins/voiceagents/src/VoiceAgentImpl.cpp | 126 +++++ src/plugins/voiceagents/test/VoiceAgentTest.cpp | 94 ++++ .../test/VoiceAgentsDataManagerTest.cpp | 294 ++++++++++ src/plugins/voiceagents/test/VoiceAgentsTestData.h | 67 +++ src/vshl-apidef.h | 43 ++ src/vshl-apidef.json | 109 ++++ src/vshl-binding.c | 116 ++++ src/vshl-binding.h | 25 + 78 files changed, 6857 insertions(+) create mode 100644 src/CMakeLists.txt create mode 100644 src/plugins/CMakeLists.txt create mode 100644 src/plugins/TestMain.cpp create mode 100644 src/plugins/VshlApi.cpp create mode 100644 src/plugins/VshlApi.h create mode 100644 src/plugins/afb/AFBApiImpl.cpp create mode 100644 src/plugins/afb/AFBApiImpl.h create mode 100644 src/plugins/afb/AFBRequestImpl.cpp create mode 100644 src/plugins/afb/AFBRequestImpl.h create mode 100644 src/plugins/afb/include/AFBEventImpl.h create mode 100644 src/plugins/afb/src/AFBEventImpl.cpp create mode 100755 src/plugins/agreement.sh create mode 100644 src/plugins/capabilities/CapabilitiesFactory.cpp create mode 100644 src/plugins/capabilities/CapabilitiesFactory.h create mode 100644 src/plugins/capabilities/CapabilityMessagingService.cpp create mode 100644 src/plugins/capabilities/CapabilityMessagingService.h create mode 100644 src/plugins/capabilities/communication/include/PhoneControlCapability.h create mode 100644 src/plugins/capabilities/communication/include/PhoneControlMessages.h create mode 100644 src/plugins/capabilities/communication/src/PhoneControlCapability.cpp create mode 100644 src/plugins/capabilities/core/include/MessageChannel.h create mode 100644 src/plugins/capabilities/core/include/PublisherForwarder.h create mode 100644 src/plugins/capabilities/core/include/SubscriberForwarder.h create mode 100644 src/plugins/capabilities/core/src/MessageChannel.cpp create mode 100644 src/plugins/capabilities/core/src/PublisherForwarder.cpp create mode 100644 src/plugins/capabilities/core/src/SubscriberForwarder.cpp create mode 100644 src/plugins/capabilities/guimetadata/include/GuiMetadataCapability.h create mode 100644 src/plugins/capabilities/guimetadata/include/GuiMetadataMessages.h create mode 100644 src/plugins/capabilities/guimetadata/src/GuiMetadataCapability.cpp create mode 100644 src/plugins/capabilities/navigation/include/NavigationCapability.h create mode 100644 src/plugins/capabilities/navigation/include/NavigationMessages.h create mode 100644 src/plugins/capabilities/navigation/src/NavigationCapability.cpp create mode 100644 src/plugins/capabilities/test/CapabilityMessagingServiceTest.cpp create mode 100644 src/plugins/capabilities/test/PublisherForwarderTest.cpp create mode 100644 src/plugins/capabilities/test/SubscriberForwarderTest.cpp create mode 100644 src/plugins/cmake/gtest.cmake create mode 100644 src/plugins/core/VRRequestProcessor.h create mode 100644 src/plugins/core/VRRequestProcessorImpl.cpp create mode 100644 src/plugins/core/include/VRAgentsObserver.h create mode 100644 src/plugins/core/include/VRRequest.h create mode 100644 src/plugins/core/include/VRRequestProcessorDelegate.h create mode 100644 src/plugins/core/src/VRAgentsObserverImpl.cpp create mode 100644 src/plugins/core/src/VRRequestImpl.cpp create mode 100644 src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp create mode 100644 src/plugins/core/test/VRRequestProcessorTest.cpp create mode 100644 src/plugins/core/test/VRRequestTest.cpp create mode 100644 src/plugins/interfaces/afb/IAFBApi.h create mode 100644 src/plugins/interfaces/capabilities/ICapability.h create mode 100644 src/plugins/interfaces/utilities/events/IEventFilter.h create mode 100644 src/plugins/interfaces/utilities/logging/ILogger.h create mode 100644 src/plugins/interfaces/voiceagents/IVoiceAgent.h create mode 100644 src/plugins/interfaces/voiceagents/IVoiceAgentsChangeObserver.h create mode 100644 src/plugins/test/common/ConsoleLogger.cpp create mode 100644 src/plugins/test/common/ConsoleLogger.h create mode 100644 src/plugins/test/mocks/AFBApiMock.h create mode 100644 src/plugins/test/mocks/AFBEventMock.h create mode 100644 src/plugins/test/mocks/AFBRequestMock.h create mode 100644 src/plugins/test/mocks/CapabilityMock.h create mode 100644 src/plugins/test/mocks/VoiceAgentsChangeObserverMock.h create mode 100644 src/plugins/utilities/events/EventRouter.cpp create mode 100644 src/plugins/utilities/events/EventRouter.h create mode 100644 src/plugins/utilities/logging/Logger.cpp create mode 100644 src/plugins/utilities/logging/Logger.h create mode 100644 src/plugins/utilities/uuid/UUIDGeneration.cpp create mode 100644 src/plugins/utilities/uuid/UUIDGeneration.h create mode 100644 src/plugins/voiceagents/VoiceAgentEventNames.h create mode 100644 src/plugins/voiceagents/VoiceAgentsDataManager.h create mode 100644 src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp create mode 100644 src/plugins/voiceagents/include/VoiceAgent.h create mode 100644 src/plugins/voiceagents/include/VoiceAgentEventsHandler.h create mode 100644 src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp create mode 100644 src/plugins/voiceagents/src/VoiceAgentImpl.cpp create mode 100644 src/plugins/voiceagents/test/VoiceAgentTest.cpp create mode 100644 src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp create mode 100644 src/plugins/voiceagents/test/VoiceAgentsTestData.h create mode 100644 src/vshl-apidef.h create mode 100644 src/vshl-apidef.json create mode 100644 src/vshl-binding.c create mode 100644 src/vshl-binding.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..729dcb6 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,44 @@ +########################################################################### +# Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# 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. +########################################################################### + +# Add target to project dependency list +PROJECT_TARGET_ADD(vshl) + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + + # Define project Targets + add_library(${TARGET_NAME} MODULE + ${TARGET_NAME}-binding.c + ) + + set(OPENAPI_DEF "${TARGET_NAME}-apidef" CACHE STRING "name and path to the JSON API definition without extension") + + # Binder exposes a unique public entry point + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + PREFIX "afb-" + LABELS "BINDINGV3" + LINK_FLAGS ${BINDINGS_LINK_FLAG} + OUTPUT_NAME ${TARGET_NAME} + ) + + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + afb-helpers + ctl-utilities + ${link_libraries}) + +add_subdirectory("plugins") \ No newline at end of file diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt new file mode 100644 index 0000000..06ef7a4 --- /dev/null +++ b/src/plugins/CMakeLists.txt @@ -0,0 +1,182 @@ +########################################################################### +# Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# 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. +########################################################################### + +PROJECT_TARGET_ADD(vshl-api) + + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + + set(VSHL_LIB_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/VshlApi.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/VshlApi.h + + # Interfaces + ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/afb/IAFBApi.h + ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/capabilities/ICapability.h + ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/utilities/events/IEventFilter.h + ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/utilities/logging/ILogger.h + ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/voiceagents/IVoiceAgent.h + ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/voiceagents/IVoiceAgentsChangeObserver.h + + # AFB + ${CMAKE_CURRENT_SOURCE_DIR}/afb/AFBApiImpl.h + ${CMAKE_CURRENT_SOURCE_DIR}/afb/AFBApiImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/afb/AFBRequestImpl.h + ${CMAKE_CURRENT_SOURCE_DIR}/afb/AFBRequestImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/afb/include/AFBEventImpl.h + ${CMAKE_CURRENT_SOURCE_DIR}/afb/src/AFBEventImpl.cpp + + # VoiceAgents + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/VoiceAgentsDataManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/VoiceAgentsDataManagerImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/VoiceAgentEventNames.h + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/include/VoiceAgent.h + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/src/VoiceAgentImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/include/VoiceAgentEventsHandler.h + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/src/VoiceAgentEventsHandler.cpp + + # Core + ${CMAKE_CURRENT_SOURCE_DIR}/core/VRRequestProcessor.h + ${CMAKE_CURRENT_SOURCE_DIR}/core/VRRequestProcessorImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/core/include/VRAgentsObserver.h + ${CMAKE_CURRENT_SOURCE_DIR}/core/src/VRAgentsObserverImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/core/include/VRRequest.h + ${CMAKE_CURRENT_SOURCE_DIR}/core/src/VRRequestImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/core/include/VRRequestProcessorDelegate.h + ${CMAKE_CURRENT_SOURCE_DIR}/core/src/VRRequestProcessorDelegateImpl.cpp + + #Capabilities + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/CapabilitiesFactory.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/CapabilitiesFactory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/CapabilityMessagingService.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/CapabilityMessagingService.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/include/MessageChannel.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/src/MessageChannel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/include/PublisherForwarder.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/src/PublisherForwarder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/include/SubscriberForwarder.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/src/SubscriberForwarder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/communication/include/PhoneControlMessages.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/communication/include/PhoneControlCapability.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/communication/src/PhoneControlCapability.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/navigation/include/NavigationMessages.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/navigation/include/NavigationCapability.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/navigation/src/NavigationCapability.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/guimetadata/include/GuiMetadataMessages.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/guimetadata/include/GuiMetadataCapability.h + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/guimetadata/src/GuiMetadataCapability.cpp + + #Utilities + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/events/EventRouter.h + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/events/EventRouter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/logging/Logger.h + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/logging/Logger.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/uuid/UUIDGeneration.h + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/uuid/UUIDGeneration.cpp + ) + + # Define targets + ADD_LIBRARY(${TARGET_NAME} MODULE + ${VSHL_LIB_SRC} + ) + + # VSHL plugin properties + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + LABELS "PLUGIN" + PREFIX "" + SUFFIX ".ctlso" + OUTPUT_NAME ${TARGET_NAME} + ) + + # Define target includes + TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME} + PUBLIC ${GLIB_PKG_INCLUDE_DIRS} + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" + PRIVATE "${CMAKE_SOURCE_DIR}/app-controller/ctl-lib" + ) + + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + afb-helpers + ${GLIB_PKG_LIBRARIES} + ${link_libraries} + ) + + option(ENABLE_UNIT_TESTS "Build unit tests or not" OFF) + if (ENABLE_UNIT_TESTS) + execute_process( + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/agreement.sh" + RESULT_VARIABLE AGREEMENT_RESULT + ) + message(STATUS "Agreement Result: ${AGREEMENT_RESULT}") + if (${AGREEMENT_RESULT} MATCHES "1") + message(FATAL_ERROR "User agreement not accepted. Quitting") + endif() + + include(cmake/gtest.cmake) + + set(VSHL_TEST_SRC ${VSHL_LIB_SRC}) + list(APPEND VSHL_TEST_SRC + # Main + ${CMAKE_CURRENT_SOURCE_DIR}/TestMain.cpp + + # Test common + ${CMAKE_CURRENT_SOURCE_DIR}/test/common/ConsoleLogger.h + ${CMAKE_CURRENT_SOURCE_DIR}/test/common/ConsoleLogger.cpp + + # Test Mocks + ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/AFBApiMock.h + ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/AFBEventMock.h + ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/AFBRequestMock.h + ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/CapabilityMock.h + ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/VoiceAgentsChangeObserverMock.h + + # Capabilities + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/test/CapabilityMessagingServiceTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/test/PublisherForwarderTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/test/SubscriberForwarderTest.cpp + + # Core + ${CMAKE_CURRENT_SOURCE_DIR}/core/test/VRRequestTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/core/test/VRRequestProcessorTest.cpp + + # VoiceAgents + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/test/VoiceAgentTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/test/VoiceAgentsDataManagerTest.cpp + ) + + ADD_EXECUTABLE(${TARGET_NAME}_Test + ${VSHL_TEST_SRC} + ) + + TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME}_Test + PUBLIC ${GLIB_PKG_INCLUDE_DIRS} + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" + PRIVATE "${CMAKE_SOURCE_DIR}/app-controller/ctl-lib" + ) + + TARGET_LINK_LIBRARIES(${TARGET_NAME}_Test + afb-helpers + libgtest + libgmock + ${GLIB_PKG_LIBRARIES} + ${link_libraries} + ) + + ENABLE_TESTING() + ADD_TEST(VshlTest ${TARGET_NAME}_Test) + endif() \ No newline at end of file diff --git a/src/plugins/TestMain.cpp b/src/plugins/TestMain.cpp new file mode 100644 index 0000000..d4fcbec --- /dev/null +++ b/src/plugins/TestMain.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "gtest/gtest.h" + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/src/plugins/VshlApi.cpp b/src/plugins/VshlApi.cpp new file mode 100644 index 0000000..f2c7b7c --- /dev/null +++ b/src/plugins/VshlApi.cpp @@ -0,0 +1,607 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "VshlApi.h" + +#include + +#include "afb/AFBApiImpl.h" +#include "afb/AFBRequestImpl.h" +#include "capabilities/CapabilitiesFactory.h" +#include "capabilities/CapabilityMessagingService.h" +#include "core/VRRequestProcessor.h" +#include "utilities/events/EventRouter.h" +#include "utilities/logging/Logger.h" +#include "voiceagents/VoiceAgentEventNames.h" +#include "voiceagents/VoiceAgentsDataManager.h" + +#include "json.hpp" + +using namespace std; + +CTLP_CAPI_REGISTER("vshl-api"); + +static std::string TAG = "vshl::plugins::VshlAPI"; + +static std::string VA_JSON_ATTR_DEFAULT = "default"; +static std::string VA_JSON_ATTR_AGENTS = "agents"; +static std::string VA_JSON_ATTR_ID = "id"; +static std::string VA_JSON_ATTR_NAME = "name"; +static std::string VA_JSON_ATTR_API = "api"; +static std::string VA_JSON_ATTR_ACTIVE = "active"; +static std::string VA_JSON_ATTR_WWS = "wakewords"; +static std::string VA_JSON_ATTR_ACTIVE_WW = "activewakeword"; +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 EVENTS_JSON_ATTR_VA_ID = "va_id"; +static std::string EVENTS_JSON_ATTR_EVENTS = "events"; + +static std::string CAPABILITIES_JSON_ATTR_ACTION = "action"; +static std::string CAPABILITIES_JSON_ATTR_ACTIONS = "actions"; +static std::string CAPABILITIES_JSON_ATTR_PAYLOAD = "payload"; + +static std::shared_ptr sLogger; +static std::shared_ptr sAfbApi; +static std::unique_ptr sCapabilitiesFactory; +static std::unique_ptr sCapabilityMessagingService; +static std::unique_ptr sVRRequestProcessor; +static std::unique_ptr sVoiceAgentsDataManager; +static std::unique_ptr sEventRouter; + +using json = nlohmann::json; +using Level = vshl::utilities::logging::Logger::Level; + +CTLP_ONLOAD(plugin, ret) { + if (plugin->api == nullptr) { + return -1; + } + + // Logger + sLogger = vshl::utilities::logging::Logger::create(plugin->api); + // sLogger->log(Level::INFO, TAG, "Vshl plugin loaded & initialized."); + + // AFB Wrapper + sAfbApi = vshl::afb::AFBApiImpl::create(plugin->api); + + // VRRequestProcessor + auto vrRequestProcessorDelegate = vshl::core::VRRequestProcessorDelegate::create(sLogger, sAfbApi); + sVRRequestProcessor = vshl::core::VRRequestProcessor::create(sLogger, vrRequestProcessorDelegate); + if (!sVRRequestProcessor) { + sLogger->log(Level::ERROR, TAG, "Failed to create VRRequestProcessor"); + return -1; + } + + // VoiceAgentDataManager + sVoiceAgentsDataManager = vshl::voiceagents::VoiceAgentsDataManager::create(sLogger, sAfbApi); + if (!sVoiceAgentsDataManager) { + sLogger->log(Level::ERROR, TAG, "Failed to create VoiceAgentsDataManager"); + return -1; + } + sVoiceAgentsDataManager->addVoiceAgentsChangeObserver(sVRRequestProcessor->getVoiceAgentsChangeObserver()); + + // EventRouter + sEventRouter = vshl::utilities::events::EventRouter::create(sLogger); + if (!sEventRouter) { + sLogger->log(Level::ERROR, TAG, "Failed to create EventRouter"); + return -1; + } + sEventRouter->addEventFilter(sVoiceAgentsDataManager->getEventFilter()); + + sCapabilitiesFactory = vshl::capabilities::CapabilitiesFactory::create(); + if (!sCapabilitiesFactory) { + sLogger->log(Level::ERROR, TAG, "Failed to create CapabilitiesFactory"); + return -1; + } + + sCapabilityMessagingService = vshl::capabilities::CapabilityMessagingService::create(sLogger, sAfbApi); + if (!sCapabilityMessagingService) { + sLogger->log(Level::ERROR, TAG, "Failed to create CapabilityMessagingService"); + return -1; + } + + return 0; +} + +CTLP_CAPI(onAuthStateEvent, source, argsJ, eventJ) { + if (sEventRouter == nullptr) { + return -1; + } + + string eventName = vshl::voiceagents::VSHL_EVENT_AUTH_STATE_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, "onAuthStateEvent: No voiceagent id found."); + return -1; + } + std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get()); + + if (!sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ))) { + sLogger->log(Level::ERROR, TAG, "onAuthStateEvent: Failed to handle."); + return -1; + } + + return 0; +} + +CTLP_CAPI(onConnectionStateEvent, source, argsJ, eventJ) { + if (sEventRouter == nullptr) { + return -1; + } + + string eventName = vshl::voiceagents::VSHL_EVENT_CONNECTION_STATE_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, "onConnectionStateEvent: No voiceagent id found."); + return -1; + } + std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get()); + + if (!sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ))) { + sLogger->log(Level::ERROR, TAG, "onConnectionStateEvent: Failed to handle."); + return -1; + } + + return 0; +} + +CTLP_CAPI(onDialogStateEvent, source, argsJ, eventJ) { + if (sEventRouter == nullptr) { + return -1; + } + + string eventName = vshl::voiceagents::VSHL_EVENT_DIALOG_STATE_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, "onDialogStateEvent: No voiceagent id found."); + return -1; + } + std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get()); + + if (!sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ))) { + sLogger->log(Level::ERROR, TAG, "onDialogStateEvent: Failed to handle."); + return -1; + } + + return 0; +} + +CTLP_CAPI(loadVoiceAgentsConfig, source, argsJ, eventJ) { + if (sVoiceAgentsDataManager == nullptr) { + sLogger->log(Level::WARNING, TAG, "loadVoiceAgentsConfig: Voice service not initialized."); + return -1; + } + + if (argsJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "loadVoiceAgentsConfig: No arguments supplied."); + return -1; + } + + json agentsConfigJson = json::parse(json_object_to_json_string(argsJ)); + if (agentsConfigJson.find(VA_JSON_ATTR_AGENTS) == agentsConfigJson.end()) { + sLogger->log(Level::ERROR, TAG, "loadVoiceAgentsConfig: No agents object found in agents json"); + return -1; + } + + json agentsJson = agentsConfigJson[VA_JSON_ATTR_AGENTS]; + for (auto agentIt = agentsJson.begin(); agentIt != agentsJson.end(); ++agentIt) { + json agentJson = *agentIt; + + if (agentJson.find(VA_JSON_ATTR_ID) == agentJson.end() || + agentJson.find(VA_JSON_ATTR_ACTIVE) == agentJson.end() || + agentJson.find(VA_JSON_ATTR_NAME) == agentJson.end() || + agentJson.find(VA_JSON_ATTR_API) == agentJson.end() || + agentJson.find(VA_JSON_ATTR_WWS) == agentJson.end() || + agentJson.find(VA_JSON_ATTR_ACTIVE_WW) == agentJson.end() || + agentJson.find(VA_JSON_ATTR_DESCRIPTION) == agentJson.end() || + agentJson.find(VA_JSON_ATTR_VENDOR) == agentJson.end()) { + std::stringstream error; + error << "loadVoiceAgentsConfig: One or more missing params in agent " + "config " + << agentJson.dump(); + sLogger->log(Level::WARNING, TAG, error.str().c_str()); + continue; + } + + std::string id(agentJson[VA_JSON_ATTR_ID].get()); + std::string name(agentJson[VA_JSON_ATTR_NAME].get()); + std::string api(agentJson[VA_JSON_ATTR_API].get()); + std::string description(agentJson[VA_JSON_ATTR_DESCRIPTION].get()); + std::string vendor(agentJson[VA_JSON_ATTR_VENDOR].get()); + std::string activeWakeword(agentJson[VA_JSON_ATTR_ACTIVE_WW].get()); + bool isActive(agentJson[VA_JSON_ATTR_ACTIVE].get()); + + shared_ptr> wakewords = std::make_shared>(); + json wakewordsJson = agentJson[VA_JSON_ATTR_WWS]; + for (auto wwIt = wakewordsJson.begin(); wwIt != wakewordsJson.end(); ++wwIt) { + wakewords->insert(wwIt->get()); + } + + sVoiceAgentsDataManager->addNewVoiceAgent( + id, name, description, api, vendor, activeWakeword, isActive, wakewords); + } + + // Set the default agent. + if (agentsConfigJson.find(VA_JSON_ATTR_DEFAULT) == agentsConfigJson.end()) { + sLogger->log(Level::ERROR, TAG, "loadVoiceAgentsConfig: No default agent found in agents json"); + return -1; + } + std::string defaultAgentId(agentsConfigJson[VA_JSON_ATTR_DEFAULT].get()); + sVoiceAgentsDataManager->setDefaultVoiceAgent(defaultAgentId); + + return 0; +} + +CTLP_CAPI(startListening, source, argsJ, eventJ) { + if (sVRRequestProcessor == nullptr) { + return -1; + } + + int result = 0; + string requestId = sVRRequestProcessor->startListening(); + + if (!requestId.empty()) { + json responseJson; + responseJson[STARTLISTENING_JSON_ATTR_REQUEST] = requestId; + AFB_ReqSuccess(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL); + } else { + AFB_ReqFail(source->request, NULL, "Failed to startListening..."); + } + + return 0; +} + +CTLP_CAPI(cancelListening, source, argsJ, eventJ) { + return 0; +} + +CTLP_CAPI(enumerateVoiceAgents, source, argsJ, eventJ) { + if (sVoiceAgentsDataManager == nullptr) { + return -1; + } + + auto agents = sVoiceAgentsDataManager->getAllVoiceAgents(); + std::string defaultAgentId(sVoiceAgentsDataManager->getDefaultVoiceAgent()); + + json responseJson; + json agentsJson = json::array(); + + for (auto agent : agents) { + json agentJson; + agentJson[VA_JSON_ATTR_ID] = agent->getId(); + agentJson[VA_JSON_ATTR_NAME] = agent->getName(); + agentJson[VA_JSON_ATTR_DESCRIPTION] = agent->getDescription(); + agentJson[VA_JSON_ATTR_API] = agent->getApi(); + agentJson[VA_JSON_ATTR_VENDOR] = agent->getVendor(); + agentJson[VA_JSON_ATTR_ACTIVE] = agent->isActive(); + agentJson[VA_JSON_ATTR_ACTIVE_WW] = agent->getActiveWakeword(); + + auto wakewords = agent->getWakeWords(); + if (wakewords != nullptr) { + json wakewordsJson; + for (auto wakeword : *wakewords) { + wakewordsJson.push_back(wakeword); + } + agentJson[VA_JSON_ATTR_WWS] = wakewordsJson; + } + + agentsJson.push_back(agentJson); + } + + responseJson[VA_JSON_ATTR_AGENTS] = agentsJson; + responseJson[VA_JSON_ATTR_DEFAULT] = defaultAgentId; + + AFB_ReqSuccess(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL); + + return 0; +} + +CTLP_CAPI(subscribe, source, argsJ, eventJ) { + if (sVoiceAgentsDataManager == nullptr) { + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "subscribe: No arguments supplied."); + 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, "subscribe: No voiceagent id found in subscribe json"); + return -1; + } + std::string voiceAgentId(subscribeJson[EVENTS_JSON_ATTR_VA_ID].get()); + + if (subscribeJson.find(EVENTS_JSON_ATTR_EVENTS) == subscribeJson.end()) { + sLogger->log(Level::ERROR, TAG, "subscribe: No events array found in subscribe json"); + return -1; + } + list events(subscribeJson[EVENTS_JSON_ATTR_EVENTS].get>()); + + // Subscribe this client for the listed events. + auto request = vshl::afb::AFBRequestImpl::create(source->request); + for (auto event : events) { + if (!sVoiceAgentsDataManager->subscribeToVshlEventFromVoiceAgent(*request, event, voiceAgentId)) { + sLogger->log(Level::ERROR, TAG, "subscribe: Failed to subscribe to event: " + event); + return -1; + } + } + + AFB_ReqSuccess(source->request, json_object_new_string("Subscription to events successfully completed."), NULL); + + return 0; +} + +CTLP_CAPI(setDefaultVoiceAgent, source, argsJ, eventJ) { + if (sVoiceAgentsDataManager == nullptr) { + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "setDefaultVoiceAgent: No arguments supplied."); + return -1; + } + + json jsonRequest = json::parse(json_object_to_json_string(eventJ)); + if (jsonRequest.find(VA_JSON_ATTR_ID) == jsonRequest.end()) { + sLogger->log(Level::ERROR, TAG, "setDefaultVoiceAgent: voice agent id not found in request json"); + return -1; + } + + std::string voiceAgentId(jsonRequest[VA_JSON_ATTR_ID].get()); + if (!sVoiceAgentsDataManager->setDefaultVoiceAgent(voiceAgentId)) { + sLogger->log(Level::ERROR, TAG, "setDefaultVoiceAgent: Failed to set default agent"); + return -1; + } + + AFB_ReqSuccess(source->request, NULL, NULL); + return 0; +} + +CTLP_CAPI(guiMetadataSubscribe, source, argsJ, eventJ) { + if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) { + return -1; + } + + shared_ptr guMetadataCapability = sCapabilitiesFactory->getGuiMetadata(); + if (!guMetadataCapability) { + sLogger->log( + Level::WARNING, + TAG, + "guimetadataSubscribe: Failed to " + "fetch guimetadata capability " + "object."); + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "guimetadataSubscribe: No arguments supplied."); + return -1; + } + + json subscribeJson = json::parse(json_object_to_json_string(eventJ)); + if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) { + sLogger->log(Level::ERROR, TAG, "guimetadataSubscribe: No events array found in subscribe json"); + return -1; + } + list events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get>()); + + // SUbscribe this client for the guimetadata events. + auto request = vshl::afb::AFBRequestImpl::create(source->request); + for (auto event : events) { + if (!sCapabilityMessagingService->subscribe(*request, guMetadataCapability, event)) { + sLogger->log(Level::ERROR, TAG, "guimetadataSubscribe: Failed to subscribe to event: " + event); + return -1; + } + } + + AFB_ReqSuccess( + source->request, json_object_new_string("Subscription to guimetadata events successfully completed."), NULL); + return 0; +} + +CTLP_CAPI(guiMetadataPublish, source, argsJ, eventJ) { + if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) { + return -1; + } + + shared_ptr guMetadataCapability = sCapabilitiesFactory->getGuiMetadata(); + if (!guMetadataCapability) { + sLogger->log( + Level::WARNING, + TAG, + "guimetadataPublish: Failed to fetch " + "guimetadata capability object."); + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "guimetadataPublish: No arguments supplied."); + return -1; + } + + json publishJson = json::parse(json_object_to_json_string(eventJ)); + if (publishJson.find(CAPABILITIES_JSON_ATTR_ACTION) == publishJson.end()) { + sLogger->log(Level::ERROR, TAG, "guimetadataPublish: No action found in publish json"); + return -1; + } + std::string action(publishJson[CAPABILITIES_JSON_ATTR_ACTION].get()); + + if (publishJson.find(CAPABILITIES_JSON_ATTR_PAYLOAD) == publishJson.end()) { + sLogger->log(Level::ERROR, TAG, "guimetadataPublish: No payload found in publish json"); + return -1; + } + std::string payload(publishJson[CAPABILITIES_JSON_ATTR_PAYLOAD].get()); + + if (!sCapabilityMessagingService->publish(guMetadataCapability, action, payload)) { + sLogger->log(Level::ERROR, TAG, "guimetadataPublish: Failed to publish message: " + action); + return -1; + } + + AFB_ReqSuccess(source->request, json_object_new_string("Successfully published guimetadata messages."), NULL); + return 0; +} + +CTLP_CAPI(phonecontrolSubscribe, source, argsJ, eventJ) { + if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) { + return -1; + } + + shared_ptr phoneControlCapability = sCapabilitiesFactory->getPhoneControl(); + if (!phoneControlCapability) { + sLogger->log(Level::WARNING, TAG, "phoneControlSubscribe: Failed to fetch phone control capability object."); + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "phoneControlSubscribe: No arguments supplied."); + return -1; + } + + json subscribeJson = json::parse(json_object_to_json_string(eventJ)); + if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) { + sLogger->log(Level::ERROR, TAG, "phoneControlSubscribe: No events array found in subscribe json"); + return -1; + } + list events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get>()); + + // SUbscribe this client for the phone call control events. + auto request = vshl::afb::AFBRequestImpl::create(source->request); + for (auto event : events) { + if (!sCapabilityMessagingService->subscribe(*request, phoneControlCapability, event)) { + sLogger->log(Level::ERROR, TAG, "phoneControlSubscribe: Failed to subscribe to event: " + event); + return -1; + } + } + + AFB_ReqSuccess( + source->request, json_object_new_string("Subscription to phone control events successfully completed."), NULL); + return 0; +} + +CTLP_CAPI(phonecontrolPublish, source, argsJ, eventJ) { + if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) { + return -1; + } + + shared_ptr phoneControlCapability = sCapabilitiesFactory->getPhoneControl(); + if (!phoneControlCapability) { + sLogger->log(Level::WARNING, TAG, "phoneControlPublish: Failed to fetch navigation capability object."); + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "phoneControlPublish: No arguments supplied."); + return -1; + } + + json publishJson = json::parse(json_object_to_json_string(eventJ)); + if (publishJson.find(CAPABILITIES_JSON_ATTR_ACTION) == publishJson.end()) { + sLogger->log(Level::ERROR, TAG, "phoneControlPublish: No action found in publish json"); + return -1; + } + std::string action(publishJson[CAPABILITIES_JSON_ATTR_ACTION].get()); + + if (publishJson.find(CAPABILITIES_JSON_ATTR_PAYLOAD) == publishJson.end()) { + sLogger->log(Level::ERROR, TAG, "phoneControlPublish: No payload found in publish json"); + return -1; + } + std::string payload(publishJson[CAPABILITIES_JSON_ATTR_PAYLOAD].get()); + + if (!sCapabilityMessagingService->publish(phoneControlCapability, action, payload)) { + sLogger->log(Level::ERROR, TAG, "phoneControlPublish: Failed to publish message: " + action); + return -1; + } + + AFB_ReqSuccess(source->request, json_object_new_string("Successfully published phone control messages."), NULL); + return 0; +} + +CTLP_CAPI(navigationSubscribe, source, argsJ, eventJ) { + if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) { + return -1; + } + + shared_ptr navigationCapability = sCapabilitiesFactory->getNavigation(); + if (!navigationCapability) { + sLogger->log(Level::WARNING, TAG, "navigationSubscribe: Failed to fetch navigation capability object."); + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "navigationSubscribe: No arguments supplied."); + return -1; + } + + json subscribeJson = json::parse(json_object_to_json_string(eventJ)); + if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) { + sLogger->log(Level::ERROR, TAG, "navigationSubscribe: No events array found in subscribe json"); + return -1; + } + list events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get>()); + + // SUbscribe this client for the navigation events. + auto request = vshl::afb::AFBRequestImpl::create(source->request); + for (auto event : events) { + if (!sCapabilityMessagingService->subscribe(*request, navigationCapability, event)) { + sLogger->log(Level::ERROR, TAG, "navigationSubscribe: Failed to subscribe to event: " + event); + return -1; + } + } + + AFB_ReqSuccess( + source->request, json_object_new_string("Subscription to navigation events successfully completed."), NULL); + return 0; +} + +CTLP_CAPI(navigationPublish, source, argsJ, eventJ) { + if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) { + return -1; + } + + shared_ptr navigationCapability = sCapabilitiesFactory->getNavigation(); + if (!navigationCapability) { + sLogger->log(Level::WARNING, TAG, "navigationPublish: Failed to fetch navigation capability object."); + return -1; + } + + if (eventJ == nullptr) { + sLogger->log(Level::WARNING, TAG, "navigationPublish: No arguments supplied."); + return -1; + } + + json publishJson = json::parse(json_object_to_json_string(eventJ)); + if (publishJson.find(CAPABILITIES_JSON_ATTR_ACTION) == publishJson.end()) { + sLogger->log(Level::ERROR, TAG, "navigationPublish: No action found in publish json"); + return -1; + } + std::string action(publishJson[CAPABILITIES_JSON_ATTR_ACTION].get()); + + if (publishJson.find(CAPABILITIES_JSON_ATTR_PAYLOAD) == publishJson.end()) { + sLogger->log(Level::ERROR, TAG, "navigationPublish: No payload found in publish json"); + return -1; + } + std::string payload(publishJson[CAPABILITIES_JSON_ATTR_PAYLOAD].get()); + + if (!sCapabilityMessagingService->publish(navigationCapability, action, payload)) { + sLogger->log(Level::ERROR, TAG, "navigationPublish: Failed to publish message: " + action); + return -1; + } + + AFB_ReqSuccess(source->request, json_object_new_string("Successfully published navigation messages."), NULL); + return 0; +} diff --git a/src/plugins/VshlApi.h b/src/plugins/VshlApi.h new file mode 100644 index 0000000..f228943 --- /dev/null +++ b/src/plugins/VshlApi.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_API_INCLUDE +#define VSHL_API_INCLUDE + +#include "ctl-plugin.h" + +#ifdef __cplusplus +extern "C" { +#endif + +CTLP_ONLOAD(plugin, ret); +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 loadVoiceAgentsConfig(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 guiMetadataSubscribe(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int guiMetadataPublish(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int phonecontrolSubscribe(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int phonecontrolPublish(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int navigationSubscribe(CtlSourceT* source, json_object* argsJ, json_object* queryJ); +int navigationPublish(CtlSourceT* source, json_object* argsJ, json_object* queryJ); + +#ifdef __cplusplus +} +#endif + +#endif // VSHL_API_INCLUDE diff --git a/src/plugins/afb/AFBApiImpl.cpp b/src/plugins/afb/AFBApiImpl.cpp new file mode 100644 index 0000000..88d1e7e --- /dev/null +++ b/src/plugins/afb/AFBApiImpl.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "afb/AFBApiImpl.h" + +#include "afb/include/AFBEventImpl.h" +#include "utilities/logging/Logger.h" + +extern "C" { +#define AFB_BINDING_VERSION 3 +#define FREEIF(x) \ + if (!x) { \ + free(x); \ + } +#define BREAKIF(x) \ + if (x) { \ + result = false; \ + break; \ + } + +#include "afb-definitions.h" +} + +static std::string TAG = "vshl::afb::AFBApiImpl"; + +/** + * Specifies the severity level of a log message + */ +using Level = vshl::common::interfaces::ILogger::Level; +using namespace vshl::common::interfaces; +using namespace vshl::utilities::logging; + +namespace vshl { +namespace afb { + +std::unique_ptr AFBApiImpl::create(AFB_ApiT api) { + return std::unique_ptr(new AFBApiImpl(api)); +} + +AFBApiImpl::AFBApiImpl(AFB_ApiT api) : mApi(api), mLogger(Logger::create(api)) { +} + +AFBApiImpl::~AFBApiImpl() { +} + +std::shared_ptr AFBApiImpl::createEvent(const std::string& eventName) { + return AFBEventImpl::create(mLogger, mApi, eventName); +} + +int AFBApiImpl::callSync( + const std::string& api, + const std::string& verb, + struct json_object* request, + struct json_object** result, + std::string& error, + std::string& info) { + char* errorStr = NULL; + char* infoStr = NULL; + int rc = AFB_ApiSync(mApi, api.c_str(), verb.c_str(), request, result, &errorStr, &infoStr); + + if (errorStr) { + error = errorStr; + free(errorStr); + } + + if (infoStr) { + info = infoStr; + free(infoStr); + } + + return rc; +} + +} // namespace afb +} // namespace vshl diff --git a/src/plugins/afb/AFBApiImpl.h b/src/plugins/afb/AFBApiImpl.h new file mode 100644 index 0000000..74aa7ef --- /dev/null +++ b/src/plugins/afb/AFBApiImpl.h @@ -0,0 +1,61 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_AFB_AFBAPIIMPL_H_ +#define VSHL_AFB_AFBAPIIMPL_H_ + +#include + +extern "C" { +#include "ctl-plugin.h" +} + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/logging/ILogger.h" + +using namespace std; + +namespace vshl { +namespace afb { + +class AFBApiImpl : public vshl::common::interfaces::IAFBApi { +public: + static std::unique_ptr create(AFB_ApiT api); + + ~AFBApiImpl(); + + std::shared_ptr createEvent(const std::string& eventName) override; + + int callSync( + const std::string& api, + const std::string& verb, + struct json_object* request, + struct json_object** result, + std::string& error, + std::string& info) override; + +private: + AFBApiImpl(AFB_ApiT api); + + // AFB API Binding + AFB_ApiT mApi; + + // Logger + std::shared_ptr mLogger; +}; + +} // namespace afb +} // namespace vshl + +#endif // VSHL_AFB_AFBAPIIMPL_H_ diff --git a/src/plugins/afb/AFBRequestImpl.cpp b/src/plugins/afb/AFBRequestImpl.cpp new file mode 100644 index 0000000..8ec5691 --- /dev/null +++ b/src/plugins/afb/AFBRequestImpl.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "afb/AFBRequestImpl.h" + +extern "C" { +#include "afb-definitions.h" +} + +namespace vshl { +namespace afb { + +std::unique_ptr AFBRequestImpl::create(AFB_ReqT afbRequest) { + return std::unique_ptr(new AFBRequestImpl(afbRequest)); +} + +AFBRequestImpl::AFBRequestImpl(AFB_ReqT afbRequest) : mAfbRequest(afbRequest) { +} + +void* AFBRequestImpl::getNativeRequest() { + return mAfbRequest; +} + +} // namespace afb +} // namespace vshl diff --git a/src/plugins/afb/AFBRequestImpl.h b/src/plugins/afb/AFBRequestImpl.h new file mode 100644 index 0000000..2e6f3ab --- /dev/null +++ b/src/plugins/afb/AFBRequestImpl.h @@ -0,0 +1,49 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_AFB_AFBREQUESTIMPL_H_ +#define VSHL_AFB_AFBREQUESTIMPL_H_ + +#include + +extern "C" { +#include "ctl-plugin.h" +} + +#include "interfaces/afb/IAFBApi.h" + +namespace vshl { +namespace afb { + +/** + * AFB Request impl + */ +class AFBRequestImpl : public vshl::common::interfaces::IAFBRequest { +public: + static std::unique_ptr create(AFB_ReqT afbRequest); + + // {@c IAFBRequest Implementation + void *getNativeRequest() override; + // @c IAFBRequest Implementation } + +private: + AFBRequestImpl(AFB_ReqT afbRequest); + + AFB_ReqT mAfbRequest; +}; + +} // namespace afb +} // namespace vshl + +#endif // VSHL_AFB_AFBREQUESTIMPL_H_ diff --git a/src/plugins/afb/include/AFBEventImpl.h b/src/plugins/afb/include/AFBEventImpl.h new file mode 100644 index 0000000..45f85f9 --- /dev/null +++ b/src/plugins/afb/include/AFBEventImpl.h @@ -0,0 +1,77 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_AFB_EVENT_H_ +#define VSHL_AFB_EVENT_H_ + +#include +#include + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/logging/ILogger.h" + +extern "C" { +#include "ctl-plugin.h" +#include +} + +using namespace std; + +namespace vshl { +namespace afb { +/* + * This class encapsulates AFB Event. + */ +class AFBEventImpl : public vshl::common::interfaces::IAFBApi::IAFBEvent { +public: + static unique_ptr + create(shared_ptr logger, AFB_ApiT api, + const string &eventName); + + // Destructor + ~AFBEventImpl(); + + /// { @c IAFBEvent implementation + string getName() const override; + bool isValid() override; + int publishEvent(struct json_object *payload) override; + bool subscribe(vshl::common::interfaces::IAFBRequest &request) override; + bool unsubscribe(vshl::common::interfaces::IAFBRequest &request) override; + /// @c IAFBEvent implementation } + +private: + AFBEventImpl(shared_ptr logger, + AFB_ApiT api, const string &eventName); + + // Make the event. This is a lazy make that happens + // usually during the subscribe stage. + void makeEventIfNeccessary(); + + // Binding API reference + AFB_ApiT mAfbApi; + + // AFB Event + afb_event_t mAfbEvent; + + // Event Name + string mEventName; + + // Logger + shared_ptr mLogger; +}; + +} // namespace afb +} // namespace vshl + +#endif // VSHL_AFB_EVENT_H_ diff --git a/src/plugins/afb/src/AFBEventImpl.cpp b/src/plugins/afb/src/AFBEventImpl.cpp new file mode 100644 index 0000000..e3c902d --- /dev/null +++ b/src/plugins/afb/src/AFBEventImpl.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "afb/include/AFBEventImpl.h" + +static string TAG = "vshl::afb::Event"; + +using Level = vshl::common::interfaces::ILogger::Level; +using namespace vshl::common::interfaces; + +namespace vshl { +namespace afb { + +unique_ptr AFBEventImpl::create( + shared_ptr logger, + AFB_ApiT api, + const string& eventName) { + return unique_ptr(new AFBEventImpl(logger, api, eventName)); +} + +AFBEventImpl::AFBEventImpl( + shared_ptr logger, + AFB_ApiT api, + const string& eventName) : + mLogger(logger), + mAfbApi(api), + mEventName(eventName), + mAfbEvent(nullptr) { +} + +AFBEventImpl::~AFBEventImpl() { +} + +string AFBEventImpl::getName() const { + return mEventName; +} + +bool AFBEventImpl::isValid() { + makeEventIfNeccessary(); + return afb_event_is_valid(mAfbEvent) == 1 ? true : false; +} + +bool AFBEventImpl::subscribe(IAFBRequest& requestInterface) { + makeEventIfNeccessary(); + auto request = static_cast(requestInterface.getNativeRequest()); + if (isValid() && afb_req_subscribe(request, mAfbEvent) == 0) { + return true; + } + + return false; +} + +bool AFBEventImpl::unsubscribe(IAFBRequest& requestInterface) { + makeEventIfNeccessary(); + auto request = static_cast(requestInterface.getNativeRequest()); + if (isValid() && afb_req_unsubscribe(request, mAfbEvent) == 0) { + return true; + } + + return false; +} + +int AFBEventImpl::publishEvent(struct json_object* payload) { + makeEventIfNeccessary(); + return afb_event_push(mAfbEvent, payload); +} + +void AFBEventImpl::makeEventIfNeccessary() { + if (mAfbEvent) { + return; + } + + mLogger->log(Level::NOTICE, TAG, "Creating VSHL event: " + mEventName); + mAfbEvent = afb_api_make_event(mAfbApi, mEventName.c_str()); +} + +} // namespace afb +} // namespace vshl diff --git a/src/plugins/agreement.sh b/src/plugins/agreement.sh new file mode 100755 index 0000000..2a8eb71 --- /dev/null +++ b/src/plugins/agreement.sh @@ -0,0 +1,43 @@ +#******************************************************************************** +# Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# A copy of the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file 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. +#********************************************************************************* + +agreement_check() { + cat << EOF + +******************************************************************************* +The scripts provided herein will retrieve several third-party libraries, +environments, and/or other software packages at build-time +("External Dependencies") from third-party sources. These are terms and +conditions that you need to agree to abide by if you choose to build the +External Dependencies. Licenses for the External Dependencies may be found at +README.md. If you do not agree with every term and condition +associated with the External Dependencies, enter “QUIT” in the command line +when prompted by the script. +******************************************************************************* + +EOF + + answer="dummy" + while [ ! -z $answer ]; do + read -p "Type \"QUIT\" to exit the script now, press ENTER to continue: " -r answer + if [ "$answer" = "QUIT" ]; then + exit 1 + fi + echo "" + done +} + +agreement_check +exit 0 \ No newline at end of file diff --git a/src/plugins/capabilities/CapabilitiesFactory.cpp b/src/plugins/capabilities/CapabilitiesFactory.cpp new file mode 100644 index 0000000..2f92519 --- /dev/null +++ b/src/plugins/capabilities/CapabilitiesFactory.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/CapabilitiesFactory.h" + +#include "capabilities/communication/include/PhoneControlCapability.h" +#include "capabilities/guimetadata/include/GuiMetadataCapability.h" +#include "capabilities/navigation/include/NavigationCapability.h" + +static string TAG = "vshl::core::CapabilitiesFactory"; + +using Level = vshl::utilities::logging::Logger::Level; + +namespace vshl { +namespace capabilities { + +// Create CapabilitiesFactory +std::unique_ptr CapabilitiesFactory::create() { + auto capabilitiesFactory = std::unique_ptr(new CapabilitiesFactory()); + return capabilitiesFactory; +} + +std::shared_ptr CapabilitiesFactory::getGuiMetadata() { + if (!mGuiMetadata) { + mGuiMetadata = vshl::capabilities::guimetadata::GuiMetadata::create(); + } + return mGuiMetadata; +} + +std::shared_ptr CapabilitiesFactory::getPhoneControl() { + if (!mPhoneControl) { + mPhoneControl = vshl::capabilities::phonecontrol::PhoneControl::create(); + } + return mPhoneControl; +} + +std::shared_ptr CapabilitiesFactory::getNavigation() { + if (!mNavigation) { + mNavigation = vshl::capabilities::navigation::Navigation::create(); + } + return mNavigation; +} + +} // namespace capabilities +} // namespace vshl \ No newline at end of file diff --git a/src/plugins/capabilities/CapabilitiesFactory.h b/src/plugins/capabilities/CapabilitiesFactory.h new file mode 100644 index 0000000..b73909b --- /dev/null +++ b/src/plugins/capabilities/CapabilitiesFactory.h @@ -0,0 +1,63 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CAPABILITIES_CAPABILITIESFACTORY_H_ +#define VSHL_CAPABILITIES_CAPABILITIESFACTORY_H_ + +#include + +#include "interfaces/capabilities/ICapability.h" +#include "utilities/logging/Logger.h" + +using namespace std; + +namespace vshl { +namespace capabilities { +/* + * Factory for creating different capability objects. + */ +class CapabilitiesFactory { +public: + // Create CapabilitiesFactory + static std::unique_ptr create(); + + // GUI Metadata capability + std::shared_ptr getGuiMetadata(); + + // Phone call control capability + std::shared_ptr getPhoneControl(); + + // Navigation capability + std::shared_ptr getNavigation(); + + // Destructor + ~CapabilitiesFactory() = default; + +private: + // Constructor + CapabilitiesFactory() = default; + + // Capabilities + shared_ptr mGuiMetadata; + shared_ptr mPhoneControl; + shared_ptr mNavigation; + + // Logger + unique_ptr mLogger; +}; + +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_CAPABILITIESFACTORY_H_ diff --git a/src/plugins/capabilities/CapabilityMessagingService.cpp b/src/plugins/capabilities/CapabilityMessagingService.cpp new file mode 100644 index 0000000..91b5f2b --- /dev/null +++ b/src/plugins/capabilities/CapabilityMessagingService.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/CapabilityMessagingService.h" + +#include "capabilities/core/include/PublisherForwarder.h" +#include "capabilities/core/include/SubscriberForwarder.h" + +static string TAG = "vshl::capabilities::CapabilityMessagingService"; + +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace capabilities { + +// Create a CapabilityMessagingService. +unique_ptr CapabilityMessagingService::create( + shared_ptr logger, + shared_ptr afbApi) { + if (logger == nullptr) { + return nullptr; + } + + if (afbApi == nullptr) { + logger->log(Level::ERROR, TAG, "Failed to create CapabilityMessagingService: AFB API null"); + return nullptr; + } + + auto capabilityMessageService = + std::unique_ptr(new CapabilityMessagingService(logger, afbApi)); + return capabilityMessageService; +} + +CapabilityMessagingService::~CapabilityMessagingService() { + mMessageChannelsMap.clear(); +} + +CapabilityMessagingService::CapabilityMessagingService( + shared_ptr logger, + shared_ptr afbApi) : + mAfbApi(afbApi), + mLogger(logger) { +} + +// Subscribe to capability specific messages. +bool CapabilityMessagingService::subscribe( + vshl::common::interfaces::IAFBRequest& request, + shared_ptr capability, + const string action) { + auto capabilityName = capability->getName(); + + if (capabilityName.empty()) { + mLogger->log(Level::ERROR, TAG, "Failed to subscribe to message. Invalid input."); + return false; + } + + auto messageChannel = getMessageChannel(capability); + return messageChannel->subscribe(request, action); +} + +// Publish capability messages. +bool CapabilityMessagingService::publish( + shared_ptr capability, + const string action, + const string payload) { + auto capabilityName = capability->getName(); + + if (capabilityName.empty()) { + mLogger->log(Level::ERROR, TAG, "Failed to publish message. Invalid input."); + return false; + } + + auto messageChannelIt = mMessageChannelsMap.find(capabilityName); + if (messageChannelIt == mMessageChannelsMap.end()) { + mLogger->log( + Level::ERROR, + TAG, + "Failed to publish message. Message channel doesn't exist for capability " + capabilityName); + return false; + } + + return messageChannelIt->second->publish(action, payload); +} + +shared_ptr CapabilityMessagingService::getMessageChannel( + shared_ptr capability) { + auto capabilityName = capability->getName(); + + if (capabilityName.empty()) { + mLogger->log(Level::ERROR, TAG, "Failed to create message channel. Invalid input."); + return nullptr; + } + + auto messageChannelIt = mMessageChannelsMap.find(capabilityName); + if (messageChannelIt == mMessageChannelsMap.end()) { + mLogger->log(Level::INFO, TAG, "Creating new message channel for capability: " + capabilityName); + auto messageChannel = vshl::capabilities::core::MessageChannel::create(mLogger, mAfbApi, capability); + mMessageChannelsMap.insert(make_pair(capabilityName, messageChannel)); + return messageChannel; + } + + return messageChannelIt->second; +} + +} // namespace capabilities +} // namespace vshl diff --git a/src/plugins/capabilities/CapabilityMessagingService.h b/src/plugins/capabilities/CapabilityMessagingService.h new file mode 100644 index 0000000..535e806 --- /dev/null +++ b/src/plugins/capabilities/CapabilityMessagingService.h @@ -0,0 +1,82 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CAPABILITIES_CAPABILITYMESSAGINGSERVICE_H_ +#define VSHL_CAPABILITIES_CAPABILITYMESSAGINGSERVICE_H_ + +#include +#include +#include + +#include "capabilities/core/include/MessageChannel.h" +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/capabilities/ICapability.h" +#include "interfaces/utilities/logging/ILogger.h" + +using namespace std; + +namespace vshl { +namespace capabilities { +/* + * This hosts service APIs that clients can use to subscribe and + * forward capability messages. Each capability has a name and + * direction (upstream/downstream). Upstream messages are from + * voiceagents to Apps and downstream messages are Apps to voiceagents. + * This class will use a factory to create publisher and subcribers for + * each capability and create assiociations between them. + */ +class CapabilityMessagingService { +public: + // Create a CapabilityMessagingService. + static std::unique_ptr + create(shared_ptr logger, + shared_ptr afbApi); + + // Subscribe to capability specific messages. + bool subscribe(vshl::common::interfaces::IAFBRequest &request, + shared_ptr capability, + const string action); + + // Publish capability messages. + bool publish(shared_ptr capability, + const string action, const string payload); + + // Destructor + ~CapabilityMessagingService(); + +private: + // Constructor + CapabilityMessagingService( + shared_ptr logger, + shared_ptr afbApi); + + // Binding API reference + shared_ptr mAfbApi; + + // Create a message channel for the capability. + shared_ptr + getMessageChannel(shared_ptr capability); + + // Map of capabilities to message channels. + unordered_map> + mMessageChannelsMap; + + // Logger + shared_ptr mLogger; +}; + +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_CAPABILITYMESSAGINGSERVICE_H_ diff --git a/src/plugins/capabilities/communication/include/PhoneControlCapability.h b/src/plugins/capabilities/communication/include/PhoneControlCapability.h new file mode 100644 index 0000000..55ada8d --- /dev/null +++ b/src/plugins/capabilities/communication/include/PhoneControlCapability.h @@ -0,0 +1,52 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +#ifndef VSHL_CAPABILITIES_PHONECONTROL_CAPABILITY_H_ +#define VSHL_CAPABILITIES_PHONECONTROL_CAPABILITY_H_ + +#include + +#include "interfaces/capabilities/ICapability.h" + +namespace vshl { +namespace capabilities { +namespace phonecontrol { + +/* + * PhoneControl capability. Calls are initiated in the endpoint. + */ +class PhoneControl : public common::interfaces::ICapability { +public: + // Create a PhoneControl. + static std::shared_ptr create(); + + ~PhoneControl() = default; + +protected: + string getName() const override; + + list getUpstreamMessages() const override; + + list getDownstreamMessages() const override; + +private: + PhoneControl() = default; +}; + +} // namespace phonecontrol +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_PHONECONTROL_CAPABILITY_H_ diff --git a/src/plugins/capabilities/communication/include/PhoneControlMessages.h b/src/plugins/capabilities/communication/include/PhoneControlMessages.h new file mode 100644 index 0000000..4c68455 --- /dev/null +++ b/src/plugins/capabilities/communication/include/PhoneControlMessages.h @@ -0,0 +1,128 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CAPABILITIES_PHONECONTROL_MESSAGES_H_ +#define VSHL_CAPABILITIES_PHONECONTROL_MESSAGES_H_ + +#include +#include + +using namespace std; + +namespace vshl { +namespace capabilities { +namespace phonecontrol { + +static string NAME = "phonecontrol"; + +// Supported actions from VA -> Apps +/* Dial message sent from VA to app handling the calling. + * + * Payload + * { + * "callId": "{{STRING}}", + * "callee": { + * "details": "{{STRING}}", + * "defaultAddress": { + * "protocol": "{{STRING}}", + * "format": "{{STRING}}", + * "value": "{{STRING}}" + * }, + * "alternativeAddresses": [{ + * "protocol": "{{STRING}}", + * "format": "{{STRING}}", + * "value": {{STRING}} + * }] + * } + * } + * } + * + * callId (required): A unique identifier for the call + * callee (required): The destination of the outgoing call + * callee.details (optional): Descriptive information about the callee + * callee.defaultAddress (required): The default address to use for calling the callee + * callee.alternativeAddresses (optional): An array of alternate addresses for the existing callee + * address.protocol (required): The protocol for this address of the callee (e.g. PSTN, SIP, H323, etc.) + * address.format (optional): The format for this address of the callee (e.g. E.164, E.163, E.123, DIN5008, etc.) + * address.value (required): The address of the callee. + * + */ +static string PHONECONTROL_DIAL = "dial"; + +// Supported actions from Apps -> VA +/* + * App notifies the voiceagents of a change in connection state of a calling device. + * + * Payload + * { + * "state" : "{{STRING}}" // CONNECTED or DISCONNECTED + * } + */ +static string PHONECONTROL_CONNECTIONSTATE_CHANGED = "connection_state_changed"; +/* + * App notifies the voiceagents that call is activated + * + * callId must match the one that is sent by VA with DIAL message above. + * + * Payload + * { + * "callId" : "{{STRING}}" + * } + */ +static string PHONECONTROL_CALL_ACTIVATED = "call_activated"; +/* + * App notifies the voiceagents of an error in initiating or maintaining a + * call on a calling device + * + * callId must match the one that is sent by VA with DIAL message above. + * error: below status codes. + * 4xx: Validation failure for the input from the DIAL message + * 500: Internal error on the platform unrelated to the cellular network + * 503: Error on the platform related to the cellular network + * + * Payload + * { + * "callId" : "{{STRING}}" + * "error" : "{{STRING}}" + * } + */ +static string PHONECONTROL_CALL_FAILED = "call_failed"; +/* + * App notifies the voiceagents that call is terminated + * + * callId must match the one that is sent by VA with DIAL message above. + * + * Payload + * { + * "callId" : "{{STRING}}" + * } + */ +static string PHONECONTROL_CALL_TERMINATED = "call_terminated"; + +// List of actions that are delivered from VA -> Apps +static list PHONECONTROL_UPSTREAM_ACTIONS = { + PHONECONTROL_DIAL, +}; + +// List of actions that are delivered from Apps -> VA +static list PHONECONTROL_DOWNSTREAM_ACTIONS = {PHONECONTROL_CONNECTIONSTATE_CHANGED, + PHONECONTROL_CALL_ACTIVATED, + PHONECONTROL_CALL_FAILED, + PHONECONTROL_CALL_TERMINATED}; + +} // namespace phonecontrol +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_PHONECONTROL_MESSAGES_H_ diff --git a/src/plugins/capabilities/communication/src/PhoneControlCapability.cpp b/src/plugins/capabilities/communication/src/PhoneControlCapability.cpp new file mode 100644 index 0000000..6a74d5a --- /dev/null +++ b/src/plugins/capabilities/communication/src/PhoneControlCapability.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/communication/include/PhoneControlCapability.h" +#include "capabilities/communication/include/PhoneControlMessages.h" + +namespace vshl { +namespace capabilities { +namespace phonecontrol { + +// Create a phonecontrol. +shared_ptr PhoneControl::create() { + auto phonecontrol = std::shared_ptr(new PhoneControl()); + return phonecontrol; +} + +string PhoneControl::getName() const { + return NAME; +} + +list PhoneControl::getUpstreamMessages() const { + return PHONECONTROL_UPSTREAM_ACTIONS; +} + +list PhoneControl::getDownstreamMessages() const { + return PHONECONTROL_DOWNSTREAM_ACTIONS; +} + +} // namespace phonecontrol +} // namespace capabilities +} // namespace vshl diff --git a/src/plugins/capabilities/core/include/MessageChannel.h b/src/plugins/capabilities/core/include/MessageChannel.h new file mode 100644 index 0000000..504e241 --- /dev/null +++ b/src/plugins/capabilities/core/include/MessageChannel.h @@ -0,0 +1,68 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CAPABILITIES_CORE_MESSAGECHANNEL_H_ +#define VSHL_CAPABILITIES_CORE_MESSAGECHANNEL_H_ + +#include + +#include "capabilities/core/include/PublisherForwarder.h" +#include "capabilities/core/include/SubscriberForwarder.h" +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/capabilities/ICapability.h" +#include "interfaces/utilities/logging/ILogger.h" + +using namespace std; + +namespace vshl { +namespace capabilities { +namespace core { +/* + * MessageChannel has one end as publisher forwarder and the other end + * as subscriber forwarder. + */ +class MessageChannel { +public: + // Create a MessageChannel. + static std::shared_ptr + create(shared_ptr logger, + shared_ptr afbApi, + shared_ptr capability); + + // Sends the message + bool publish(const string action, const string payload); + + // Subscribe + bool subscribe(vshl::common::interfaces::IAFBRequest &request, + const string action); + + // Destructor + virtual ~MessageChannel() = default; + +private: + // Constructor + MessageChannel(shared_ptr logger, + shared_ptr afbApi, + shared_ptr capability); + + // Forwarders + shared_ptr mPublisherForwarder; + shared_ptr mSubscriberForwarder; +}; + +} // namespace core +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_CORE_MESSAGECHANNEL_H_ diff --git a/src/plugins/capabilities/core/include/PublisherForwarder.h b/src/plugins/capabilities/core/include/PublisherForwarder.h new file mode 100644 index 0000000..9cc89b5 --- /dev/null +++ b/src/plugins/capabilities/core/include/PublisherForwarder.h @@ -0,0 +1,73 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CAPABILITIES_CORE_PUBLISHERFORWARDER_H_ +#define VSHL_CAPABILITIES_CORE_PUBLISHERFORWARDER_H_ + +#include + +#include "capabilities/core/include/SubscriberForwarder.h" + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/capabilities/ICapability.h" +#include "interfaces/utilities/logging/ILogger.h" + +using namespace std; + +namespace vshl { +namespace capabilities { +namespace core { +/* + * This class is responsible for forwarding the messages to be published + * to subscriber forwarder. Subscriber forwarder will deliver the messages + * as AFB Events to all the subscribed clients. + * There is one PublisherForwarder and one SubscriberForwarder per capability. + */ +class PublisherForwarder { +public: + // Create a PublisherForwarder. + static std::shared_ptr create( + shared_ptr logger, + shared_ptr capability); + + // Connect a subscriber forwarder to this publisher forwarder + void setSubscriberForwarder(shared_ptr subscriberForwarder); + + // Forward message to the subscriber forwarder + bool forwardMessage(const string action, const string payload); + + // Destructor + ~PublisherForwarder(); + +private: + // Constructor + PublisherForwarder( + shared_ptr logger, + shared_ptr capability); + + // Subscriber forwarder connected to this publisher forwarder. + shared_ptr mSubscriberForwarder; + + // Capability + shared_ptr mCapability; + + // Logger + shared_ptr mLogger; +}; + +} // namespace core +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_CORE_PUBLISHERFORWARDER_H_ diff --git a/src/plugins/capabilities/core/include/SubscriberForwarder.h b/src/plugins/capabilities/core/include/SubscriberForwarder.h new file mode 100644 index 0000000..94c04bf --- /dev/null +++ b/src/plugins/capabilities/core/include/SubscriberForwarder.h @@ -0,0 +1,84 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CAPABILITIES_CORE_SUBSCRIBERFORWARDER_H_ +#define VSHL_CAPABILITIES_CORE_SUBSCRIBERFORWARDER_H_ + +#include +#include +#include + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/capabilities/ICapability.h" +#include "interfaces/utilities/logging/ILogger.h" + +using namespace std; + +namespace vshl { +namespace capabilities { +namespace core { +/* + * This class is responsible for forwarding the messages publishing + * to the actual clients using AFB. + */ +class SubscriberForwarder { +public: + // Create a SubscriberForwarder. + static std::shared_ptr + create(shared_ptr logger, + shared_ptr afbApi, + shared_ptr capability); + + // Publish a capability message to the actual client. + bool forwardMessage(const string action, const string payload); + + // Subscribe + bool subscribe(vshl::common::interfaces::IAFBRequest &request, + const string action); + + // Destructor + ~SubscriberForwarder(); + +private: + // Constructor + SubscriberForwarder( + shared_ptr logger, + shared_ptr afbApi, + shared_ptr capability); + + // Creates both upstream and downstream events + void createEvents(); + + // Binding API reference + shared_ptr mAfbApi; + + // Capability + shared_ptr mCapability; + + // Maps of capability action events to its corresponding Event object. + // Event name maps to Action Name + unordered_map> + mUpstreamEventsMap; + unordered_map> + mDownstreamEventsMap; + + // Logger + shared_ptr mLogger; +}; + +} // namespace core +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_CORE_SUBSCRIBERFORWARDER_H_ diff --git a/src/plugins/capabilities/core/src/MessageChannel.cpp b/src/plugins/capabilities/core/src/MessageChannel.cpp new file mode 100644 index 0000000..eaa1349 --- /dev/null +++ b/src/plugins/capabilities/core/src/MessageChannel.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/core/include/MessageChannel.h" + +namespace vshl { +namespace capabilities { +namespace core { + +// Create a MessageChannel. +std::shared_ptr MessageChannel::create( + shared_ptr logger, + shared_ptr api, + shared_ptr capability) { + auto messageChannel = std::shared_ptr(new MessageChannel(logger, api, capability)); + return messageChannel; +} + +MessageChannel::MessageChannel( + shared_ptr logger, + shared_ptr api, + shared_ptr capability) { + // Subscriber forwarder + mSubscriberForwarder = SubscriberForwarder::create(logger, api, capability); + // Publisher forwarder + mPublisherForwarder = PublisherForwarder::create(logger, capability); + mPublisherForwarder->setSubscriberForwarder(mSubscriberForwarder); +} + +bool MessageChannel::publish(const string action, const string payload) { + return mPublisherForwarder->forwardMessage(action, payload); +} + +bool MessageChannel::subscribe(vshl::common::interfaces::IAFBRequest& request, const string action) { + return mSubscriberForwarder->subscribe(request, action); +} + +} // namespace core +} // namespace capabilities +} // namespace vshl diff --git a/src/plugins/capabilities/core/src/PublisherForwarder.cpp b/src/plugins/capabilities/core/src/PublisherForwarder.cpp new file mode 100644 index 0000000..81de6a0 --- /dev/null +++ b/src/plugins/capabilities/core/src/PublisherForwarder.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/core/include/PublisherForwarder.h" + +static string TAG = "vshl::capabilities::PublisherForwarder"; + +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace capabilities { +namespace core { + +// Create a PublisherForwarder. +std::shared_ptr PublisherForwarder::create( + shared_ptr logger, + shared_ptr capability) { + if (logger == nullptr) { + return nullptr; + } + + if (capability == nullptr) { + logger->log(Level::ERROR, TAG, "Failed to create PublisherForwarder: Capability null"); + return nullptr; + } + + auto publisherForwarder = std::shared_ptr(new PublisherForwarder(logger, capability)); + return publisherForwarder; +} + +// Constructor +PublisherForwarder::PublisherForwarder( + shared_ptr logger, + shared_ptr capability) { + mCapability = capability; + mLogger = logger; +} + +// Destructor +PublisherForwarder::~PublisherForwarder() { +} + +void PublisherForwarder::setSubscriberForwarder(shared_ptr subscriberForwarder) { + mSubscriberForwarder = subscriberForwarder; +} + +bool PublisherForwarder::forwardMessage(const string action, const string payload) { + if (!mSubscriberForwarder) { + mLogger->log(Level::ERROR, TAG, "Failed to forward message for capability: " + mCapability->getName()); + return false; + } + + return mSubscriberForwarder->forwardMessage(action, payload); +} + +} // namespace core +} // namespace capabilities +} // namespace vshl diff --git a/src/plugins/capabilities/core/src/SubscriberForwarder.cpp b/src/plugins/capabilities/core/src/SubscriberForwarder.cpp new file mode 100644 index 0000000..ea42305 --- /dev/null +++ b/src/plugins/capabilities/core/src/SubscriberForwarder.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/core/include/SubscriberForwarder.h" + +static string TAG = "vshl::capabilities::SubscriberForwarder"; + +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace capabilities { +namespace core { + +// Create a SubscriberForwarder. +std::shared_ptr SubscriberForwarder::create( + shared_ptr logger, + shared_ptr afbApi, + shared_ptr capability) { + if (logger == nullptr) { + return nullptr; + } + + if (afbApi == nullptr) { + logger->log(Level::ERROR, TAG, "Failed to create SubscriberForwarder: AFB API null"); + return nullptr; + } + + if (capability == nullptr) { + logger->log(Level::ERROR, TAG, "Failed to create SubscriberForwarder: Capability null"); + return nullptr; + } + + auto subscriberForwarder = + std::shared_ptr(new SubscriberForwarder(logger, afbApi, capability)); + return subscriberForwarder; +} + +SubscriberForwarder::SubscriberForwarder( + shared_ptr logger, + shared_ptr afbApi, + shared_ptr capability) : + mAfbApi(afbApi), + mLogger(logger), + mCapability(capability) { + createEvents(); +} + +SubscriberForwarder::~SubscriberForwarder() { + mUpstreamEventsMap.clear(); + mDownstreamEventsMap.clear(); +} + +void SubscriberForwarder::createEvents() { + if (!mCapability) { + mLogger->log(Level::NOTICE, TAG, "Create Events failed. No capability assigned."); + return; + } + + // Upstream events + auto upstreamEvents = mCapability->getUpstreamMessages(); + for (auto upstreamEventName : upstreamEvents) { + auto it = mUpstreamEventsMap.find(upstreamEventName); + if (it == mUpstreamEventsMap.end() && mAfbApi) { + // create a new event and add it to the map. + shared_ptr event = mAfbApi->createEvent(upstreamEventName); + if (event == nullptr) { + mLogger->log(Level::ERROR, TAG, "Failed to create upstream event: " + upstreamEventName); + } else { + mUpstreamEventsMap.insert(make_pair(upstreamEventName, event)); + } + } + } + + // Downstream events + auto downstreamEvents = mCapability->getDownstreamMessages(); + for (auto downstreamEventName : downstreamEvents) { + auto it = mDownstreamEventsMap.find(downstreamEventName); + if (it == mDownstreamEventsMap.end() && mAfbApi) { + // create a new event and add it to the map. + shared_ptr event = mAfbApi->createEvent(downstreamEventName); + if (event == nullptr) { + mLogger->log(Level::ERROR, TAG, "Failed to create downstream event: " + downstreamEventName); + } else { + mDownstreamEventsMap.insert(make_pair(downstreamEventName, event)); + } + } + } +} + +bool SubscriberForwarder::forwardMessage(const string action, const string payload) { + auto upstreamEventIt = mUpstreamEventsMap.find(action); + if (upstreamEventIt != mUpstreamEventsMap.end()) { + mLogger->log(Level::NOTICE, TAG, "Publishing upstream event: " + action); + upstreamEventIt->second->publishEvent(json_object_new_string(payload.c_str())); + return true; + } + + auto downstreamEventIt = mDownstreamEventsMap.find(action); + if (downstreamEventIt != mDownstreamEventsMap.end()) { + mLogger->log(Level::NOTICE, TAG, "Publishing downstream event: " + action); + downstreamEventIt->second->publishEvent(json_object_new_string(payload.c_str())); + return true; + } + + mLogger->log(Level::NOTICE, TAG, "Failed to publish upstream event: " + action); + return false; +} + +bool SubscriberForwarder::subscribe(vshl::common::interfaces::IAFBRequest& request, const string action) { + auto upstreamEventIt = mUpstreamEventsMap.find(action); + if (upstreamEventIt != mUpstreamEventsMap.end()) { + mLogger->log(Level::NOTICE, TAG, "Subscribing to upstream event: " + action); + return upstreamEventIt->second->subscribe(request); + } + + auto downstreamEventIt = mDownstreamEventsMap.find(action); + if (downstreamEventIt != mDownstreamEventsMap.end()) { + mLogger->log(Level::NOTICE, TAG, "Subscribing to downstream event: " + action); + return downstreamEventIt->second->subscribe(request); + } + + mLogger->log(Level::NOTICE, TAG, "Failed to subscribe to upstream event: " + action); + return false; +} + +} // namespace core +} // namespace capabilities +} // namespace vshl diff --git a/src/plugins/capabilities/guimetadata/include/GuiMetadataCapability.h b/src/plugins/capabilities/guimetadata/include/GuiMetadataCapability.h new file mode 100644 index 0000000..199f49a --- /dev/null +++ b/src/plugins/capabilities/guimetadata/include/GuiMetadataCapability.h @@ -0,0 +1,52 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +#ifndef VSHL_CAPABILITIES_GUIMETADATA_CAPABILITY_H_ +#define VSHL_CAPABILITIES_GUIMETADATA_CAPABILITY_H_ + +#include + +#include "interfaces/capabilities/ICapability.h" + +namespace vshl { +namespace capabilities { +namespace guimetadata { + +/* + * GuiMetadata capability + */ +class GuiMetadata : public common::interfaces::ICapability { +public: + // Create a GuiMetadata. + static std::shared_ptr create(); + + ~GuiMetadata() = default; + +protected: + string getName() const override; + + list getUpstreamMessages() const override; + + list getDownstreamMessages() const override; + +private: + GuiMetadata() = default; +}; + +} // namespace guimetadata +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_GUIMETADATA_CAPABILITY_H_ diff --git a/src/plugins/capabilities/guimetadata/include/GuiMetadataMessages.h b/src/plugins/capabilities/guimetadata/include/GuiMetadataMessages.h new file mode 100644 index 0000000..783b401 --- /dev/null +++ b/src/plugins/capabilities/guimetadata/include/GuiMetadataMessages.h @@ -0,0 +1,50 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CAPABILITIES_GUIMETADATA_ACTIONS_H_ +#define VSHL_CAPABILITIES_GUIMETADATA_ACTIONS_H_ + +#include +#include + +using namespace std; + +namespace vshl { +namespace capabilities { +namespace guimetadata { + +static string NAME = "guimetadata"; + +// Supported actions from VA -> Apps +static string GUIMETADATA_RENDER_TEMPLATE = "render_template"; +static string GUIMETADATA_CLEAR_TEMPLATE = "clear_template"; +static string GUIMETADATA_RENDER_PLAYER_INFO = "render_player_info"; +static string GUIMETADATA_CLEAR_PLAYER_INFO = "clear_player_info"; + +// Supported actions from Apps -> VA + +// List of actions that are delivered from VA -> Apps +static list GUIMETADATA_UPSTREAM_ACTIONS = {GUIMETADATA_RENDER_TEMPLATE, + GUIMETADATA_CLEAR_TEMPLATE, + GUIMETADATA_RENDER_PLAYER_INFO, + GUIMETADATA_CLEAR_PLAYER_INFO}; + +// List of actions that are delivered from Apps -> VA +static list GUIMETADATA_DOWNSTREAM_ACTIONS = {}; + +} // namespace guimetadata +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_GUIMETADATA_ACTIONS_H_ diff --git a/src/plugins/capabilities/guimetadata/src/GuiMetadataCapability.cpp b/src/plugins/capabilities/guimetadata/src/GuiMetadataCapability.cpp new file mode 100644 index 0000000..106fe99 --- /dev/null +++ b/src/plugins/capabilities/guimetadata/src/GuiMetadataCapability.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/guimetadata/include/GuiMetadataCapability.h" +#include "capabilities/guimetadata/include/GuiMetadataMessages.h" + +namespace vshl { +namespace capabilities { +namespace guimetadata { + +// Create a GuiMetadata. +shared_ptr GuiMetadata::create() { + auto guiMetadata = std::shared_ptr(new GuiMetadata()); + return guiMetadata; +} + +string GuiMetadata::getName() const { + return NAME; +} + +list GuiMetadata::getUpstreamMessages() const { + return GUIMETADATA_UPSTREAM_ACTIONS; +} + +list GuiMetadata::getDownstreamMessages() const { + return GUIMETADATA_DOWNSTREAM_ACTIONS; +} + +} // namespace guimetadata +} // namespace capabilities +} // namespace vshl diff --git a/src/plugins/capabilities/navigation/include/NavigationCapability.h b/src/plugins/capabilities/navigation/include/NavigationCapability.h new file mode 100644 index 0000000..66109d5 --- /dev/null +++ b/src/plugins/capabilities/navigation/include/NavigationCapability.h @@ -0,0 +1,52 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +#ifndef VSHL_CAPABILITIES_NAVIGATION_CAPABILITY_H_ +#define VSHL_CAPABILITIES_NAVIGATION_CAPABILITY_H_ + +#include + +#include "interfaces/capabilities/ICapability.h" + +namespace vshl { +namespace capabilities { +namespace navigation { + +/* + * Navigation capability + */ +class Navigation : public common::interfaces::ICapability { +public: + // Create a Navigation. + static std::shared_ptr create(); + + ~Navigation() = default; + +protected: + string getName() const override; + + list getUpstreamMessages() const override; + + list getDownstreamMessages() const override; + +private: + Navigation() = default; +}; + +} // namespace navigation +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_NAVIGATION_CAPABILITY_H_ diff --git a/src/plugins/capabilities/navigation/include/NavigationMessages.h b/src/plugins/capabilities/navigation/include/NavigationMessages.h new file mode 100644 index 0000000..aed9b9e --- /dev/null +++ b/src/plugins/capabilities/navigation/include/NavigationMessages.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CAPABILITIES_NAVIGATION_ACTIONS_H_ +#define VSHL_CAPABILITIES_NAVIGATION_ACTIONS_H_ + +#include +#include + +using namespace std; + +namespace vshl { +namespace capabilities { +namespace navigation { + +static string NAME = "navigation"; + +// Supported actions from VA -> Apps +static string NAVIGATION_SET_DESTINATION = "set_destination"; +static string NAVIGATION_CANCEL = "cancel_navigation"; + +// Supported actions from Apps -> VA + +// List of actions that are delivered from VA -> Apps +static list NAVIGATION_UPSTREAM_ACTIONS = { + NAVIGATION_SET_DESTINATION, + NAVIGATION_CANCEL, +}; + +// List of actions that are delivered from Apps -> VA +static list NAVIGATION_DOWNSTREAM_ACTIONS = {}; + +} // namespace navigation +} // namespace capabilities +} // namespace vshl + +#endif // VSHL_CAPABILITIES_NAVIGATION_ACTIONS_H_ diff --git a/src/plugins/capabilities/navigation/src/NavigationCapability.cpp b/src/plugins/capabilities/navigation/src/NavigationCapability.cpp new file mode 100644 index 0000000..d4a5119 --- /dev/null +++ b/src/plugins/capabilities/navigation/src/NavigationCapability.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/navigation/include/NavigationCapability.h" +#include "capabilities/navigation/include/NavigationMessages.h" + +namespace vshl { +namespace capabilities { +namespace navigation { + +// Create a Navigation. +shared_ptr Navigation::create() { + auto navigation = std::shared_ptr(new Navigation()); + return navigation; +} + +string Navigation::getName() const { + return NAME; +} + +list Navigation::getUpstreamMessages() const { + return NAVIGATION_UPSTREAM_ACTIONS; +} + +list Navigation::getDownstreamMessages() const { + return NAVIGATION_DOWNSTREAM_ACTIONS; +} + +} // namespace navigation +} // namespace capabilities +} // namespace vshl diff --git a/src/plugins/capabilities/test/CapabilityMessagingServiceTest.cpp b/src/plugins/capabilities/test/CapabilityMessagingServiceTest.cpp new file mode 100644 index 0000000..741a8aa --- /dev/null +++ b/src/plugins/capabilities/test/CapabilityMessagingServiceTest.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/CapabilityMessagingService.h" + +#include "test/common/ConsoleLogger.h" +#include "test/mocks/AFBApiMock.h" +#include "test/mocks/AFBEventMock.h" +#include "test/mocks/AFBRequestMock.h" +#include "test/mocks/CapabilityMock.h" + +using namespace vshl::common::interfaces; +using namespace vshl::capabilities; +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class CapabilityMessagingServiceTest : public ::testing::Test { +protected: + void SetUp() override { + mConsoleLogger = std::make_shared(); + mAfbApi = std::make_shared<::testing::NiceMock>(); + } + + std::shared_ptr<::testing::NiceMock> mAfbApi; + std::shared_ptr mConsoleLogger; +}; + +TEST_F(CapabilityMessagingServiceTest, failsInitializationOnInvalidParams) { + auto service = CapabilityMessagingService::create(mConsoleLogger, nullptr); + ASSERT_EQ(service, nullptr); + + service = CapabilityMessagingService::create(nullptr, mAfbApi); + ASSERT_EQ(service, nullptr); +} + +TEST_F(CapabilityMessagingServiceTest, initializesSuccessfully) { + auto service = CapabilityMessagingService::create(mConsoleLogger, mAfbApi); + ASSERT_NE(service, nullptr); +} + +TEST_F(CapabilityMessagingServiceTest, canSubscribeAndPublishCapabilityEvents) { + auto service = CapabilityMessagingService::create(mConsoleLogger, mAfbApi); + + auto capability = std::make_shared<::testing::NiceMock>(); + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + std::string capabilityName = "weather"; + + ON_CALL(*capability, getName()).WillByDefault(::testing::Return(capabilityName)); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + // We don't care if and how many times subscribe method is called on event. + std::shared_ptr mockEvent(new ::testing::NiceMock()); + ON_CALL(*mockEvent, subscribe(::testing::_)).WillByDefault(::testing::Return(true)); + ON_CALL(*mockEvent, publishEvent(::testing::_)).WillByDefault(::testing::Return(true)); + auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr { + mockEvent->setName(eventName); + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + + auto request = std::make_shared<::testing::StrictMock>(); + std::string payload = "The answer to life the universe and everything = 42"; + + bool result = service->publish(capability, *upstreamEvents.begin(), payload); + ASSERT_FALSE(result); // Without subscribing to the event. Publish should fail. + + result = service->subscribe(*request, capability, *upstreamEvents.begin()); + ASSERT_TRUE(result); + + result = service->subscribe(*request, capability, *downstreamEvents.begin()); + ASSERT_TRUE(result); + + result = service->publish(capability, *downstreamEvents.begin(), payload); + ASSERT_TRUE(result); +} + +} // namespace test +} // namespace vshl \ No newline at end of file diff --git a/src/plugins/capabilities/test/PublisherForwarderTest.cpp b/src/plugins/capabilities/test/PublisherForwarderTest.cpp new file mode 100644 index 0000000..d0ba319 --- /dev/null +++ b/src/plugins/capabilities/test/PublisherForwarderTest.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/core/include/PublisherForwarder.h" +#include "capabilities/core/include/SubscriberForwarder.h" + +#include "test/common/ConsoleLogger.h" +#include "test/mocks/AFBApiMock.h" +#include "test/mocks/AFBEventMock.h" +#include "test/mocks/CapabilityMock.h" + +using namespace vshl::common::interfaces; +using namespace vshl::capabilities::core; +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class PublisherForwarderTest : public ::testing::Test { +protected: + void SetUp() override { + mConsoleLogger = std::make_shared(); + mAfbApi = std::make_shared<::testing::StrictMock>(); + + mEventCreatorFn = [](const std::string& eventName) -> std::shared_ptr { + std::shared_ptr mockEvent(new ::testing::StrictMock()); + mockEvent->setName(eventName); + return mockEvent; + }; + } + + std::shared_ptr createSubscriberForwarder( + std::shared_ptr<::testing::StrictMock> capability) { + EXPECT_CALL(*capability, getUpstreamMessages()).Times(1); + EXPECT_CALL(*capability, getDownstreamMessages()).Times(1); + + return SubscriberForwarder::create(mConsoleLogger, mAfbApi, capability); + } + + std::shared_ptr createPublisherForwarder( + std::shared_ptr<::testing::StrictMock> capability) { + return PublisherForwarder::create(mConsoleLogger, capability); + } + + std::shared_ptr<::testing::StrictMock> mAfbApi; + std::shared_ptr mConsoleLogger; + std::function(const std::string&)> mEventCreatorFn; + std::shared_ptr<::testing::StrictMock> mAfbEventMock; +}; + +TEST_F(PublisherForwarderTest, failsInitializationOnInvalidParams) { + auto capability = std::make_shared<::testing::StrictMock>(); + + auto forwarder = PublisherForwarder::create(mConsoleLogger, nullptr); + ASSERT_EQ(forwarder, nullptr); + + forwarder = PublisherForwarder::create(nullptr, capability); + ASSERT_EQ(forwarder, nullptr); +} + +TEST_F(PublisherForwarderTest, initializesCorrectly) { + auto capability = std::make_shared<::testing::StrictMock>(); + auto forwarder = createPublisherForwarder(capability); + + ASSERT_NE(forwarder, nullptr); +} + +TEST_F(PublisherForwarderTest, canForwardMessages) { + std::shared_ptr mockEvent(new ::testing::StrictMock()); + ON_CALL(*mockEvent, publishEvent(::testing::_)).WillByDefault(::testing::Return(true)); + EXPECT_CALL(*mockEvent, publishEvent(::testing::_)).Times(4); + auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr { + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + auto capability = std::make_shared<::testing::StrictMock>(); + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + auto publisherForwarder = createPublisherForwarder(capability); + ASSERT_NE(publisherForwarder, nullptr); + + auto subscriberForwarder = createSubscriberForwarder(capability); + ASSERT_NE(subscriberForwarder, nullptr); + + std::string payload = "The answer to life the universe and everything = 42"; + publisherForwarder->setSubscriberForwarder(subscriberForwarder); + + auto itCapability = downstreamEvents.begin(); + ASSERT_TRUE(publisherForwarder->forwardMessage(*itCapability++, payload)); + ASSERT_TRUE(publisherForwarder->forwardMessage(*itCapability++, payload)); + itCapability = upstreamEvents.begin(); + ASSERT_TRUE(publisherForwarder->forwardMessage(*itCapability++, payload)); + ASSERT_TRUE(publisherForwarder->forwardMessage(*itCapability++, payload)); +} + +} // namespace test +} // namespace vshl \ No newline at end of file diff --git a/src/plugins/capabilities/test/SubscriberForwarderTest.cpp b/src/plugins/capabilities/test/SubscriberForwarderTest.cpp new file mode 100644 index 0000000..ff438df --- /dev/null +++ b/src/plugins/capabilities/test/SubscriberForwarderTest.cpp @@ -0,0 +1,262 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "capabilities/core/include/SubscriberForwarder.h" + +#include "test/common/ConsoleLogger.h" +#include "test/mocks/AFBApiMock.h" +#include "test/mocks/AFBEventMock.h" +#include "test/mocks/AFBRequestMock.h" +#include "test/mocks/CapabilityMock.h" + +using namespace vshl::common::interfaces; +using namespace vshl::capabilities::core; +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class SubscriberForwarderTest : public ::testing::Test { +protected: + void SetUp() override { + mConsoleLogger = std::make_shared(); + mAfbApi = std::make_shared<::testing::StrictMock>(); + + mEventCreatorFn = [](const std::string& eventName) -> std::shared_ptr { + std::shared_ptr mockEvent(new ::testing::StrictMock()); + mockEvent->setName(eventName); + return mockEvent; + }; + } + + std::shared_ptr createSubscriberForwarder( + std::shared_ptr<::testing::StrictMock> capability) { + EXPECT_CALL(*capability, getUpstreamMessages()).Times(1); + EXPECT_CALL(*capability, getDownstreamMessages()).Times(1); + + return SubscriberForwarder::create(mConsoleLogger, mAfbApi, capability); + } + + std::shared_ptr<::testing::StrictMock> mAfbApi; + std::shared_ptr mConsoleLogger; + std::function(const std::string&)> mEventCreatorFn; + std::shared_ptr<::testing::StrictMock> mAfbEventMock; +}; + +TEST_F(SubscriberForwarderTest, failsInitializationOnInvalidParams) { + auto capability = std::make_shared<::testing::StrictMock>(); + + auto forwarder = SubscriberForwarder::create(mConsoleLogger, mAfbApi, nullptr); + ASSERT_EQ(forwarder, nullptr); + + forwarder = SubscriberForwarder::create(mConsoleLogger, nullptr, capability); + ASSERT_EQ(forwarder, nullptr); + + forwarder = SubscriberForwarder::create(nullptr, mAfbApi, capability); + ASSERT_EQ(forwarder, nullptr); +} + +TEST_F(SubscriberForwarderTest, initializesCorrectly) { + auto capability = std::make_shared<::testing::StrictMock>(); + auto forwarder = createSubscriberForwarder(capability); + + ASSERT_NE(forwarder, nullptr); +} + +TEST_F(SubscriberForwarderTest, createsEventsOnInitialization) { + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(mEventCreatorFn)); + + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock>(); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + auto itCapability = upstreamEvents.begin(); + EXPECT_CALL(*mAfbApi, createEvent(*itCapability)).Times(1); + itCapability++; + EXPECT_CALL(*mAfbApi, createEvent(*itCapability)).Times(1); + + itCapability = downstreamEvents.begin(); + EXPECT_CALL(*mAfbApi, createEvent(*itCapability)).Times(1); + itCapability++; + EXPECT_CALL(*mAfbApi, createEvent(*itCapability)).Times(1); + + auto forwarder = createSubscriberForwarder(capability); + ASSERT_NE(forwarder, nullptr); +} + +TEST_F(SubscriberForwarderTest, canNotSubscribeToNonExistentEvents) { + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(mEventCreatorFn)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock>(); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + auto forwarder = createSubscriberForwarder(capability); + ASSERT_NE(forwarder, nullptr); + + auto nonExistentEvents = std::list({"non", "existent", "events"}); + auto itCapability = nonExistentEvents.begin(); + auto request = std::make_shared<::testing::StrictMock>(); + ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++)); + ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++)); + ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++)); +} + +TEST_F(SubscriberForwarderTest, canNotSubscribeOnEventCreationFailure) { + // Fail the event creation and return null event. + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Return(nullptr)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock>(); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + auto forwarder = createSubscriberForwarder(capability); + ASSERT_NE(forwarder, nullptr); + + auto itCapability = downstreamEvents.begin(); + auto request = std::make_shared<::testing::StrictMock>(); + std::string payload = "The answer to life the universe and everything = 42"; + ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++)); + ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++)); + itCapability = upstreamEvents.begin(); + ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++)); + ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++)); +} + +TEST_F(SubscriberForwarderTest, canSubscribeToEvents) { + std::shared_ptr mockEvent(new ::testing::StrictMock()); + ON_CALL(*mockEvent, subscribe(::testing::_)).WillByDefault(::testing::Return(true)); + EXPECT_CALL(*mockEvent, subscribe(::testing::_)).Times(4); + auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr { + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock>(); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + auto forwarder = createSubscriberForwarder(capability); + ASSERT_NE(forwarder, nullptr); + + auto itCapability = downstreamEvents.begin(); + auto request = std::make_shared<::testing::StrictMock>(); + ASSERT_TRUE(forwarder->subscribe(*request, *itCapability++)); + ASSERT_TRUE(forwarder->subscribe(*request, *itCapability)); + itCapability = upstreamEvents.begin(); + ASSERT_TRUE(forwarder->subscribe(*request, *itCapability++)); + ASSERT_TRUE(forwarder->subscribe(*request, *itCapability)); +} + +TEST_F(SubscriberForwarderTest, canNotPublishNonExistentEvents) { + std::shared_ptr mockEvent(new ::testing::StrictMock()); + EXPECT_CALL(*mockEvent, publishEvent(::testing::_)).Times(0); + auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr { + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock>(); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + auto forwarder = createSubscriberForwarder(capability); + ASSERT_NE(forwarder, nullptr); + + auto nonExistentEvents = std::list({"non", "existent", "events"}); + auto itCapability = nonExistentEvents.begin(); + ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, "My-Payload")); + ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, "My-Payload")); + ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, "My-Payload")); +} + +TEST_F(SubscriberForwarderTest, canNotPublishOnEventCreationFailure) { + // Fail the event creation and return null event. + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Return(nullptr)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock>(); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + auto forwarder = createSubscriberForwarder(capability); + ASSERT_NE(forwarder, nullptr); + + auto itCapability = downstreamEvents.begin(); + std::string payload = "The answer to life the universe and everything = 42"; + ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, payload)); + ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, payload)); + itCapability = upstreamEvents.begin(); + ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, payload)); + ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, payload)); +} + +TEST_F(SubscriberForwarderTest, canPublishEvents) { + std::shared_ptr mockEvent(new ::testing::StrictMock()); + ON_CALL(*mockEvent, publishEvent(::testing::_)).WillByDefault(::testing::Return(true)); + EXPECT_CALL(*mockEvent, publishEvent(::testing::_)).Times(4); + auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr { + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list upstreamEvents({"up-ev1", "up-ev2"}); + std::list downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock>(); + ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents)); + ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents)); + + auto forwarder = createSubscriberForwarder(capability); + ASSERT_NE(forwarder, nullptr); + + auto itCapability = downstreamEvents.begin(); + std::string payload = "The answer to life the universe and everything = 42"; + ASSERT_TRUE(forwarder->forwardMessage(*itCapability++, payload)); + ASSERT_TRUE(forwarder->forwardMessage(*itCapability++, payload)); + itCapability = upstreamEvents.begin(); + ASSERT_TRUE(forwarder->forwardMessage(*itCapability++, payload)); + ASSERT_TRUE(forwarder->forwardMessage(*itCapability++, payload)); +} + +} // namespace test +} // namespace vshl \ No newline at end of file diff --git a/src/plugins/cmake/gtest.cmake b/src/plugins/cmake/gtest.cmake new file mode 100644 index 0000000..def6559 --- /dev/null +++ b/src/plugins/cmake/gtest.cmake @@ -0,0 +1,44 @@ +# gtest + +find_package(Threads REQUIRED) + +# Enable ExternalProject CMake module +INCLUDE(ExternalProject) + +ExternalProject_Add( + gtest + URL https://github.com/google/googletest/archive/release-1.8.1.zip + SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src" + BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON +) + +# Get GTest source and binary directories from CMake project +ExternalProject_Get_Property(gtest source_dir binary_dir) + +# Create a libgtest target to be used as a dependency by test programs +ADD_LIBRARY(libgtest INTERFACE) +TARGET_INCLUDE_DIRECTORIES(libgtest + INTERFACE + ${source_dir}/googletest/include +) +TARGET_LINK_LIBRARIES(libgtest + INTERFACE + ${binary_dir}/googlemock/gtest/libgtest.a + ${CMAKE_THREAD_LIBS_INIT} +) + +# Create a libgmock target to be used as a dependency by test programs +ADD_LIBRARY(libgmock INTERFACE) +TARGET_INCLUDE_DIRECTORIES(libgmock + INTERFACE + ${source_dir}/googlemock/include +) +TARGET_LINK_LIBRARIES(libgmock + INTERFACE + ${binary_dir}/googlemock/libgmock.a + ${CMAKE_THREAD_LIBS_INIT} +) \ No newline at end of file diff --git a/src/plugins/core/VRRequestProcessor.h b/src/plugins/core/VRRequestProcessor.h new file mode 100644 index 0000000..c349a19 --- /dev/null +++ b/src/plugins/core/VRRequestProcessor.h @@ -0,0 +1,75 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CORE_INCLUDE_VR_REQUESTPROCESSOR_H_ +#define VSHL_CORE_INCLUDE_VR_REQUESTPROCESSOR_H_ + +#include +#include + +#include "core/include/VRRequest.h" +#include "core/include/VRRequestProcessorDelegate.h" +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/logging/ILogger.h" +#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h" + +using namespace std; + +namespace vshl { +namespace core { +/* + * This class is the entry point for all the voice recognition request + * creation and arbitration. + */ +class VRRequestProcessor { +public: + // Create a VRRequestProcessor. + static unique_ptr create( + shared_ptr logger, + shared_ptr delegate); + + // Triggers a voiceagent to start listening to user speech input. + // Returns the request ID. If start fails, then empty request ID + // is returned. + string startListening(); + + // Cancels all the active requests + void cancel(); + + // Returns the voiceagents observer that belongs to the core module. + shared_ptr getVoiceAgentsChangeObserver() const; + + // Destructor + ~VRRequestProcessor(); + +private: + // Constructor + VRRequestProcessor( + shared_ptr logger, + shared_ptr delegate); + + // Voiceagents observer + shared_ptr mVoiceAgentsChangeObserver; + + // Request Processor Delegate + shared_ptr mDelegate; + + // Logger + shared_ptr mLogger; +}; + +} // namespace core +} // namespace vshl + +#endif // VSHL_CORE_INCLUDE_VR_REQUESTPROCESSOR_H_ diff --git a/src/plugins/core/VRRequestProcessorImpl.cpp b/src/plugins/core/VRRequestProcessorImpl.cpp new file mode 100644 index 0000000..7441a7d --- /dev/null +++ b/src/plugins/core/VRRequestProcessorImpl.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "core/VRRequestProcessor.h" + +#include "core/include/VRAgentsObserver.h" + +static string TAG = "vshl::core::VRRequestProcessor"; + +using Level = vshl::utilities::logging::Logger::Level; + +namespace vshl { +namespace core { +// Create a VRRequestProcessor. +unique_ptr VRRequestProcessor::create( + shared_ptr logger, + shared_ptr delegate) { + auto processor = std::unique_ptr(new VRRequestProcessor(logger, delegate)); + return processor; +} + +VRRequestProcessor::VRRequestProcessor( + shared_ptr logger, + shared_ptr delegate) : + mLogger(logger), + mDelegate(delegate) { + mVoiceAgentsChangeObserver = VRAgentsObserver::create(mDelegate); +} + +VRRequestProcessor::~VRRequestProcessor() { + mDelegate->cancelAllRequests(); +} + +string VRRequestProcessor::startListening() { + // Currently start is simply going to send the request to + // the default voice agent as the wake word detection is not + // enabled. + shared_ptr defaultVA = mDelegate->getDefaultVoiceAgent(); + if (!defaultVA) { + mLogger->log(Level::ERROR, TAG, "Failed to start. 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->startRequestForVoiceAgent(defaultVA); +} + +void VRRequestProcessor::cancel() { + // Cancel all pending requests + mDelegate->cancelAllRequests(); +} + +shared_ptr VRRequestProcessor::getVoiceAgentsChangeObserver() + const { + return mVoiceAgentsChangeObserver; +} + +} // namespace core +} // namespace vshl diff --git a/src/plugins/core/include/VRAgentsObserver.h b/src/plugins/core/include/VRAgentsObserver.h new file mode 100644 index 0000000..d4c0c7b --- /dev/null +++ b/src/plugins/core/include/VRAgentsObserver.h @@ -0,0 +1,67 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CORE_INCLUDE_VR_AGENTS_OBSERVER_H_ +#define VSHL_CORE_INCLUDE_VR_AGENTS_OBSERVER_H_ + +#include + +#include "core/include/VRRequestProcessorDelegate.h" +#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h" +#include "utilities/logging/Logger.h" + +using namespace std; + +namespace vshl { +namespace core { +/* + * This class will observe the changes to the voiceagents data and transfers + * the actual handling responsibility to its delegate. + */ +class VRAgentsObserver + : public vshl::common::interfaces::IVoiceAgentsChangeObserver { +public: + // Create a VRAgentsObserver. + static shared_ptr + create(weak_ptr delegate); + + ~VRAgentsObserver(); + +protected: + void OnDefaultVoiceAgentChanged( + shared_ptr defaultVoiceAgent) + override; + void OnVoiceAgentAdded( + shared_ptr voiceAgent) override; + void OnVoiceAgentRemoved( + shared_ptr voiceAgent) override; + void OnVoiceAgentActiveWakeWordChanged( + shared_ptr voiceAgent) override; + void OnVoiceAgentActivated( + shared_ptr voiceAgent) override; + void OnVoiceAgentDeactivated( + shared_ptr voiceAgent) override; + +private: + // Constructor + VRAgentsObserver(weak_ptr delegate); + + // Delegate that needs to be informed of the voiceagent data changes. + weak_ptr mWeakDelegate; +}; + +} // namespace core +} // namespace vshl + +#endif // VSHL_CORE_INCLUDE_VR_AGENTS_OBSERVER_H_ diff --git a/src/plugins/core/include/VRRequest.h b/src/plugins/core/include/VRRequest.h new file mode 100644 index 0000000..522ec78 --- /dev/null +++ b/src/plugins/core/include/VRRequest.h @@ -0,0 +1,80 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CORE_INCLUDE_VR_REQUEST_H_ +#define VSHL_CORE_INCLUDE_VR_REQUEST_H_ + +#include + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/logging/ILogger.h" +#include "interfaces/voiceagents/IVoiceAgent.h" + +using namespace std; + +namespace vshl { +namespace core { +/* + * This class implements the notion of a Voice Recognition Request. + * Each VR Request is currently mapped to one voice agent. + */ +class VRRequest { +public: + // API Verbs + static std::string VA_VERB_STARTLISTENING; + static std::string VA_VERB_CANCEL; + + // Create a VRRequest. + static unique_ptr create( + shared_ptr logger, + shared_ptr afbApi, + string requestId, + shared_ptr voiceAgent); + + // Destructor + ~VRRequest(); + + // Invokes the underlying voiceagent's startlistening API. + // Returns true if started successfully. False otherwise. + bool startListening(); + + // Cancels the voice recognition in the unlerlying voiceagent. + // Returns true if canceled successfully. False otherwise. + bool cancel(); + +private: + // Constructor + VRRequest( + shared_ptr logger, + shared_ptr afbApi, + const string requestId, + shared_ptr voiceAgent); + + // Binding API reference. + shared_ptr mApi; + + // Voice agent associated with this request + shared_ptr mVoiceAgent; + + // Request ID + string mRequestId; + + // Logger + shared_ptr mLogger; +}; + +} // namespace core +} // namespace vshl + +#endif // VSHL_CORE_INCLUDE_VR_REQUEST_H_ diff --git a/src/plugins/core/include/VRRequestProcessorDelegate.h b/src/plugins/core/include/VRRequestProcessorDelegate.h new file mode 100644 index 0000000..94b7304 --- /dev/null +++ b/src/plugins/core/include/VRRequestProcessorDelegate.h @@ -0,0 +1,88 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_CORE_INCLUDE_VR_REQUESTPROCESSORDELEGATE_H_ +#define VSHL_CORE_INCLUDE_VR_REQUESTPROCESSORDELEGATE_H_ + +#include +#include + +#include "core/include/VRRequest.h" +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/logging/ILogger.h" +#include "interfaces/voiceagents/IVoiceAgent.h" +#include "utilities/uuid/UUIDGeneration.h" + +using namespace std; + +namespace vshl { +namespace core { +/* + * This is a delegate for VRRequestProcessor actions. + * The lifetime and dependencies of this object is managed + * by VRRequestProcessor. It plays the role of a Delegate in + * Delegate pattern and a one shop stop for most of the core + * module's state. + */ +class VRRequestProcessorDelegate { +public: + // create method + static shared_ptr create( + shared_ptr logger, + shared_ptr afbApi); + + // Destructor + ~VRRequestProcessorDelegate(); + + // Set default voiceagent + void setDefaultVoiceAgent(shared_ptr voiceAgent); + + // Get the default voiceagent + shared_ptr getDefaultVoiceAgent() const; + + // Add new request to the list and start processing it. + // New request is created and startListening on the + // voiceagent is called. + string startRequestForVoiceAgent(shared_ptr voiceAgent); + + // Cancel all requests + void cancelAllRequests(); + + // Get All outstanding requests + // Used only by Test + unordered_map> getAllRequests(); + +private: + // Constructor + VRRequestProcessorDelegate( + shared_ptr logger, + shared_ptr afbApi); + + // Binding API reference + shared_ptr mApi; + + // Default voiceagent + shared_ptr mDefaultVoiceAgent; + + // A map of voiceagent IDs and their respective VR Request objects. + unordered_map> mVRRequests; + + // Logger + shared_ptr mLogger; +}; + +} // namespace core +} // namespace vshl + +#endif // VSHL_CORE_INCLUDE_VR_REQUESTPROCESSORDELEGATE_H_ diff --git a/src/plugins/core/src/VRAgentsObserverImpl.cpp b/src/plugins/core/src/VRAgentsObserverImpl.cpp new file mode 100644 index 0000000..7ee4a7e --- /dev/null +++ b/src/plugins/core/src/VRAgentsObserverImpl.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "core/include/VRAgentsObserver.h" + +namespace vshl { +namespace core { + +shared_ptr VRAgentsObserver::create(weak_ptr delegate) { + auto observer = std::shared_ptr(new VRAgentsObserver(delegate)); + return observer; +} + +VRAgentsObserver::VRAgentsObserver(weak_ptr delegate) { + mWeakDelegate = delegate; +} + +VRAgentsObserver::~VRAgentsObserver() { +} + +void VRAgentsObserver::OnDefaultVoiceAgentChanged(shared_ptr defaultVoiceAgent) { + if (auto delegate = mWeakDelegate.lock()) { + delegate->setDefaultVoiceAgent(defaultVoiceAgent); + } +} + +void VRAgentsObserver::OnVoiceAgentAdded(shared_ptr voiceAgent) { +} + +void VRAgentsObserver::OnVoiceAgentRemoved(shared_ptr voiceAgent) { +} + +void VRAgentsObserver::OnVoiceAgentActiveWakeWordChanged(shared_ptr voiceAgent) { + // Not Implemented +} + +void VRAgentsObserver::OnVoiceAgentActivated(shared_ptr voiceAgent) { +} + +void VRAgentsObserver::OnVoiceAgentDeactivated(shared_ptr voiceAgent) { +} +} // namespace core +} // namespace vshl diff --git a/src/plugins/core/src/VRRequestImpl.cpp b/src/plugins/core/src/VRRequestImpl.cpp new file mode 100644 index 0000000..00adf96 --- /dev/null +++ b/src/plugins/core/src/VRRequestImpl.cpp @@ -0,0 +1,92 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "core/include/VRRequest.h" + +#define FREEIF(x) \ + if (!x) { \ + free(x); \ + } +#define BREAKIF(x) \ + if (x) { \ + result = false; \ + break; \ + } + +static string TAG = "vshl::core::VRRequest"; + +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace core { + +string VRRequest::VA_VERB_STARTLISTENING = "startListening"; +string VRRequest::VA_VERB_CANCEL = "cancel"; + +unique_ptr VRRequest::create( + shared_ptr logger, + shared_ptr afbApi, + const string requestId, + shared_ptr voiceAgent) { + if (logger == nullptr) { + return nullptr; + } + + if (afbApi == nullptr) { + logger->log(Level::ERROR, TAG, "Invalid AFB API"); + return nullptr; + } + + auto request = std::unique_ptr(new VRRequest(logger, afbApi, requestId, voiceAgent)); + return request; +} + +VRRequest::VRRequest( + shared_ptr logger, + shared_ptr afbApi, + string requestId, + shared_ptr voiceAgent) : + mApi(afbApi), + mRequestId(requestId), + mVoiceAgent(voiceAgent), + mLogger(logger) { +} + +VRRequest::~VRRequest() { +} + +bool VRRequest::startListening() { + json_object* object = NULL; + std::string error, info; + bool result = true; + int rc = mApi->callSync(mVoiceAgent->getApi(), VA_VERB_STARTLISTENING, NULL, &object, error, info); + + FREEIF(object); + + return true; +} + +bool VRRequest::cancel() { + json_object* object = NULL; + std::string error, info; + bool result = true; + int rc = mApi->callSync(mVoiceAgent->getApi(), VA_VERB_CANCEL, NULL, &object, error, info); + + FREEIF(object); + + return true; +} + +} // namespace core +} // namespace vshl diff --git a/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp b/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp new file mode 100644 index 0000000..e20b22e --- /dev/null +++ b/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "core/include/VRRequestProcessorDelegate.h" + +static string TAG = "vshl::core::VRRequestProcessorDelegate"; + +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace core { +shared_ptr VRRequestProcessorDelegate::create( + shared_ptr logger, + shared_ptr afbApi) { + auto delegate = std::shared_ptr(new VRRequestProcessorDelegate(logger, afbApi)); + return delegate; +} + +VRRequestProcessorDelegate::VRRequestProcessorDelegate( + shared_ptr logger, + shared_ptr afbApi) : + mApi(afbApi), + mLogger(logger) { +} + +VRRequestProcessorDelegate::~VRRequestProcessorDelegate() { + mVRRequests.clear(); +} + +string VRRequestProcessorDelegate::startRequestForVoiceAgent( + shared_ptr voiceAgent) { + if (!mApi) { + mLogger->log(Level::ERROR, TAG, "Failed to startRequestForVoiceAgent: " + voiceAgent->getId() + ", No API."); + return ""; + } + + // Generate a new request ID. + string newReqId = vshl::utilities::uuid::generateUUID(); + + // Create a new request and start listening. + shared_ptr newRequest = VRRequest::create(mLogger, mApi, newReqId, voiceAgent); + + // mLogger->log(Level::DEBUG, TAG, "Starting request with ID: " + newReqId); + if (!newRequest->startListening()) { + mLogger->log(Level::ERROR, TAG, "Failed to start listening."); + 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()) { + auto vrRequestsIt = mVRRequests.begin(); + while (vrRequestsIt != mVRRequests.end()) { + if (!vrRequestsIt->second->cancel()) { + mLogger->log(Level::WARNING, TAG, "Failed to cancel request: " + vrRequestsIt->first); + } + vrRequestsIt++; + } + mVRRequests.clear(); + } +} + +unordered_map> VRRequestProcessorDelegate::getAllRequests() { + return mVRRequests; +} + +void VRRequestProcessorDelegate::setDefaultVoiceAgent(shared_ptr voiceAgent) { + mDefaultVoiceAgent = voiceAgent; +} + +shared_ptr VRRequestProcessorDelegate::getDefaultVoiceAgent() const { + return mDefaultVoiceAgent; +} + +} // namespace core +} // namespace vshl diff --git a/src/plugins/core/test/VRRequestProcessorTest.cpp b/src/plugins/core/test/VRRequestProcessorTest.cpp new file mode 100644 index 0000000..c1a37df --- /dev/null +++ b/src/plugins/core/test/VRRequestProcessorTest.cpp @@ -0,0 +1,213 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "core/VRRequestProcessor.h" +#include "voiceagents/include/VoiceAgent.h" +#include "voiceagents/test/VoiceAgentsTestData.h" + +#include "test/common/ConsoleLogger.h" +#include "test/mocks/AFBApiMock.h" + +using namespace vshl::core; +using namespace vshl::voiceagents; +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class VRRequestProcessorTest : public ::testing::Test { +protected: + void SetUp() override { + mConsoleLogger = std::make_shared(); + mAfbApi = std::make_shared<::testing::StrictMock>(); + + auto vaTestData = *(getVoiceAgentsTestData().begin()); + mVoiceAgent = VoiceAgent::create( + mConsoleLogger, + vaTestData.id, + vaTestData.name, + vaTestData.description, + vaTestData.api, + vaTestData.vendor, + vaTestData.activeWakeword, + vaTestData.isActive, + vaTestData.wakewords); + + mVRReqProcessorDelegate = VRRequestProcessorDelegate::create(mConsoleLogger, mAfbApi); + mVRRequestProcessor = VRRequestProcessor::create(mConsoleLogger, mVRReqProcessorDelegate); + } + + std::shared_ptr<::testing::StrictMock> mAfbApi; + std::shared_ptr mConsoleLogger; + std::shared_ptr mVoiceAgent; + + std::shared_ptr mVRReqProcessorDelegate; + std::shared_ptr mVRRequestProcessor; +}; + +TEST_F(VRRequestProcessorTest, initializesCorrectly) { + ASSERT_NE(mVRRequestProcessor, nullptr); +} + +TEST_F(VRRequestProcessorTest, startListeningFailsOnLackOfDefaultAgent) { + auto requestId = mVRRequestProcessor->startListening(); + ASSERT_EQ(requestId, ""); +} + +TEST_F(VRRequestProcessorTest, startListeningAndCancelWorks) { + mVRReqProcessorDelegate->setDefaultVoiceAgent(mVoiceAgent); + + { + ::testing::InSequence dummy; + + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_STARTLISTENING, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_CANCEL, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + } + + mVRRequestProcessor->startListening(); + auto requests = mVRReqProcessorDelegate->getAllRequests(); + ASSERT_EQ(requests.size(), 1); + + mVRRequestProcessor->cancel(); + requests = mVRReqProcessorDelegate->getAllRequests(); + ASSERT_EQ(requests.size(), 0); +} + +TEST_F(VRRequestProcessorTest, requestIsCancelledOnObjectDestruction) { + auto delegate = VRRequestProcessorDelegate::create(mConsoleLogger, mAfbApi); + auto processor = VRRequestProcessor::create(mConsoleLogger, delegate); + + delegate->setDefaultVoiceAgent(mVoiceAgent); + + { + ::testing::InSequence dummy; + + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_STARTLISTENING, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_CANCEL, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + } + + auto requestId = processor->startListening(); + ASSERT_NE(requestId, ""); + + auto requests = delegate->getAllRequests(); + ASSERT_EQ(requests.size(), 1); +} + +TEST_F(VRRequestProcessorTest, backToBackStartListeningCancelsEarlierRequest) { + mVRReqProcessorDelegate->setDefaultVoiceAgent(mVoiceAgent); + + { + ::testing::InSequence dummy; + + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_STARTLISTENING, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_CANCEL, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_STARTLISTENING, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_CANCEL, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + } + + mVRRequestProcessor->startListening(); + auto requests = mVRReqProcessorDelegate->getAllRequests(); + ASSERT_EQ(requests.size(), 1); + + mVRRequestProcessor->startListening(); + requests = mVRReqProcessorDelegate->getAllRequests(); + ASSERT_EQ(requests.size(), 1); + + mVRRequestProcessor->cancel(); + requests = mVRReqProcessorDelegate->getAllRequests(); + ASSERT_EQ(requests.size(), 0); +} + +} // namespace test +} // namespace vshl \ No newline at end of file diff --git a/src/plugins/core/test/VRRequestTest.cpp b/src/plugins/core/test/VRRequestTest.cpp new file mode 100644 index 0000000..b1cd0a6 --- /dev/null +++ b/src/plugins/core/test/VRRequestTest.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "core/include/VRRequest.h" +#include "voiceagents/include/VoiceAgent.h" +#include "voiceagents/test/VoiceAgentsTestData.h" + +#include "test/common/ConsoleLogger.h" +#include "test/mocks/AFBApiMock.h" + +using namespace vshl::core; +using namespace vshl::voiceagents; +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class VRRequestTest : public ::testing::Test { +protected: + void SetUp() override { + mRequestId = "Req-0001"; + + mConsoleLogger = std::make_shared(); + mAfbApi = std::make_shared<::testing::StrictMock>(); + + auto vaTestData = *(getVoiceAgentsTestData().begin()); + mVoiceAgent = VoiceAgent::create( + mConsoleLogger, + vaTestData.id, + vaTestData.name, + vaTestData.description, + vaTestData.api, + vaTestData.vendor, + vaTestData.activeWakeword, + vaTestData.isActive, + vaTestData.wakewords); + + mVRRequest = VRRequest::create(mConsoleLogger, mAfbApi, mRequestId, mVoiceAgent); + } + + std::string mRequestId; + std::shared_ptr<::testing::StrictMock> mAfbApi; + std::shared_ptr mConsoleLogger; + std::shared_ptr mVoiceAgent; + + std::shared_ptr mVRRequest; +}; + +TEST_F(VRRequestTest, initializesCorrectly) { + ASSERT_NE(mVRRequest, nullptr); +} + +TEST_F(VRRequestTest, failsCreationOnInvalidParams) { + auto vrRequest = VRRequest::create(mConsoleLogger, nullptr, mRequestId, mVoiceAgent); + ASSERT_EQ(vrRequest, nullptr); + + vrRequest = VRRequest::create(nullptr, mAfbApi, mRequestId, mVoiceAgent); + ASSERT_EQ(vrRequest, nullptr); +} + +TEST_F(VRRequestTest, startsListeningSuccessfully) { + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), + VRRequest::VA_VERB_STARTLISTENING, + ::testing::_, + ::testing::_, + ::testing::_, + ::testing::_)) + .Times(1); + + ASSERT_TRUE(mVRRequest->startListening()); +} + +TEST_F(VRRequestTest, cancelsSuccessfully) { + EXPECT_CALL( + *mAfbApi, + callSync( + mVoiceAgent->getApi(), VRRequest::VA_VERB_CANCEL, ::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .Times(1); + + ASSERT_TRUE(mVRRequest->cancel()); +} + +} // namespace test +} // namespace vshl \ No newline at end of file diff --git a/src/plugins/interfaces/afb/IAFBApi.h b/src/plugins/interfaces/afb/IAFBApi.h new file mode 100644 index 0000000..cd98006 --- /dev/null +++ b/src/plugins/interfaces/afb/IAFBApi.h @@ -0,0 +1,98 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_COMMON_INTERFACES_AFBAPI_H_ +#define VSHL_COMMON_INTERFACES_AFBAPI_H_ + +#include +#include + +#include + +using namespace std; + +namespace vshl { +namespace common { +namespace interfaces { + +/** + * Interface to represent AFB Request. + */ +class IAFBRequest { +public: + /** + * Gets the native request object. + */ + virtual void* getNativeRequest() = 0; +}; + +/** + * Interface to encapsulate all AFB (AGL Application Framework Binding) + * functions. + */ +class IAFBApi { +public: + /** + * Interface to represent AFB Event + */ + class IAFBEvent { + public: + /** + * Gets human readable name of the event. + */ + virtual std::string getName() const = 0; + + /** + * Returns true if event is valid. False otherwise. + */ + virtual bool isValid() = 0; + + /** + * Publish event to all observers. + * + * @return The number of observers that received the event. + */ + virtual int publishEvent(struct json_object* payload) = 0; + + /** + * Subscribe to the event + * + * @c request Party interested in the event. + */ + virtual bool subscribe(IAFBRequest& request) = 0; + + /** + * Unsubscribe to the event + * + * @c request Party no longer interested in the event. + */ + virtual bool unsubscribe(IAFBRequest& request) = 0; + }; + + virtual std::shared_ptr createEvent(const std::string& eventName) = 0; + + virtual int callSync( + const std::string& api, + const std::string& verb, + struct json_object* request, + struct json_object** result, + std::string& error, + std::string& info) = 0; +}; + +} // namespace interfaces +} // namespace common +} // namespace vshl + +#endif // VSHL_COMMON_INTERFACES_AFBAPI_H_ diff --git a/src/plugins/interfaces/capabilities/ICapability.h b/src/plugins/interfaces/capabilities/ICapability.h new file mode 100644 index 0000000..4b134b7 --- /dev/null +++ b/src/plugins/interfaces/capabilities/ICapability.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +#ifndef VSHL_COMMON_INTERFACES_ICAPABILITY_H_ +#define VSHL_COMMON_INTERFACES_ICAPABILITY_H_ + +#include +#include + +using namespace std; + +namespace vshl { +namespace common { +namespace interfaces { + +/* + * This interface defines the structure for a specific voiceagent capability. + */ +class ICapability { +public: + /* + * Returns the capability's name. + */ + virtual string getName() const = 0; + + /* + * Returns the list of upstream messages. + */ + virtual list getUpstreamMessages() const = 0; + + /* + * Returns the list of downstream messages + */ + virtual list getDownstreamMessages() const = 0; + + /** + * Virtual destructor to assure proper cleanup of derived types. + */ + virtual ~ICapability() = default; +}; + +} // namespace interfaces +} // namespace common +} // namespace vshl + +#endif // VSHL_COMMON_INTERFACES_ICAPABILITY_H_ diff --git a/src/plugins/interfaces/utilities/events/IEventFilter.h b/src/plugins/interfaces/utilities/events/IEventFilter.h new file mode 100644 index 0000000..33d8202 --- /dev/null +++ b/src/plugins/interfaces/utilities/events/IEventFilter.h @@ -0,0 +1,46 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_COMMON_INTERFACES_IEVENTFILTER_H_ +#define VSHL_COMMON_INTERFACES_IEVENTFILTER_H_ + +#include + +using namespace std; + +namespace vshl { +namespace common { +namespace interfaces { +/* + * This is an abstract class that is responsible for filtering the events + * that are delivered to the high level voice service from apps or voiceagents. + */ +class IEventFilter { +public: + // Name of the event filter. + virtual string getName() = 0; + + // Every event filter needs to implement this method and + // return true if consuming the event or false otherwise. + virtual bool onIncomingEvent(const string eventName, const string voiceAgentId, const string payload) = 0; + + // Destructor + virtual ~IEventFilter() = default; +}; + +} // namespace interfaces +} // namespace common +} // namespace vshl + +#endif // VSHL_COMMON_INTERFACES_IEVENTFILTER_H_ diff --git a/src/plugins/interfaces/utilities/logging/ILogger.h b/src/plugins/interfaces/utilities/logging/ILogger.h new file mode 100644 index 0000000..a2618bc --- /dev/null +++ b/src/plugins/interfaces/utilities/logging/ILogger.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_COMMON_INTERFACES_LOGGER_H_ +#define VSHL_COMMON_INTERFACES_LOGGER_H_ + +#include + +namespace vshl { +namespace common { +namespace interfaces { + +class ILogger { +public: + enum Level { + DEBUG, + INFO, + WARNING, + ERROR, + NOTICE, + }; + + virtual void log(Level level, const std::string &tag, + const std::string &message) = 0; +}; + +} // namespace interfaces +} // namespace common +} // namespace vshl + +#endif // VSHL_COMMON_INTERFACES_LOGGER_H_ diff --git a/src/plugins/interfaces/voiceagents/IVoiceAgent.h b/src/plugins/interfaces/voiceagents/IVoiceAgent.h new file mode 100644 index 0000000..367ad72 --- /dev/null +++ b/src/plugins/interfaces/voiceagents/IVoiceAgent.h @@ -0,0 +1,95 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +#ifndef VSHL_COMMON_INTERFACES_IVOICEAGENT_H_ +#define VSHL_COMMON_INTERFACES_IVOICEAGENT_H_ + +#include +#include +#include + +using namespace std; + +namespace vshl { +namespace common { +namespace interfaces { + +/* + * This interface define the structure for VoiceAgent Information. + * The implementation of this structure is owned by voiceagents module. + */ +class IVoiceAgent { +public: + /* + * Set the active wakeword for this voiceagent + */ + virtual bool setActiveWakeWord(const string &wakeword) = 0; + + /* + * Sets the activation state of this voiceagent + */ + virtual void setIsActive(bool active) = 0; + + /* + * Returns the voiceagent's ID. + */ + virtual string getId() const = 0; + + /* + * Returns the voiceagent's name. + */ + virtual string getName() const = 0; + + /* + * Returns the voiceagent's description. + */ + virtual string getDescription() const = 0; + + /* + * Returns the voiceagent's API. + */ + virtual string getApi() const = 0; + + /* + * Returns the voiceagent's vendor information/ + */ + virtual string getVendor() const = 0; + + /* + * Returns the list of wakewords mapped to the voiceagent. + */ + virtual shared_ptr> getWakeWords() const = 0; + + /* + * Returns true if the voiceagent is active. False otherwise. + */ + virtual bool isActive() const = 0; + + /* + * Returns the active wakeword for the voiceagent. + */ + virtual string getActiveWakeword() const = 0; + + /** + * Virtual destructor to assure proper cleanup of derived types. + */ + virtual ~IVoiceAgent() = default; +}; + +} // namespace interfaces +} // namespace common +} // namespace vshl + +#endif // VSHL_COMMON_INTERFACES_IVOICEAGENT_H_ diff --git a/src/plugins/interfaces/voiceagents/IVoiceAgentsChangeObserver.h b/src/plugins/interfaces/voiceagents/IVoiceAgentsChangeObserver.h new file mode 100644 index 0000000..e552ab5 --- /dev/null +++ b/src/plugins/interfaces/voiceagents/IVoiceAgentsChangeObserver.h @@ -0,0 +1,80 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +#ifndef VSHL_COMMON_INTERFACES_IVOICEAGENTSCHANGEOBSERVER_H_ +#define VSHL_COMMON_INTERFACES_IVOICEAGENTSCHANGEOBSERVER_H_ + +#include +#include + +#include "interfaces/voiceagents/IVoiceAgent.h" + +using namespace std; + +namespace vshl { +namespace common { +namespace interfaces { + +/* + * This interface is used to observe changes to the voiceagents datastore. + * The voiceagents data store is contained in the voiceagents module. + */ +class IVoiceAgentsChangeObserver { +public: + /** + * This method notifies the observers that the default voiceagent selection + * has been updated. + */ + virtual void + OnDefaultVoiceAgentChanged(shared_ptr defaultVoiceAgent) = 0; + + /** + * This method notifies the observers that a new voiceagent has been added. + */ + virtual void OnVoiceAgentAdded(shared_ptr voiceAgent) = 0; + + /** + * This method notifies the observers that a voiceagent is removed. + */ + virtual void OnVoiceAgentRemoved(shared_ptr voiceAgent) = 0; + + /** + * This method notifies the observers that active wakeword for a voiceagent is + * updated. + */ + virtual void + OnVoiceAgentActiveWakeWordChanged(shared_ptr voiceAgent) = 0; + + /** + * This method notifies the observers that the voiceagent has been activated. + */ + virtual void OnVoiceAgentActivated(shared_ptr voiceAgent) = 0; + + /** + * This method notifies the observers that the voiceagent has been activated. + */ + virtual void OnVoiceAgentDeactivated(shared_ptr voiceAgent) = 0; + + /** + * Virtual destructor to assure proper cleanup of derived types. + */ + virtual ~IVoiceAgentsChangeObserver() = default; +}; + +} // namespace interfaces +} // namespace common +} // namespace vshl + +#endif // VSHL_COMMON_INTERFACES_IVOICEAGENTSCHANGEOBSERVER_H_ diff --git a/src/plugins/test/common/ConsoleLogger.cpp b/src/plugins/test/common/ConsoleLogger.cpp new file mode 100644 index 0000000..d4f9eef --- /dev/null +++ b/src/plugins/test/common/ConsoleLogger.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "test/common/ConsoleLogger.h" + +namespace vshl { +namespace test { +namespace common { + +void ConsoleLogger::log(Level level, const std::string& tag, const std::string& message) { + string format_msg = "Tag: " + tag + ", message: " + message; + std::cout << format_msg << std::endl; +} + +} // namespace common +} // namespace test +} // namespace vshl diff --git a/src/plugins/test/common/ConsoleLogger.h b/src/plugins/test/common/ConsoleLogger.h new file mode 100644 index 0000000..11bc0d8 --- /dev/null +++ b/src/plugins/test/common/ConsoleLogger.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_TEST_COMMON_CONSOLE_LOGGER_H_ +#define VSHL_TEST_COMMON_CONSOLE_LOGGER_H_ + +#include "interfaces/utilities/logging/ILogger.h" + +using namespace std; + +namespace vshl { +namespace test { +namespace common { + +class ConsoleLogger : public vshl::common::interfaces::ILogger { +public: + // ILogger interface + void log(Level level, const std::string &tag, + const std::string &message) override; +}; + +} // namespace common +} // namespace test +} // namespace vshl + +#endif // VSHL_TEST_COMMON_CONSOLE_LOGGER_H_ diff --git a/src/plugins/test/mocks/AFBApiMock.h b/src/plugins/test/mocks/AFBApiMock.h new file mode 100644 index 0000000..46e2e99 --- /dev/null +++ b/src/plugins/test/mocks/AFBApiMock.h @@ -0,0 +1,41 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_TEST_MOCKS_AFBAPIMOCK_H_ +#define VSHL_TEST_MOCKS_AFBAPIMOCK_H_ + +#include + +#include "interfaces/afb/IAFBApi.h" + +namespace vshl { +namespace test { + +class AFBApiMock : public vshl::common::interfaces::IAFBApi { +public: + MOCK_METHOD1(createEvent, std::shared_ptr(const std::string& eventName)); + MOCK_METHOD6( + callSync, + int(const std::string& api, + const std::string& verb, + struct json_object* request, + struct json_object** result, + std::string& error, + std::string& info)); +}; + +} // namespace test +} // namespace vshl + +#endif // VSHL_TEST_MOCKS_AFBAPIMOCK_H_ \ No newline at end of file diff --git a/src/plugins/test/mocks/AFBEventMock.h b/src/plugins/test/mocks/AFBEventMock.h new file mode 100644 index 0000000..3d78e9f --- /dev/null +++ b/src/plugins/test/mocks/AFBEventMock.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_TEST_MOCKS_AFBEVENTMOCK_H_ +#define VSHL_TEST_MOCKS_AFBEVENTMOCK_H_ + +#include + +#include "interfaces/afb/IAFBApi.h" + +namespace vshl { +namespace test { + +class AFBEventMock : public vshl::common::interfaces::IAFBApi::IAFBEvent { +public: + void setName(const std::string& name) { + mName = name; + } + + std::string getName() const override { + return mName; + } + + MOCK_METHOD0(isValid, bool()); + MOCK_METHOD1(publishEvent, int(struct json_object* payload)); + MOCK_METHOD1(subscribe, bool(vshl::common::interfaces::IAFBRequest& request)); + MOCK_METHOD1(unsubscribe, bool(vshl::common::interfaces::IAFBRequest& request)); + +private: + std::string mName; +}; + +} // namespace test +} // namespace vshl + +#endif // VSHL_TEST_MOCKS_AFBEVENTMOCK_H_ \ No newline at end of file diff --git a/src/plugins/test/mocks/AFBRequestMock.h b/src/plugins/test/mocks/AFBRequestMock.h new file mode 100644 index 0000000..5557565 --- /dev/null +++ b/src/plugins/test/mocks/AFBRequestMock.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_TEST_MOCKS_AFBREQUESTMOCK_H_ +#define VSHL_TEST_MOCKS_AFBREQUESTMOCK_H_ + +#include + +#include "interfaces/afb/IAFBApi.h" + +namespace vshl { +namespace test { + +class AFBRequestMock : public vshl::common::interfaces::IAFBRequest { +public: + MOCK_METHOD0(getNativeRequest, void*()); +}; + +} // namespace test +} // namespace vshl + +#endif // VSHL_TEST_MOCKS_AFBREQUESTMOCK_H_ \ No newline at end of file diff --git a/src/plugins/test/mocks/CapabilityMock.h b/src/plugins/test/mocks/CapabilityMock.h new file mode 100644 index 0000000..a2201df --- /dev/null +++ b/src/plugins/test/mocks/CapabilityMock.h @@ -0,0 +1,35 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_TEST_MOCKS_CAPABILITYMOCK_H_ +#define VSHL_TEST_MOCKS_CAPABILITYMOCK_H_ + +#include + +#include "interfaces/capabilities/ICapability.h" + +namespace vshl { +namespace test { + +class CapabilityMock : public vshl::common::interfaces::ICapability { +public: + MOCK_CONST_METHOD0(getName, std::string()); + MOCK_CONST_METHOD0(getUpstreamMessages, std::list()); + MOCK_CONST_METHOD0(getDownstreamMessages, std::list()); +}; + +} // namespace test +} // namespace vshl + +#endif // VSHL_TEST_MOCKS_CAPABILITYMOCK_H_ \ No newline at end of file diff --git a/src/plugins/test/mocks/VoiceAgentsChangeObserverMock.h b/src/plugins/test/mocks/VoiceAgentsChangeObserverMock.h new file mode 100644 index 0000000..6edeea2 --- /dev/null +++ b/src/plugins/test/mocks/VoiceAgentsChangeObserverMock.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_TEST_MOCKS_VOICEAGENTSCHANGEOBSERVERMOCK_H_ +#define VSHL_TEST_MOCKS_VOICEAGENTSCHANGEOBSERVERMOCK_H_ + +#include + +#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h" + +namespace vshl { +namespace test { + +class VoiceAgentsChangeObserverMock : public vshl::common::interfaces::IVoiceAgentsChangeObserver { +public: + MOCK_METHOD1(OnDefaultVoiceAgentChanged, void(shared_ptr defaultVoiceAgent)); + MOCK_METHOD1(OnVoiceAgentAdded, void(shared_ptr voiceAgent)); + MOCK_METHOD1(OnVoiceAgentRemoved, void(shared_ptr voiceAgent)); + MOCK_METHOD1(OnVoiceAgentActiveWakeWordChanged, void(shared_ptr voiceAgent)); + MOCK_METHOD1(OnVoiceAgentActivated, void(shared_ptr voiceAgent)); + MOCK_METHOD1(OnVoiceAgentDeactivated, void(shared_ptr voiceAgent)); +}; + +} // namespace test +} // namespace vshl + +#endif // VSHL_TEST_MOCKS_VOICEAGENTSCHANGEOBSERVERMOCK_H_ \ No newline at end of file diff --git a/src/plugins/utilities/events/EventRouter.cpp b/src/plugins/utilities/events/EventRouter.cpp new file mode 100644 index 0000000..999c3dd --- /dev/null +++ b/src/plugins/utilities/events/EventRouter.cpp @@ -0,0 +1,68 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "utilities/events/EventRouter.h" + +static string TAG = "vshl::utilities::events::EventRouter"; + +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace utilities { +namespace events { + +unique_ptr EventRouter::create(shared_ptr logger) { + return std::unique_ptr(new EventRouter(logger)); +} + +EventRouter::EventRouter(shared_ptr logger) : mLogger(logger) { +} + +EventRouter::~EventRouter() { + mEventFilters.clear(); +} + +bool EventRouter::handleIncomingEvent(const string eventName, const string voiceAgentId, const string payload) { + for (auto eventFilter : mEventFilters) { + if (eventFilter->onIncomingEvent(eventName, voiceAgentId, payload)) { + return true; + } + } + + return false; +} + +bool EventRouter::addEventFilter(shared_ptr filter) { + if (!filter) { + mLogger->log(Level::ERROR, TAG, "Failed to add event filter. Invalid arguments."); + return false; + } + + mEventFilters.insert(filter); + return true; +} + +bool EventRouter::removeEventFilter(shared_ptr filter) { + if (!filter) { + mLogger->log(Level::ERROR, TAG, "Failed to add remove filter. Invalid arguments."); + return false; + } + + mEventFilters.erase(filter); + return true; +} + +} // namespace events +} // namespace utilities +} // namespace vshl diff --git a/src/plugins/utilities/events/EventRouter.h b/src/plugins/utilities/events/EventRouter.h new file mode 100644 index 0000000..fd7f0c2 --- /dev/null +++ b/src/plugins/utilities/events/EventRouter.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_UTILITIES_EVENTS_EVENTMANAGER_H_ +#define VSHL_UTILITIES_EVENTS_EVENTMANAGER_H_ + +#include +#include +#include + +#include "interfaces/utilities/events/IEventFilter.h" +#include "interfaces/utilities/logging/ILogger.h" + +using namespace std; + +namespace vshl { +namespace utilities { +namespace events { +/* + * This class is responsible for routing incoming events to + * the appropriate event listener for consumption. + * Note: The listeners should implement the IEventFilter class. + */ +class EventRouter { +public: + static unique_ptr create(shared_ptr logger); + + // Destructor + ~EventRouter(); + + // Add event filter as listerner. + bool addEventFilter(shared_ptr filter); + + // Remove event filter as listerner. + bool removeEventFilter(shared_ptr filter); + + // This method is called by the controller for routing + // the event to appropriate listener. + bool handleIncomingEvent(const string eventName, const string voiceAgentId, const string payload); + +private: + EventRouter(shared_ptr logger); + + // set of event filters. + unordered_set> mEventFilters; + + // Logger + shared_ptr mLogger; +}; + +} // namespace events +} // namespace utilities +} // namespace vshl + +#endif // VSHL_UTILITIES_EVENTS_EVENTROUTER_H_ diff --git a/src/plugins/utilities/logging/Logger.cpp b/src/plugins/utilities/logging/Logger.cpp new file mode 100644 index 0000000..6374e19 --- /dev/null +++ b/src/plugins/utilities/logging/Logger.cpp @@ -0,0 +1,56 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "utilities/logging/Logger.h" + +namespace vshl { +namespace utilities { +namespace logging { + +// Constructor +Logger::Logger(AFB_ApiT api) { + mApi = api; +} + +unique_ptr Logger::create(AFB_ApiT api) { + auto logger = std::unique_ptr(new Logger(api)); + return logger; +} + +void Logger::log(Level level, const std::string& tag, const std::string& message) { + string format_msg = "Tag: " + tag + ", message: " + message; + switch (level) { + case Level::NOTICE: + AFB_ApiNotice(mApi, format_msg.c_str()); + break; + case Level::WARNING: + AFB_ApiWarning(mApi, format_msg.c_str()); + break; + case Level::DEBUG: + AFB_ApiDebug(mApi, format_msg.c_str()); + break; + case Level::ERROR: + AFB_ApiError(mApi, format_msg.c_str()); + break; + case Level::INFO: + AFB_ApiInfo(mApi, format_msg.c_str()); + break; + default: + break; + } +} + +} // namespace logging +} // namespace utilities +} // namespace vshl diff --git a/src/plugins/utilities/logging/Logger.h b/src/plugins/utilities/logging/Logger.h new file mode 100644 index 0000000..79c89a5 --- /dev/null +++ b/src/plugins/utilities/logging/Logger.h @@ -0,0 +1,53 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_UTILITIES_LOGGING_LOGGER_H_ +#define VSHL_UTILITIES_LOGGING_LOGGER_H_ + +#include + +extern "C" { +#define AFB_BINDING_VERSION 3 +#include "afb-definitions.h" +#include "ctl-plugin.h" +}; + +#include "interfaces/utilities/logging/ILogger.h" + +using namespace std; + +namespace vshl { +namespace utilities { +namespace logging { + +class Logger : public vshl::common::interfaces::ILogger { +public: + static std::unique_ptr create(AFB_ApiT api); + + // ILogger interface + void log(Level level, const std::string &tag, + const std::string &message) override; + +private: + Logger(AFB_ApiT api); + + // Binding API reference + AFB_ApiT mApi; +}; + +} // namespace logging +} // namespace utilities +} // namespace vshl + +#endif // VSHL_UTILITIES_LOGGING_LOGGER_H_ diff --git a/src/plugins/utilities/uuid/UUIDGeneration.cpp b/src/plugins/utilities/uuid/UUIDGeneration.cpp new file mode 100644 index 0000000..bb03fc6 --- /dev/null +++ b/src/plugins/utilities/uuid/UUIDGeneration.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +// This code is copied from https://github.com/alexa/avs-device-sdk/ +// Minor modifications are made to the original file. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utilities/uuid/UUIDGeneration.h" + +namespace vshl { +namespace utilities { +namespace uuid { + +/// String to identify log entries originating from this file. +static const std::string TAG("UUIDGeneration"); + +/// The UUID version (Version 4), shifted into the correct position in the byte. +static const uint8_t UUID_VERSION_VALUE = 4 << 4; + +/// The UUID variant (Variant 1), shifted into the correct position in the byte. +static const size_t UUID_VARIANT_VALUE = 2 << 6; + +/// Separator used between UUID fields. +static const std::string SEPARATOR("-"); + +/// Number of bits in the replacement value. +static const size_t MAX_NUM_REPLACEMENT_BITS = CHAR_BIT; + +/// Number of bits in a hex digit. +static const size_t BITS_IN_HEX_DIGIT = 4; + +/** + * Randomly generate a string of hex digits. Before the conversion of hex to string, + * the function allows replacement of the bits of the first two generated hex digits. + * Replacement happens starting at the most significant, to the least significant bit. + * + * @param ibe A random number generator. + * @param numDigits The number of hex digits [0-9],[a-f] to generate. + * @param replacementBits The replacement value for up to the first two digits generated. + * @param numReplacementBits The number of bits of @c replacementBits to use. + * + * @return A hex string of length @c numDigits. + */ +static const std::string generateHexWithReplacement( + std::independent_bits_engine& ibe, + unsigned int numDigits, + uint8_t replacementBits, + unsigned short numReplacementBits) { + if (numReplacementBits > MAX_NUM_REPLACEMENT_BITS) { + return ""; + } + + if (numReplacementBits > (numDigits * BITS_IN_HEX_DIGIT)) { + return ""; + } + + // Makes assumption that 1 digit = 4 bits. + std::vector bytes(ceil(numDigits / 2.0)); + std::generate(bytes.begin(), bytes.end(), std::ref(ibe)); + + // Replace the specified number of bits from the first byte. + bytes.at(0) &= (0xff >> numReplacementBits); + replacementBits &= (0xff << (MAX_NUM_REPLACEMENT_BITS - numReplacementBits)); + bytes.at(0) |= replacementBits; + + std::ostringstream oss; + for (const auto& byte : bytes) { + oss << std::hex << std::setfill('0') << std::setw(2) << static_cast(byte); + } + + std::string bytesText = oss.str(); + // Remove the last digit for odd numDigits case. + bytesText.resize(numDigits); + + return bytesText; +} + +/** + * Randomly generate a string of hex digits. + * + * @param ibe A random number generator. + * @param numDigits The number of hex digits [0-9],[a-f] to generate. + * + * @return A hex string of length @c numDigits. + */ +static const std::string generateHex( + std::independent_bits_engine& ibe, + unsigned int numDigits) { + return generateHexWithReplacement(ibe, numDigits, 0, 0); +} + +const std::string generateUUID() { + static bool seeded = false; + static std::independent_bits_engine ibe; + static std::mutex mutex; + std::unique_lock lock(mutex); + if (!seeded) { + std::random_device rd; + ibe.seed( + rd() + + std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()) + .count()); + seeded = true; + } + + std::ostringstream uuidText; + uuidText << generateHex(ibe, 8) << SEPARATOR << generateHex(ibe, 4) << SEPARATOR + << generateHexWithReplacement(ibe, 4, UUID_VERSION_VALUE, 4) << SEPARATOR + << generateHexWithReplacement(ibe, 4, UUID_VARIANT_VALUE, 2) << SEPARATOR << generateHex(ibe, 12); + + lock.unlock(); + + return uuidText.str(); +} + +} // namespace uuid +} // namespace utilities +} // namespace vshl diff --git a/src/plugins/utilities/uuid/UUIDGeneration.h b/src/plugins/utilities/uuid/UUIDGeneration.h new file mode 100644 index 0000000..7af9cb1 --- /dev/null +++ b/src/plugins/utilities/uuid/UUIDGeneration.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +// This code is copied from https://github.com/alexa/avs-device-sdk/ +// Minor modifications are made to the original file. + +#ifndef VSHL_UTILITIES_UUID_UUIDGENERATION_H_ +#define VSHL_UTILITIES_UUID_UUIDGENERATION_H_ + +#include + +namespace vshl { +namespace utilities { +namespace uuid { + +/** + * Generates a variant 1, version 4 universally unique identifier (UUID) consisting of 32 hexadecimal digits. + * The UUID generated is of the format xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx where M indicates the version, and the two + * most significant bits of N indicates the variant. M is 0100 (binary) for version 4 and N is 10xx(binary) for + * variant 1. + * @see https://tools.ietf.org/html/rfc4122. + * + * @return A uuid as a string. + */ +const std::string generateUUID(); + +} // namespace uuid +} // namespace utilities +} // namespace vshl + +#endif // VSHL_UTILITIES_UUID_UUIDGENERATION_H_ \ No newline at end of file diff --git a/src/plugins/voiceagents/VoiceAgentEventNames.h b/src/plugins/voiceagents/VoiceAgentEventNames.h new file mode 100644 index 0000000..4575528 --- /dev/null +++ b/src/plugins/voiceagents/VoiceAgentEventNames.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_ +#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_ + +#include +#include + +using namespace std; + +namespace vshl { +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 list VSHL_EVENTS = { + VSHL_EVENT_AUTH_STATE_EVENT, VSHL_EVENT_CONNECTION_STATE_EVENT, + VSHL_EVENT_DIALOG_STATE_EVENT, +}; + +} // namespace voiceagents +} // namespace vshl + +#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_ diff --git a/src/plugins/voiceagents/VoiceAgentsDataManager.h b/src/plugins/voiceagents/VoiceAgentsDataManager.h new file mode 100644 index 0000000..a4c9143 --- /dev/null +++ b/src/plugins/voiceagents/VoiceAgentsDataManager.h @@ -0,0 +1,135 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_ +#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_ + +#include +#include +#include +#include + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/events/IEventFilter.h" +#include "interfaces/utilities/logging/ILogger.h" +#include "interfaces/voiceagents/IVoiceAgent.h" +#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h" +#include "voiceagents/include/VoiceAgent.h" +#include "voiceagents/include/VoiceAgentEventsHandler.h" + +namespace vshl { +namespace voiceagents { +/* + * This class implements the data model for voiceagents. + * Supports add, remove and query operations on voiceagent data. + * Notifies the observers of the changes in the voiceagents data model. + */ +class VoiceAgentsDataManager { +public: + // Create a VoiceAgentsDataManager. + static std::unique_ptr create( + shared_ptr logger, + shared_ptr afbApi); + + /** + * Activates the list of voiceagents. + * + * @return Number of activated agents + */ + uint32_t activateVoiceAgents(const unordered_set& activeVoiceAgentIds); + + /** + * Deactivates the list of voiceagents. + * + * @return Number of de-activated agents + */ + uint32_t deactivateVoiceAgents(const unordered_set& inactiveVoiceAgentIds); + + // Sets the default voiceagent. + bool setDefaultVoiceAgent(const string& voiceAgentId); + + // Sets the default voiceagent. + std::string getDefaultVoiceAgent(); + + // Sets the active wakeword for the voiceagent. + bool setActiveWakeWord(const string& voiceAgentId, const string& wakeword); + + // Adds a new voiceagent to the cache and also persists the information in a + // database. + // This call would notify all the observers about the new voiceagent addition. + bool addNewVoiceAgent( + const string& id, + const string& name, + const string& description, + const string& api, + const string& vendor, + const string& activeWakeword, + const bool isActive, + const shared_ptr> wakewords); + + // Removes the voiceagent from thecache and also from persistent database. + // This call would notify all the observers about the removal of the + // voiceagent. + bool removeVoiceAgent(const string& voiceAgentId); + + // Returns the set of all voice agents in @c VoiceAgentsDataManger cache + std::set> getAllVoiceAgents(); + + // Returns the event filter that belongs to the core module. + shared_ptr getEventFilter() const; + + // Subscribe to an event coming from the voiceagent. + bool subscribeToVshlEventFromVoiceAgent( + vshl::common::interfaces::IAFBRequest& request, + const string eventName, + const string voiceagentId); + + // Adds a new voiceagent change observer. + bool addVoiceAgentsChangeObserver(shared_ptr observer); + + // Removes the voiceagent change observer from the list. + bool removeVoiceAgentsChangeObserver(shared_ptr observer); + + // Destructor + ~VoiceAgentsDataManager(); + +private: + // Constructor + VoiceAgentsDataManager( + shared_ptr logger, + shared_ptr afbApi); + + // Binding API reference + shared_ptr mAfbApi; + + // A list of all the voiceagent change observers + unordered_set> mVoiceAgentChangeObservers; + + // A map of voiceagents grouped by ID + unordered_map> mVoiceAgents; + + // Voiceagent event handler. + shared_ptr mVoiceAgentEventsHandler; + + // Default voiceagent + string mDefaultVoiceAgentId; + + // Logger + shared_ptr mLogger; +}; + +} // namespace voiceagents +} // namespace vshl + +#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_ diff --git a/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp b/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp new file mode 100644 index 0000000..626a7fc --- /dev/null +++ b/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp @@ -0,0 +1,272 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "voiceagents/VoiceAgentsDataManager.h" + +#include "voiceagents/include/VoiceAgentEventsHandler.h" + +static string TAG = "vshl::voiceagents::VoiceAgentsDataManager"; + +/** + * Specifies the severity level of a log message + */ +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace voiceagents { + +std::unique_ptr VoiceAgentsDataManager::create( + shared_ptr logger, + shared_ptr afbApi) { + return std::unique_ptr(new VoiceAgentsDataManager(logger, afbApi)); +} + +// Constructor +VoiceAgentsDataManager::VoiceAgentsDataManager( + shared_ptr logger, + shared_ptr afbApi) : + mLogger(logger), + mAfbApi(afbApi) { + mVoiceAgentEventsHandler = VoiceAgentEventsHandler::create(mLogger, mAfbApi); +} + +// Destructor +VoiceAgentsDataManager::~VoiceAgentsDataManager() { + // Clear the observers + mVoiceAgentChangeObservers.clear(); + // Clear the voiceagents + mVoiceAgents.clear(); +} + +uint32_t VoiceAgentsDataManager::activateVoiceAgents(const unordered_set& activeVoiceAgentIds) { + if (activeVoiceAgentIds.empty() || mVoiceAgents.empty()) { + mLogger->log(Level::ERROR, TAG, "Failed to activate voiceagents"); + return 0; + } + + uint32_t agentsActivated = 0; + for (auto voiceAgentId : activeVoiceAgentIds) { + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt != mVoiceAgents.end()) { + // activate the voiceagent + ++agentsActivated; + if (!voiceAgentIt->second->isActive()) { + voiceAgentIt->second->setIsActive(true); + // Notify observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentActivated(voiceAgentIt->second); + } + } + } + } + return agentsActivated; +} + +uint32_t VoiceAgentsDataManager::deactivateVoiceAgents(const unordered_set& inactiveVoiceAgentIds) { + if (inactiveVoiceAgentIds.empty() || mVoiceAgents.empty()) { + mLogger->log(Level::ERROR, TAG, "Failed to deactivate voiceagents"); + return 0; + } + + uint32_t agentsDeactivated = 0; + for (auto voiceAgentId : inactiveVoiceAgentIds) { + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt != mVoiceAgents.end()) { + ++agentsDeactivated; + if (voiceAgentIt->second->isActive()) { + // deactivate the voiceagent + voiceAgentIt->second->setIsActive(false); + // Notify observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentDeactivated(voiceAgentIt->second); + } + } + } + } + + return agentsDeactivated; +} + +bool VoiceAgentsDataManager::setDefaultVoiceAgent(const string& voiceAgentId) { + if (mVoiceAgents.empty() || voiceAgentId.empty()) { + string message = string("Failed to set default voiceagent id: ") + voiceAgentId; + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + auto defaultVoiceAgentIt = mVoiceAgents.find(voiceAgentId); + + if (defaultVoiceAgentIt != mVoiceAgents.end()) { + if (mDefaultVoiceAgentId != voiceAgentId) { + // Notify observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnDefaultVoiceAgentChanged(defaultVoiceAgentIt->second); + } + } + mDefaultVoiceAgentId = voiceAgentId; + } else { + mLogger->log(Level::ERROR, TAG, "Can't set default agent. Invalid voice agent id:" + voiceAgentId); + return false; + } + + return true; +} + +std::string VoiceAgentsDataManager::getDefaultVoiceAgent() { + return mDefaultVoiceAgentId; +} + +bool VoiceAgentsDataManager::setActiveWakeWord(const string& voiceAgentId, const string& wakeword) { + if (mVoiceAgents.empty() || wakeword.empty()) { + string message = + string("Failed to set active wakeword: ") + wakeword + string(" for voiceagent id: ") + voiceAgentId; + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt == mVoiceAgents.end()) { + return false; + } + + string oldWakeWord = voiceAgentIt->second->getActiveWakeword(); + if (oldWakeWord != wakeword) { + voiceAgentIt->second->setActiveWakeWord(wakeword); + // Notify observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentActiveWakeWordChanged(voiceAgentIt->second); + } + } + + return true; +} + +bool VoiceAgentsDataManager::addNewVoiceAgent( + const string& id, + const string& name, + const string& description, + const string& api, + const string& vendor, + const string& activeWakeword, + const bool isActive, + const shared_ptr> wakewords) { + shared_ptr voiceAgent = + VoiceAgent::create(mLogger, id, name, description, api, vendor, activeWakeword, isActive, wakewords); + + if (voiceAgent.get() == nullptr || voiceAgent->getId().empty()) { + string message = string("Invalid Arguments: Failed to add new voiceagent"); + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + if (!mVoiceAgents.empty() && mVoiceAgents.find(voiceAgent->getId()) != mVoiceAgents.end()) { + string message = + string("Failed to add new voiceagent. Voiceagent: ") + voiceAgent->getId() + string(" already exists."); + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + mVoiceAgents.insert(make_pair(voiceAgent->getId(), voiceAgent)); + + // Notify the observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentAdded(voiceAgent); + } + + // Create all vshl events for the voiceagent. + mVoiceAgentEventsHandler->createVshlEventsForVoiceAgent(voiceAgent->getId()); + + return true; +} + +bool VoiceAgentsDataManager::removeVoiceAgent(const string& voiceAgentId) { + if (mVoiceAgents.empty()) { + string message = string("Failed to remove voiceagent: ") + voiceAgentId + string(". Voiceagents data empty."); + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt == mVoiceAgents.end()) { + string message = string("Failed to remove voiceagent: ") + voiceAgentId + string(". Doesn't exist."); + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + auto voiceAgent = voiceAgentIt->second; + // Remove from the map + mVoiceAgents.erase(voiceAgentId); + // Notify the observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentRemoved(voiceAgent); + } + + // Remove all vshl events for the voiceagent. + mVoiceAgentEventsHandler->removeVshlEventsForVoiceAgent(voiceAgent->getId()); + + return true; +} + +std::set> VoiceAgentsDataManager::getAllVoiceAgents() { + std::set> voiceAgentsSet; + for (auto element : mVoiceAgents) { + voiceAgentsSet.insert(element.second); + } + + return voiceAgentsSet; +} + +// Returns the event filter that belongs to the core module. +shared_ptr VoiceAgentsDataManager::getEventFilter() const { + return mVoiceAgentEventsHandler; +} + +bool VoiceAgentsDataManager::subscribeToVshlEventFromVoiceAgent( + vshl::common::interfaces::IAFBRequest& request, + const string eventName, + const string voiceAgentId) { + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt == mVoiceAgents.end()) { + mLogger->log( + Level::ERROR, + TAG, + "\ + Failed to subscribe to VSHL events from voiceagent. VoiceAgent: " + + voiceAgentId + " doesn't exist."); + return false; + } + return mVoiceAgentEventsHandler->subscribeToVshlEventFromVoiceAgent(request, eventName, voiceAgentIt->second); +} + +bool VoiceAgentsDataManager::addVoiceAgentsChangeObserver( + shared_ptr observer) { + if (!observer) { + return false; + } + + mVoiceAgentChangeObservers.insert(observer); + return true; +} + +bool VoiceAgentsDataManager::removeVoiceAgentsChangeObserver( + shared_ptr observer) { + if (!observer) { + return false; + } + + mVoiceAgentChangeObservers.erase(observer); + return true; +} +} // namespace voiceagents +} // namespace vshl diff --git a/src/plugins/voiceagents/include/VoiceAgent.h b/src/plugins/voiceagents/include/VoiceAgent.h new file mode 100644 index 0000000..4dd55d4 --- /dev/null +++ b/src/plugins/voiceagents/include/VoiceAgent.h @@ -0,0 +1,95 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_ +#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_ + +#include +#include + +#include "interfaces/utilities/logging/ILogger.h" +#include "interfaces/voiceagents/IVoiceAgent.h" + +using namespace std; + +namespace vshl { +namespace voiceagents { +/* + * Default implementation of IVoiceAgent interface. + */ +class VoiceAgent : public vshl::common::interfaces::IVoiceAgent { +public: + // Creates @c VoiceAgent instance + static shared_ptr + create(shared_ptr logger, const string &id, + const string &name, const string &description, const string &api, + const string &vendor, const string &activeWakeword, + const bool isActive, + const shared_ptr> wakewords); + + // Destructor + ~VoiceAgent(); + + // IVoiceAgent overriden methods + bool setActiveWakeWord(const string &wakeword) override; + void setIsActive(bool active) override; + string getId() const override; + string getName() const override; + string getDescription() const override; + string getApi() const override; + string getVendor() const override; + shared_ptr> getWakeWords() const override; + bool isActive() const override; + string getActiveWakeword() const override; + +private: + // Constructor + VoiceAgent(shared_ptr logger, + const string &id, const string &name, const string &description, + const string &api, const string &vendor, + const string &activeWakeword, const bool isActive, + const shared_ptr> wakewords); + + // Logger + shared_ptr mLogger; + + // Id + string mId; + + // Name + string mName; + + // Description + string mDescription; + + // API + string mApi; + + // Vendor + string mVendor; + + // Active wakeword + string mActiveWakeword; + + // Active ?? + bool mIsActive; + + // Wakewords + shared_ptr> mWakewords; +}; + +} // namespace voiceagents +} // namespace vshl + +#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_ diff --git a/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h b/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h new file mode 100644 index 0000000..3c1ca6c --- /dev/null +++ b/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h @@ -0,0 +1,95 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_ +#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_ + +#include +#include +#include + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/events/IEventFilter.h" +#include "interfaces/utilities/logging/ILogger.h" +#include "voiceagents/VoiceAgentEventNames.h" +#include "voiceagents/include/VoiceAgent.h" + +using namespace std; + +namespace vshl { +namespace voiceagents { +/* + * This class is reponsible for handling agent specific events + * subscription and delivery on behalf of the high level voice service. + * This class also listen to the incoming events from voice agents + * and implements propagation to application layer. + */ +class VoiceAgentEventsHandler : public vshl::common::interfaces::IEventFilter { +public: + // Create a VREventFilter. + static shared_ptr create( + shared_ptr logger, + shared_ptr afbApi); + + // Creates all the vshl events for a specific voiceagent id. + // For e.g if voiceagent is VA-001 then a new vshl event + // voice_authstate_event#VA-001 for auth state will be created. + // Please see VoiceAgentEventNames.h for all the event names. + void createVshlEventsForVoiceAgent(const string voiceAgentId); + + // Removes the events from its bookkeeping. + void removeVshlEventsForVoiceAgent(const string voiceAgentId); + + // Subscribe to a vshl event corresponding to a voiceagent. + bool subscribeToVshlEventFromVoiceAgent( + vshl::common::interfaces::IAFBRequest& request, + const string eventName, + const shared_ptr voiceAgent); + + ~VoiceAgentEventsHandler(); + +protected: + string getName() override; + + // IEventFilter override + bool onIncomingEvent(const string eventName, const string voiceAgentId, const string payload) override; + +private: + // Constructor + VoiceAgentEventsHandler( + shared_ptr logger, + shared_ptr afbApi); + + // Helper method to generate the event name with voiceagent Id + // concatenated. + string createEventNameWithVAId(string eventName, string voiceAgentId); + + // call subscribe verb on the voiceagent. True if subscription successful. + // False otherwise. + bool callSubscribeVerb(const shared_ptr voiceAgent); + + // Binding API reference + shared_ptr mAfbApi; + + // A map of VSHL event ID to its Event object + unordered_map> mEventsMap; + + // Logger + shared_ptr mLogger; +}; + +} // namespace voiceagents +} // namespace vshl + +#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_ diff --git a/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp b/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp new file mode 100644 index 0000000..4952721 --- /dev/null +++ b/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "voiceagents/include/VoiceAgentEventsHandler.h" + +static string TAG = "vshl::voiceagents::VoiceAgentEventsHandler"; +static string VA_VERB_SUBSCRIBE = "subscribe"; + +using Level = vshl::common::interfaces::ILogger::Level; +using namespace vshl::common::interfaces; + +namespace vshl { +namespace voiceagents { + +shared_ptr VoiceAgentEventsHandler::create( + shared_ptr logger, + shared_ptr afbApi) { + auto eventFilter = std::shared_ptr(new VoiceAgentEventsHandler(logger, afbApi)); + return eventFilter; +} + +VoiceAgentEventsHandler::VoiceAgentEventsHandler( + shared_ptr logger, + shared_ptr afbApi) : + mAfbApi(afbApi), + mLogger(logger) { +} + +VoiceAgentEventsHandler::~VoiceAgentEventsHandler() { + mEventsMap.clear(); +} + +string VoiceAgentEventsHandler::getName() { + return TAG; +} + +void VoiceAgentEventsHandler::createVshlEventsForVoiceAgent(const string voiceAgentId) { + // Update the events map with all the VSHL Events. + for (auto eventName : VSHL_EVENTS) { + string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId); + auto it = mEventsMap.find(eventNameWithVAId); + if (it == mEventsMap.end() && mAfbApi) { + // create a new event and add it to the map. + shared_ptr event = mAfbApi->createEvent(eventNameWithVAId); + mEventsMap.insert(make_pair(eventNameWithVAId, event)); + } + } +} + +void VoiceAgentEventsHandler::removeVshlEventsForVoiceAgent(const string voiceAgentId) { + // Update the events map with all the VSHL Events. + for (auto eventName : VSHL_EVENTS) { + string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId); + auto it = mEventsMap.find(eventNameWithVAId); + if (it != mEventsMap.end()) { + mEventsMap.erase(it); + } + } +} + +bool VoiceAgentEventsHandler::subscribeToVshlEventFromVoiceAgent( + vshl::common::interfaces::IAFBRequest& request, + const string eventName, + const shared_ptr voiceAgent) { + auto supportedEventsIt = find(VSHL_EVENTS.begin(), VSHL_EVENTS.end(), eventName); + if (supportedEventsIt == VSHL_EVENTS.end()) { + mLogger->log(Level::ERROR, TAG, "Event: " + eventName + " not a known event."); + return false; + } + + // Check if the entry for the voiceagent is present in the + // events map. If not then return false because the responsibility + // of adding to the map lies in the hands of AddVoiceAgent method. + string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgent->getId()); + auto createdEventsIt = mEventsMap.find(eventNameWithVAId); + if (createdEventsIt == mEventsMap.end()) { + mLogger->log(Level::ERROR, TAG, "Not able to subscribe. Event doesn't exist, " + eventNameWithVAId); + return false; + } + createdEventsIt->second->subscribe(request); + + if (!callSubscribeVerb(voiceAgent)) { + mLogger->log(Level::WARNING, TAG, "Failed to subscribe to voiceagent: " + voiceAgent->getId()); + } + + return true; +} + +// IEventFilter override. +bool VoiceAgentEventsHandler::onIncomingEvent(const string eventName, const string voiceAgentId, const string payload) { + 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())); + } + + return true; +} + +string VoiceAgentEventsHandler::createEventNameWithVAId(string eventName, string voiceAgentId) { + return eventName + "#" + voiceAgentId; +} + +bool VoiceAgentEventsHandler::callSubscribeVerb(const shared_ptr voiceAgent) { + if (!voiceAgent) { + mLogger->log(Level::ERROR, TAG, "Failed to callSubscribeVerb. Invalid input parameter."); + return false; + } + + if (!mAfbApi) { + mLogger->log( + Level::ERROR, TAG, "Failed to callSubscribeVerb on voicegent: " + voiceAgent->getId() + ", No API."); + return false; + } + + // TODO(Naveen): Move to utilities. + json_object* object = NULL; + std::string error, info; + int rc = mAfbApi->callSync(voiceAgent->getApi(), VA_VERB_SUBSCRIBE, NULL, &object, error, info); + + if (object) { + free(object); + } + + return true; +} +} // namespace voiceagents +} // namespace vshl diff --git a/src/plugins/voiceagents/src/VoiceAgentImpl.cpp b/src/plugins/voiceagents/src/VoiceAgentImpl.cpp new file mode 100644 index 0000000..f2ef8a1 --- /dev/null +++ b/src/plugins/voiceagents/src/VoiceAgentImpl.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "voiceagents/include/VoiceAgent.h" + +static string TAG = "vshl::voiceagents::VoiceAgent"; + +/** + * Specifies the severity level of a log message + */ +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace voiceagents { +// Creates @c VoiceAgent instance +shared_ptr VoiceAgent::create( + shared_ptr logger, + const string& id, + const string& name, + const string& description, + const string& api, + const string& vendor, + const string& activeWakeword, + const bool isActive, + const shared_ptr> wakewords) { + if (wakewords == nullptr) { + logger->log(Level::ERROR, TAG, "Wakeword list null"); + return nullptr; + } + + auto voiceAgent = std::unique_ptr( + new VoiceAgent(logger, id, name, description, api, vendor, activeWakeword, isActive, wakewords)); + if (!voiceAgent->setActiveWakeWord(activeWakeword)) { + return nullptr; + } + + return voiceAgent; +} + +VoiceAgent::VoiceAgent( + shared_ptr logger, + const string& id, + const string& name, + const string& description, + const string& api, + const string& vendor, + const string& activeWakeword, + const bool isActive, + const shared_ptr> wakewords) : + mLogger(logger), + mId(id), + mName(name), + mDescription(description), + mApi(api), + mVendor(vendor), + mActiveWakeword(activeWakeword), + mIsActive(isActive), + mWakewords(wakewords) { +} + +// Destructor +VoiceAgent::~VoiceAgent() { +} + +// Set the active wakeword for this voiceagent +bool VoiceAgent::setActiveWakeWord(const string& wakeword) { + if (mWakewords->find(wakeword) != mWakewords->end()) { + mActiveWakeword = wakeword; + return true; + } + + mLogger->log(Level::ERROR, TAG, "Wakeword: " + wakeword + " doesn't exist in wakeword list"); + return false; +} + +// Sets the activation state of this voiceagent +void VoiceAgent::setIsActive(bool active) { + mIsActive = active; +} + +string VoiceAgent::getId() const { + return mId; +} + +string VoiceAgent::getName() const { + return mName; +} + +string VoiceAgent::getDescription() const { + return mDescription; +} + +string VoiceAgent::getApi() const { + return mApi; +} + +string VoiceAgent::getVendor() const { + return mVendor; +} + +shared_ptr> VoiceAgent::getWakeWords() const { + return mWakewords; +} + +bool VoiceAgent::isActive() const { + return mIsActive; +} + +string VoiceAgent::getActiveWakeword() const { + return mActiveWakeword; +} +} // namespace voiceagents +} // namespace vshl diff --git a/src/plugins/voiceagents/test/VoiceAgentTest.cpp b/src/plugins/voiceagents/test/VoiceAgentTest.cpp new file mode 100644 index 0000000..e5ad15e --- /dev/null +++ b/src/plugins/voiceagents/test/VoiceAgentTest.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "voiceagents/include/VoiceAgent.h" + +#include "voiceagents/test/VoiceAgentsTestData.h" +#include "test/common/ConsoleLogger.h" + +using namespace vshl::voiceagents; +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class VoiceAgentTest : public ::testing::Test { +protected: + void SetUp() override { + mConsoleLogger = std::make_shared(); + + mVoiceAgentData = *(getVoiceAgentsTestData().begin()); + mVoiceAgent = VoiceAgent::create( + mConsoleLogger, + mVoiceAgentData.id, + mVoiceAgentData.name, + mVoiceAgentData.description, + mVoiceAgentData.api, + mVoiceAgentData.vendor, + mVoiceAgentData.activeWakeword, + mVoiceAgentData.isActive, + mVoiceAgentData.wakewords); + } + + std::shared_ptr mConsoleLogger; + std::shared_ptr mVoiceAgent; + VoiceAgentTestData mVoiceAgentData; +}; + +TEST_F(VoiceAgentTest, InitializesCorrectly) { + ASSERT_NE(mVoiceAgent, nullptr); + ASSERT_EQ(mVoiceAgent->getId(), mVoiceAgentData.id); + ASSERT_EQ(mVoiceAgent->getName(), mVoiceAgentData.name); + ASSERT_EQ(mVoiceAgent->getDescription(), mVoiceAgentData.description); + ASSERT_EQ(mVoiceAgent->getApi(), mVoiceAgentData.api); + ASSERT_EQ(mVoiceAgent->getVendor(), mVoiceAgentData.vendor); + ASSERT_EQ(mVoiceAgent->getActiveWakeword(), mVoiceAgentData.activeWakeword); + ASSERT_EQ(mVoiceAgent->isActive(), mVoiceAgentData.isActive); + + std::unordered_set wakeWords = *mVoiceAgentData.wakewords; + ASSERT_EQ(*(mVoiceAgent->getWakeWords()), wakeWords); +} + +TEST_F(VoiceAgentTest, FailsCreationOnNonExistentWakeword) { + std::string nonExistentWW = "non-existent"; + auto voiceAgent = VoiceAgent::create( + mConsoleLogger, + mVoiceAgentData.id, + mVoiceAgentData.name, + mVoiceAgentData.description, + mVoiceAgentData.api, + mVoiceAgentData.vendor, + nonExistentWW, + mVoiceAgentData.isActive, + mVoiceAgentData.wakewords); + ASSERT_EQ(voiceAgent, nullptr); +} + +TEST_F(VoiceAgentTest, SetsWakewordCorrectly) { + std::string wakeword = *(mVoiceAgentData.wakewords->begin()); + ASSERT_TRUE(mVoiceAgent->setActiveWakeWord(wakeword)); + ASSERT_EQ(mVoiceAgent->getActiveWakeword(), wakeword); +} + +TEST_F(VoiceAgentTest, FailsToSetNonExistentWakeword) { + std::string nonExistentWW = "non-existent"; + ASSERT_FALSE(mVoiceAgent->setActiveWakeWord(nonExistentWW)); + ASSERT_EQ(mVoiceAgent->getActiveWakeword(), mVoiceAgentData.activeWakeword); +} + +} // namespace test +} // namespace vshl \ No newline at end of file diff --git a/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp b/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp new file mode 100644 index 0000000..58c62ed --- /dev/null +++ b/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp @@ -0,0 +1,294 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 "voiceagents/VoiceAgentsDataManager.h" + +#include "test/common/ConsoleLogger.h" +#include "test/mocks/AFBApiMock.h" +#include "test/mocks/VoiceAgentsChangeObserverMock.h" +#include "voiceagents/test/VoiceAgentsTestData.h" + +using namespace vshl::common::interfaces; +using namespace vshl::voiceagents; + +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class VoiceAgentDataManagerTest : public ::testing::Test { +protected: + void SetUp() override { + mConsoleLogger = std::make_shared(); + mAfbApi = std::make_shared<::testing::NiceMock>(); + mVADataManager = VoiceAgentsDataManager::create(mConsoleLogger, mAfbApi); + + mAgentsChangeObserver = std::make_shared< + ::testing::StrictMock>(); + mVADataManager->addVoiceAgentsChangeObserver(mAgentsChangeObserver); + + mVoiceAgentsData = getVoiceAgentsTestData(); + } + + void TearDown() override { + mVADataManager->removeVoiceAgentsChangeObserver(mAgentsChangeObserver); + } + + static bool addVoiceAgent(VoiceAgentsDataManager &mgr, + VoiceAgentTestData &data) { + return mgr.addNewVoiceAgent(data.id, data.name, data.description, data.api, + data.vendor, data.activeWakeword, data.isActive, + data.wakewords); + } + + static bool isEqual(const VoiceAgentTestData &lhs, const IVoiceAgent &rhs) { + return lhs.id == rhs.getId() && lhs.name == rhs.getName() && + lhs.description == rhs.getDescription() && lhs.api == rhs.getApi() && + lhs.vendor == rhs.getVendor() && + lhs.activeWakeword == rhs.getActiveWakeword() && + lhs.isActive == rhs.isActive() && + *lhs.wakewords == *rhs.getWakeWords(); + } + + static std::shared_ptr + findVoiceAgent(std::set> &voiceAgents, + std::string &vaId) { + for (auto va : voiceAgents) { + if (va->getId() == vaId) + return va; + } + + return nullptr; + } + + std::shared_ptr mConsoleLogger; + + // It is a NiceMock because we don't want gtest to produce warnings about non + // interesting calls. + // The non interesting calls like createEvent is internal implementation + // detail for many of the + // tests in this class, and hence suppression of these warnings with NiceMock. + std::shared_ptr<::testing::NiceMock> mAfbApi; + + // It is a StrictMock because we want to fail the test for all non interesting + // calls. + std::shared_ptr<::testing::StrictMock> + mAgentsChangeObserver; + + std::vector mVoiceAgentsData; + std::unique_ptr mVADataManager; +}; + +TEST_F(VoiceAgentDataManagerTest, InitializesCorrectly) { + ASSERT_NE(mVADataManager, nullptr); + ASSERT_EQ(mVADataManager->getAllVoiceAgents().size(), 0); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), std::string()); +} + +TEST_F(VoiceAgentDataManagerTest, addingVoiceAgentWithSameIdFails) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(1); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_FALSE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + + auto allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 1); +} + +TEST_F(VoiceAgentDataManagerTest, + addingVoiceAgentWithNonExistentActiveWakewordFails) { + auto voiceAgetData = mVoiceAgentsData[0]; + voiceAgetData.activeWakeword = "non-existent"; + ASSERT_FALSE(addVoiceAgent(*mVADataManager, voiceAgetData)); + + auto allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 0); +} + +TEST_F(VoiceAgentDataManagerTest, canAddNewVoiceAgents) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + + auto allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 1); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 2); + + for (auto va : allVoiceAgents) { + bool voiceAgentFound = false; + for (auto vaData : mVoiceAgentsData) { + if (isEqual(vaData, *va)) { + voiceAgentFound = true; + break; + } + } + ASSERT_TRUE(voiceAgentFound); + } +} + +TEST_F(VoiceAgentDataManagerTest, removingUnknonwVoiceAgentFails) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(1); + + ASSERT_FALSE(mVADataManager->removeVoiceAgent("non-existent-vaid")); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_FALSE(mVADataManager->removeVoiceAgent("non-existent-vaid")); +} + +TEST_F(VoiceAgentDataManagerTest, canRemoveVoiceAgents) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentRemoved(::testing::_)) + .Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + auto allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 2); + + ASSERT_TRUE(mVADataManager->removeVoiceAgent(mVoiceAgentsData[0].id)); + + allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 1); + + ASSERT_TRUE(mVADataManager->removeVoiceAgent(mVoiceAgentsData[1].id)); + + allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 0); +} + +TEST_F(VoiceAgentDataManagerTest, activatingNonExistentVoiceAgentsFails) { + uint32_t result = mVADataManager->activateVoiceAgents({"non", "existent"}); + ASSERT_EQ(result, 0); + + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + result = mVADataManager->activateVoiceAgents({"non", "existent"}); + ASSERT_EQ(result, 0); +} + +TEST_F(VoiceAgentDataManagerTest, deactivatingNonExistentVoiceAgentsFails) { + uint32_t result = mVADataManager->deactivateVoiceAgents({"non", "existent"}); + ASSERT_EQ(result, 0); + + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + result = mVADataManager->deactivateVoiceAgents({"non", "existent"}); + ASSERT_EQ(result, 0); +} + +TEST_F(VoiceAgentDataManagerTest, canActivateDeactivateVoiceAgents) { + { + ::testing::InSequence dummy; + + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)) + .Times(2); + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentDeactivated(::testing::_)) + .Times(1); + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentActivated(::testing::_)) + .Times(1); + } + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + std::string vaId = mVoiceAgentsData[0].id; + + auto allVA = mVADataManager->getAllVoiceAgents(); + auto voiceAgent = findVoiceAgent(allVA, vaId); + ASSERT_NE(voiceAgent, nullptr); + ASSERT_TRUE(voiceAgent->isActive()); + + uint32_t result = + mVADataManager->deactivateVoiceAgents({"non-existent", vaId}); + ASSERT_EQ(result, 1); + ASSERT_FALSE(voiceAgent->isActive()); + + // Try de-activating already de-activated agent + result = mVADataManager->deactivateVoiceAgents({vaId}); + ASSERT_EQ(result, 1); + ASSERT_FALSE(voiceAgent->isActive()); + + result = mVADataManager->activateVoiceAgents({"non-existent", vaId}); + ASSERT_EQ(result, 1); + ASSERT_TRUE(voiceAgent->isActive()); + + // Try activating already activated agent + result = mVADataManager->activateVoiceAgents({vaId}); + ASSERT_EQ(result, 1); + ASSERT_TRUE(voiceAgent->isActive()); +} + +TEST_F(VoiceAgentDataManagerTest, + NoDefaultAgentIsReturnedWhenNoDefaultAgentIsSet) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + std::string defaultAgentId = mVADataManager->getDefaultVoiceAgent(); + ASSERT_EQ(defaultAgentId, ""); +} + +TEST_F(VoiceAgentDataManagerTest, DefaultAgentCanBeSet) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + auto allAgents = mVADataManager->getAllVoiceAgents(); + std::string vaId1 = mVoiceAgentsData[1].id; + std::string vaId2 = mVoiceAgentsData[0].id; + auto va1 = findVoiceAgent(allAgents, vaId1); + auto va2 = findVoiceAgent(allAgents, vaId2); + + { + ::testing::InSequence dummy; + + EXPECT_CALL(*mAgentsChangeObserver, OnDefaultVoiceAgentChanged(va1)) + .Times(1); + EXPECT_CALL(*mAgentsChangeObserver, OnDefaultVoiceAgentChanged(va2)) + .Times(1); + } + + ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId1)); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId1); + + ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId2)); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2); + + ASSERT_FALSE(mVADataManager->setDefaultVoiceAgent("non-existent")); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2); + + // Setting default agent to already default agent shouldn't result in extra + // callback to OnDefaultVoiceAgentChanged + ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId2)); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2); +} + +} // namespace test +} // namespace vshl \ No newline at end of file diff --git a/src/plugins/voiceagents/test/VoiceAgentsTestData.h b/src/plugins/voiceagents/test/VoiceAgentsTestData.h new file mode 100644 index 0000000..ced068f --- /dev/null +++ b/src/plugins/voiceagents/test/VoiceAgentsTestData.h @@ -0,0 +1,67 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 +#include +#include + +namespace vshl { +namespace test { + +typedef std::shared_ptr> WakeWords; + +struct VoiceAgentTestData { + std::string id; + std::string name; + std::string description; + std::string api; + std::string vendor; + std::string activeWakeword; + bool isActive; + WakeWords wakewords; +}; + +static std::vector getVoiceAgentsTestData() { + std::vector voiceAgentsTestData{ + { + "VA-001", // Id + "Foundation", // Name + "Voice Agent For Galactic Empire", // Description + "api-1", // API + "Asimov", // Vendor + "Hari Seldon", // Active Wakeword + true, // Is Active + std::shared_ptr>( + new std::unordered_set{"Hari Seldon", "Cleon I ", "Eto Demerzel"}) // Wake Words + }, + { + "VA-002", // Id + "Betelgeuse", // Name + "Voice Agent For Galaxy hopper", // Description + "api-2", // API + "Douglas Adams", // Vendor + "Ford Prefect", // Active Wakeword + true, // Is Active + std::shared_ptr>( + new std::unordered_set{"Ford Prefect", "Zaphod Beeblebrox"}) // Wake Words + }, + }; + + return voiceAgentsTestData; +} + +} // namespace test +} // namespace vshl diff --git a/src/vshl-apidef.h b/src/vshl-apidef.h new file mode 100644 index 0000000..7a14498 --- /dev/null +++ b/src/vshl-apidef.h @@ -0,0 +1,43 @@ + +static const char _afb_description_vshl[] = + "{\"openapi\":\"3.0.0\",\"$schema\":\"http://iot.bzh/download/openapi/sch" + "ema-3.0/default-schema.json\",\"info\":{\"description\":\"\",\"title\":\"" + "High Level Voice Service API\",\"version\":\"1.0\",\"x-binding-c-generat" + "or\":{\"api\":\"vshl\",\"version\":3,\"prefix\":\"afv_\",\"postfix\":\"\"" + ",\"start\":null,\"onevent\":null,\"init\":\"init\",\"scope\":\"\",\"priv" + "ate\":false,\"noconcurrency\":true}},\"servers\":[{\"url\":\"ws://{host}" + ":{port}/api/monitor\",\"description\":\"TS caching binding\",\"variables" + "\":{\"host\":{\"default\":\"localhost\"},\"port\":{\"default\":\"1234\"}" + "},\"x-afb-events\":[{\"$ref\":\"#/components/schemas/afb-event\"}]}],\"c" + "omponents\":{\"schemas\":{\"afb-reply\":{\"$ref\":\"#/components/schemas" + "/afb-reply-v3\"},\"afb-event\":{\"$ref\":\"#/components/schemas/afb-even" + "t-v3\"},\"afb-reply-v3\":{\"title\":\"Generic response.\",\"type\":\"obj" + "ect\",\"required\":[\"jtype\",\"request\"],\"properties\":{\"jtype\":{\"" + "type\":\"string\",\"const\":\"afb-reply\"},\"request\":{\"type\":\"objec" + "t\",\"required\":[\"status\"],\"properties\":{\"status\":{\"type\":\"str" + "ing\"},\"info\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"},\"" + "uuid\":{\"type\":\"string\"},\"reqid\":{\"type\":\"string\"}}},\"respons" + "e\":{\"type\":\"object\"}}},\"afb-event-v3\":{\"type\":\"object\",\"requ" + "ired\":[\"jtype\",\"event\"],\"properties\":{\"jtype\":{\"type\":\"strin" + "g\",\"const\":\"afb-event\"},\"event\":{\"type\":\"string\"},\"data\":{\"" + "type\":\"object\"}}}},\"responses\":{\"200\":{\"description\":\"A comple" + "x object array response\",\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/afb-reply\"}}}}}}}"; + +static const struct afb_verb_v3 _afb_verbs_vshl[] = { + {.verb = NULL, .callback = NULL, .auth = NULL, .info = NULL, .vcbdata = NULL, .session = 0, .glob = 0}}; + +int init(afb_api_t api); + +const struct afb_binding_v3 afbBindingV3 = {.api = "vshl", + .specification = _afb_description_vshl, + .info = "", + .verbs = _afb_verbs_vshl, + .preinit = NULL, + .init = init, + .onevent = NULL, + .userdata = NULL, + .provide_class = NULL, + .require_class = NULL, + .require_api = NULL, + .noconcurrency = 1}; diff --git a/src/vshl-apidef.json b/src/vshl-apidef.json new file mode 100644 index 0000000..67bda88 --- /dev/null +++ b/src/vshl-apidef.json @@ -0,0 +1,109 @@ +{ + "openapi": "3.0.0", + "$schema": "http://iot.bzh/download/openapi/schema-3.0/default-schema.json", + "info": { + "description": "", + "title": "High Level Voice Service API", + "version": "1.0", + "x-binding-c-generator": { + "api": "vshl", + "version": 3, + "prefix": "afv_", + "postfix": "", + "start": null, + "onevent": null, + "init": "init", + "scope": "", + "private": false, + "noconcurrency": true + } + }, + "servers": [{ + "url": "ws://{host}:{port}/api/monitor", + "description": "TS caching binding", + "variables": { + "host": { + "default": "localhost" + }, + "port": { + "default": "1234" + } + }, + "x-afb-events": [{ + "$ref": "#/components/schemas/afb-event" + }] + }], + "components": { + "schemas": { + "afb-reply": { + "$ref": "#/components/schemas/afb-reply-v3" + }, + "afb-event": { + "$ref": "#/components/schemas/afb-event-v3" + }, + "afb-reply-v3": { + "title": "Generic response.", + "type": "object", + "required": ["jtype", "request"], + "properties": { + "jtype": { + "type": "string", + "const": "afb-reply" + }, + "request": { + "type": "object", + "required": ["status"], + "properties": { + "status": { + "type": "string" + }, + "info": { + "type": "string" + }, + "token": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "reqid": { + "type": "string" + } + } + }, + "response": { + "type": "object" + } + } + }, + "afb-event-v3": { + "type": "object", + "required": ["jtype", "event"], + "properties": { + "jtype": { + "type": "string", + "const": "afb-event" + }, + "event": { + "type": "string" + }, + "data": { + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "A complex object array response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/afb-reply" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/vshl-binding.c b/src/vshl-binding.c new file mode 100644 index 0000000..17afbad --- /dev/null +++ b/src/vshl-binding.c @@ -0,0 +1,116 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#define _GNU_SOURCE +#include "vshl-binding.h" + +afb_dynapi* AFB_default; + +// Config Section definition (note: controls section index should match handle +// retrieval in HalConfigExec) +static CtlSectionT ctrlSections[] = {{.key = "plugins", .loadCB = PluginConfig}, + {.key = "onload", .loadCB = OnloadConfig}, + {.key = "controls", .loadCB = ControlConfig}, + {.key = "events", .loadCB = EventConfig}, + {.key = NULL}}; + +static AFB_ApiVerbs ctrlApiVerbs[] = { + {.verb = NULL} /* marker for end of the array */ +}; + +static int ctrlLoadStaticVerbs(afb_dynapi* apiHandle, AFB_ApiVerbs* verbs) { + int errcount = 0; + + for (int idx = 0; verbs[idx].verb; idx++) { + errcount += afb_dynapi_add_verb( + apiHandle, + ctrlApiVerbs[idx].verb, + NULL, + ctrlApiVerbs[idx].callback, + (void*)&ctrlApiVerbs[idx], + ctrlApiVerbs[idx].auth, + 0); + } + + return errcount; +}; + +// next generation dynamic API-V3 mode +#include + +static int ctrlLoadOneApi(void* cbdata, AFB_ApiT apiHandle) { + CtlConfigT* ctrlConfig = (CtlConfigT*)cbdata; + + // save closure as api's data context + afb_dynapi_set_userdata(apiHandle, ctrlConfig); + + // add static controls verbs + int err = ctrlLoadStaticVerbs(apiHandle, ctrlApiVerbs); + if (err) { + AFB_ApiError(apiHandle, "ctrlLoadStaticVerbs fail to register static V2 verbs"); + return ERROR; + } + + // load section for corresponding API + err = CtlLoadSections(apiHandle, ctrlConfig, ctrlSections); + if (err) { + AFB_ApiError(apiHandle, "CtlLoadSections fail to load the sections"); + return ERROR; + } + + // declare an event event manager for this API; + afb_dynapi_on_event(apiHandle, CtrlDispatchApiEvent); + + // init API function (does not receive user closure ??? + // afb_dynapi_on_init(apiHandle, CtrlInitOneApi); + + afb_dynapi_seal(apiHandle); + return err; +} + +int afbBindingEntry(afb_dynapi* apiHandle) { + AFB_default = apiHandle; + AFB_ApiNotice(apiHandle, "Controller in afbBindingEntry"); + + const char* dirList = getenv("CONTROL_CONFIG_PATH"); + if (!dirList) dirList = CONTROL_CONFIG_PATH; + + const char* configPath = CtlConfigSearch(apiHandle, dirList, ""); + if (!configPath) { + AFB_ApiError(apiHandle, "CtlPreInit: No %s* config found in %s ", GetBinderName(), dirList); + return ERROR; + } + + // load config file and create API + CtlConfigT* ctrlConfig = CtlLoadMetaData(apiHandle, configPath); + if (!ctrlConfig) { + AFB_ApiError(apiHandle, "CtrlBindingDyn No valid control config file in:\n-- %s", configPath); + return ERROR; + } + + if (!ctrlConfig->api) { + AFB_ApiError(apiHandle, "CtrlBindingDyn API Missing from metadata in:\n-- %s", configPath); + return ERROR; + } + + AFB_ApiNotice(apiHandle, "Controller API='%s' info='%s'", ctrlConfig->api, ctrlConfig->info); + + // create one API per config file (Pre-V3 return code ToBeChanged) + int status = afb_dynapi_new_api(apiHandle, ctrlConfig->api, ctrlConfig->info, 1, ctrlLoadOneApi, ctrlConfig); + + // config exec should be done after api init in order to enable onload to use newly defined ctl API. + if (!status) status = CtlConfigExec(apiHandle, ctrlConfig); + + return status; +} diff --git a/src/vshl-binding.h b/src/vshl-binding.h new file mode 100644 index 0000000..75e7c91 --- /dev/null +++ b/src/vshl-binding.h @@ -0,0 +1,25 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ +#ifndef _CTL_BINDING_INCLUDE_ +#define _CTL_BINDING_INCLUDE_ + +#define AFB_BINDING_VERSION 3 +#include + +#ifndef ERROR +#define ERROR -1 +#endif + +#endif /* _CTL_BINDING_INCLUDE_ */ \ No newline at end of file -- cgit 1.2.3-korg