diff options
author | Naveen Bobbili <nbobbili@amazon.com> | 2018-11-12 16:12:38 -0800 |
---|---|---|
committer | Naveen Bobbili <nbobbili@amazon.com> | 2018-11-13 15:05:41 -0800 |
commit | b6abca2edcb36c0c0848d1cd8dc291f23293aa80 (patch) | |
tree | a838812e0b66f0695cb6cf0f8bebfa38315ce8b8 | |
parent | be70712f89eacd20dca413bcce46e4aa26b5709e (diff) |
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 <nbobbili@amazon.com>
102 files changed, 8596 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5752e40 --- /dev/null +++ b/.clang-format @@ -0,0 +1,102 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +#AlignConsecutiveAssignments: false +#AlignConsecutiveDeclarations: false +#AlignEscapedNewlines: Left +#AlignOperands: true +#AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +#AllowShortBlocksOnASingleLine: false +#AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +#AllowShortIfStatementsOnASingleLine: true +#AllowShortLoopsOnASingleLine: true +#AlwaysBreakAfterDefinitionReturnType: None +#AlwaysBreakAfterReturnType: None +#AlwaysBreakBeforeMultilineStrings: true +#AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +#BraceWrapping: +# AfterClass: false +# AfterControlStatement: false +# AfterEnum: false +# AfterFunction: false +# AfterNamespace: false +# AfterObjCDeclaration: false +# AfterStruct: false +# AfterUnion: false +# BeforeCatch: false +# BeforeElse: false +# IndentBraces: false +# SplitEmptyFunctionBody: true +#BreakBeforeBinaryOperators: None +#BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: true +#BreakBeforeTernaryOperators: true +#BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +#BreakAfterJavaFieldAnnotations: false +#BreakStringLiterals: true +ColumnLimit: 120 +#CommentPragmas: '^ IWYU pragma:' +#CompactNamespaces: false +#ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 8 +#ContinuationIndentWidth: 4 +#Cpp11BracedListStyle: true +DerivePointerAlignment: false +#DisableFormat: false +#ExperimentalAutoDetectBinPacking: false +#FixNamespaceComments: true +#ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +#IncludeCategories: +# - Regex: '^<.*\.h>' +# Priority: 1 +# - Regex: '^<.*' +# Priority: 2 +# - Regex: '.*' +# Priority: 3 +#IncludeIsMainRegex: '([-_](test|unittest))?$' +#IndentCaseLabels: true +IndentWidth: 4 +#IndentWrappedFunctionNames: false +#JavaScriptQuotes: Leave +#JavaScriptWrapImports: true +#KeepEmptyLinesAtTheStartOfBlocks: false +#MacroBlockBegin: '' +#MacroBlockEnd: '' +#MaxEmptyLinesToKeep: 1 +#NamespaceIndentation: None +#ObjCBlockIndentWidth: 2 +#ObjCSpaceAfterProperty: false +#ObjCSpaceBeforeProtocolList: false +#PenaltyBreakAssignment: 2 +#PenaltyBreakBeforeFirstCallParameter: 1 +#PenaltyBreakComment: 300 +#PenaltyBreakFirstLessLess: 120 +#PenaltyBreakString: 1000 +#PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 20000 +#PointerAlignment: Left +#ReflowComments: true +SortIncludes: false +#SpaceAfterCStyleCast: false +#SpaceAfterTemplateKeyword: true +#SpaceBeforeAssignmentOperators: true +#SpaceBeforeParens: ControlStatements +#SpaceInEmptyParentheses: false +#SpacesBeforeTrailingComments: 2 +#SpacesInAngles: false +#SpacesInContainerLiterals: true +#SpacesInCStyleCastParentheses: false +#SpacesInParentheses: false +#SpacesInSquareBrackets: false +#Standard: Auto +#TabWidth: 8 +#UseTab: Never +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9b45c19 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "afb-helpers"] + path = afb-helpers + url = https://gerrit.automotivelinux.org/gerrit/apps/app-afb-helpers-submodule +[submodule "app-controller"] + path = app-controller + url = https://gerrit.automotivelinux.org/gerrit/apps/app-controller-submodule diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f39acc4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +########################################################################### +# Copyright 2018 IoT.bzh +# +# author: Romain Forlot <romain.forlot@iot.bzh> +# +# 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. +########################################################################### + +CMAKE_MINIMUM_REQUIRED(VERSION 3.5.1) + +include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake) diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/License.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d814c9 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# 1. High Level Voice Service (VSHL) +This repository hosts the code for the AGL's high level voice service binding also known as VSHL. +Please refer to the [architecture](https://confluence.automotivelinux.org/display/SPE/Speech+EG+Architecture) for more information. + +# 2. Build Dependencies and License Information + +During the build time, the following dependencies are fetched and run by the build system. Please refer to each of the individual entities for the particular licenses. +* [Google Test v1.8.0](https://github.com/google/googletest) when compiled with ENABLE_UNIT_TESTS option. + +# 3. Getting the Source Code +``` +export MY_PROJECTS_DIR = <Your Project Directory> +pushd $MY_PROJECTS_DIR +git clone --recursive https://gerrit.automotivelinux.org/gerrit/apps/agl-service-voice-high +``` + +# 4. Renesas R-Car M3 board +## 4.1 Building VSHL + +``` +pushd agl-voiceservice-highlevel +mkdir build +pushd build +source /opt/agl-sdk/6.0.1-aarch64/environment-setup-aarch64-agl-linux +cmake .. +make autobuild +popd +./conf.d/autobuild/agl/autobuild package +``` +* The build output will be located at $MY_PROJECTS_DIR/agl-voiceservice-highlevel/build/vshl.wgt + +## 4.2 Running VSHL +``` +# afm-util install vshl.wgt +# afm-util start vshl@1.0 +``` + +# 5. Ubuntu 16.04 +## 5.1 Building VSHL + +``` +pushd agl-voiceservice-highlevel/ +mkdir build +pushd build +cmake .. +make autobuild +popd +./conf.d/autobuild/linux/autobuild package +```` +To build the included unit tests modify the cmake step as following: +cmake .. -DENABLE_UNIT_TESTS=ON + +## 5.2 Running VSHL +``` +afb-daemon --port=1111 --name=afb-vshl --workdir=$MY_PROJECTS_DIR/agl-voice-service/build/package --ldpaths=lib --roothttp=htdocs --token= -vvv +``` + +# 6. Running the Unit Tests +## 6.1 Ubuntu 16.04 +``` +pushd agl-voiceservice-highlevel/ +./build/src/plugins/vshl-api_Test +popd +``` + +# 7. Testing VSHL +* The binding can be tested by launching the HTML5 sample application that is bundled with the package in a browser. + +``` +http://localhost:1111 +``` + +# 8. Contributing code +Before contributing the source, its recommended to format the code with clang-format. This is done automatically during commit step if following instructions are followed. +**Prerequisite**: Install clang-format-6.0 or greater. +There are following 2 options. + +* Before commit, manually run clang-format on the file (It will use local .clang-format file for checking the rules) +``` +clang-format -i <path to source file> +``` + +* Setup clang-format as pre-commit git hook. This is one time step after you clone the repo +``` +cd ${VSHL_ROOT} +cp tools/pre-commit .git/hooks/pre-commit +``` + +* With the hook in place, everytime you try to commit, it will check the format and disallow commit if format doesn't abide by the .clang-format rules. +It will also give you the option to apply a patch (it creates a patch in /tmp folder) to make the source abide by the .clang-format rules. Apply the patch and proceed to commit +``` +git apply /tmp/<patch> +git add <source files> +git commit +```
\ No newline at end of file diff --git a/afb-helpers b/afb-helpers new file mode 160000 +Subproject f0ce5b665dd33b285d723720c16ac0542cde4e6 diff --git a/app-controller b/app-controller new file mode 160000 +Subproject 33abde52666af1335571252143d21de5d305ca9 diff --git a/conf.d/CMakeLists.txt b/conf.d/CMakeLists.txt new file mode 100644 index 0000000..3beb009 --- /dev/null +++ b/conf.d/CMakeLists.txt @@ -0,0 +1,20 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <rfulup@iot.bzh> +# +# 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. +########################################################################### + +# This component should be included as a submodule and wont compile as standalone +project_subdirs_add() diff --git a/conf.d/autobuild/agl/autobuild b/conf.d/autobuild/agl/autobuild new file mode 100755 index 0000000..83097ab --- /dev/null +++ b/conf.d/autobuild/agl/autobuild @@ -0,0 +1,67 @@ +#!/usr/bin/make -f +# Copyright (C) 2015, 2016 "IoT.bzh" +# Author "Romain Forlot" <romain.forlot@iot.bzh> +# +# 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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build) +DEST := ${BUILD_DIR}/target + +.PHONY: all clean distclean configure build package help update + +all: help + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory" + @echo "" + @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +update: configure + @cmake --build ${BUILD_DIR} --target autobuild + +clean: + @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean + +distclean: + @rm -rf ${BUILD_DIR} + +configure: ${BUILD_DIR}/Makefile + +build: configure + @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all + +package: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/var + @cmake --build ${BUILD_DIR} --target widget + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST} + +install: build + @cmake --build ${BUILD_DIR} --target install + +${BUILD_DIR}/Makefile: + @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} + @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) diff --git a/conf.d/autobuild/linux/autobuild b/conf.d/autobuild/linux/autobuild new file mode 100755 index 0000000..83097ab --- /dev/null +++ b/conf.d/autobuild/linux/autobuild @@ -0,0 +1,67 @@ +#!/usr/bin/make -f +# Copyright (C) 2015, 2016 "IoT.bzh" +# Author "Romain Forlot" <romain.forlot@iot.bzh> +# +# 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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build) +DEST := ${BUILD_DIR}/target + +.PHONY: all clean distclean configure build package help update + +all: help + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory" + @echo "" + @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +update: configure + @cmake --build ${BUILD_DIR} --target autobuild + +clean: + @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean + +distclean: + @rm -rf ${BUILD_DIR} + +configure: ${BUILD_DIR}/Makefile + +build: configure + @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all + +package: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/var + @cmake --build ${BUILD_DIR} --target widget + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST} + +install: build + @cmake --build ${BUILD_DIR} --target install + +${BUILD_DIR}/Makefile: + @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} + @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) diff --git a/conf.d/cmake/00-debian-osconfig.cmake b/conf.d/cmake/00-debian-osconfig.cmake new file mode 100644 index 0000000..2ce0ad3 --- /dev/null +++ b/conf.d/cmake/00-debian-osconfig.cmake @@ -0,0 +1 @@ +list(APPEND PKG_REQUIRED_LIST lua-5.3>=5.3) diff --git a/conf.d/cmake/00-default-osconfig.cmake b/conf.d/cmake/00-default-osconfig.cmake new file mode 100644 index 0000000..a2b9325 --- /dev/null +++ b/conf.d/cmake/00-default-osconfig.cmake @@ -0,0 +1 @@ +list(APPEND PKG_REQUIRED_LIST lua>=5.3) diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake new file mode 100644 index 0000000..1059bb1 --- /dev/null +++ b/conf.d/cmake/config.cmake @@ -0,0 +1,223 @@ +########################################################################### +# Copyright 2018 IoT.bzh +# +# author: Sebastien Douheret <sebastien@iot.bzh> +# +# 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 Info +# ------------------ +set(PROJECT_NAME vshl) +set(PROJECT_VERSION "1.0") +set(PROJECT_PRETTY_NAME "High Level Voice Service APIs") +set(PROJECT_DESCRIPTION "Binding that provide voice services to AGL apps.") +set(PROJECT_ICON "icon.png") +set(PROJECT_AUTHOR "Naveen Bobbili") +set(PROJECT_AUTHOR_MAIL "nbobbili@amazon.com") +set(PROJECT_LICENSE "APL2.0") +set(PROJECT_LANGUAGES "CXX") + +# Where are stored the project configuration files +# relative to the root project directory +set(PROJECT_CMAKE_CONF_DIR "conf.d") + +# Where are stored your external libraries for your project. This is 3rd party library that you don't maintain +# but used and must be built and linked. +# set(PROJECT_LIBDIR "libs") + +# Which directories inspect to find CMakeLists.txt target files +# set(PROJECT_SRC_DIR_PATTERN "*") + +# Compilation Mode (DEBUG, RELEASE) +# ---------------------------------- +set(CMAKE_BUILD_TYPE "RELEASE") +#set(USE_EFENCE 1) + +# Helpers Submodule parameters +# set(AFB_HELPERS_QTWSCLIENT OFF CACHE BOOL "Adds QT5 WebSocket helpers from submodule") + +# Kernel selection if needed. You can choose between a +# mandatory version to impose a minimal version. +# Or check Kernel minimal version and just print a Warning +# about missing features and define a preprocessor variable +# to be used as preprocessor condition in code to disable +# incompatibles features. Preprocessor define is named +# KERNEL_MINIMAL_VERSION_OK. +# +# NOTE*** FOR NOW IT CHECKS KERNEL Yocto environment and +# Yocto SDK Kernel version. +# ----------------------------------------------- +#set (kernel_mandatory_version 4.8) +#set (kernel_minimal_version 4.8) + +# Compiler selection if needed. Impose a minimal version. +# ----------------------------------------------- +set (gcc_minimal_version 4.9) + +# PKG_CONFIG required packages +# ----------------------------- +set (PKG_REQUIRED_LIST + json-c + libsystemd>=222 + afb-daemon + libmicrohttpd>=0.9.55 +) + +# Prefix path where will be installed the files +# Default: /usr/local (need root permission to write in) +# ------------------------------------------------------ +#set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt) + +# Customize link option +# ----------------------------- +#list(APPEND link_libraries -an-option) + +# Compilation options definition +# Use CMake generator expressions to specify only for a specific language +# Values are prefilled with default options that is currently used. +# Either separate options with ";", or each options must be quoted separately +# DO NOT PUT ALL OPTION QUOTED AT ONCE , COMPILATION COULD FAILED ! +# ---------------------------------------------------------------------------- +set(COMPILE_OPTIONS + -Wno-missing-field-initializers + -Wno-format-security +# -Wall +# -Wextra +# -Wconversion +# -Wno-unused-parameter +# -Wno-sign-compare +# -Wno-sign-conversion +# -Werror=maybe-uninitialized +# -Werror=implicit-function-declaration +# -ffunction-sections +# -fdata-sections +# -fPIC + CACHE STRING "Compilation flags") +#set(C_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C language.") +#set(CXX_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C++ language.") +#set(PROFILING_COMPILE_OPTIONS +# -g +# -O0 +# -pg +# -Wp,-U_FORTIFY_SOURCE +# CACHE STRING "Compilation flags for PROFILING build type.") +#set(DEBUG_COMPILE_OPTIONS +# -g +# -ggdb +# -Wp,-U_FORTIFY_SOURCE +# CACHE STRING "Compilation flags for DEBUG build type.") +#set(CCOV_COMPILE_OPTIONS +# -g +# -O2 +# --coverage +# CACHE STRING "Compilation flags for CCOV build type.") +#set(RELEASE_COMPILE_OPTIONS +# -g +# -O2 +# CACHE STRING "Compilation flags for RELEASE build type.") + +set(CONTROL_SUPPORT_LUA 1) +# Search Paths: Build directory -> CMake installation path -> AFM installation path +add_definitions(-DCONTROL_PLUGIN_PATH="${CMAKE_BINARY_DIR}/package/lib/plugins:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib/plugins:/var/local/lib/afm/applications/${PROJECT_NAME}/${PROJECT_VERSION}/lib/plugins") +add_definitions(-DCONTROL_CONFIG_PATH="${CMAKE_BINARY_DIR}/package/etc:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}:/var/local/lib/afm/applications/${PROJECT_NAME}") +#add_definitions(-DCONTROL_LUA_PATH="${CMAKE_SOURCE_DIR}/conf.d/project/lua.d:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/var:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}") +add_definitions(-DCTL_PLUGIN_MAGIC=1286576532) +add_definitions(-DUSE_API_DYN=1 -DAFB_BINDING_VERSION=3 -DAFB_BINDING_WANT_DYNAPI) + +# (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable] +# --------------------------------------------------------------------- +#set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) +#set(LD_LIBRARY_PATH ${CMAKE_INSTALL_PREFIX}/lib64 ${CMAKE_INSTALL_PREFIX}/lib) + +# Optional location for config.xml.in +# ----------------------------------- +#set(WIDGET_ICON "\"conf.d/wgt/${PROJECT_ICON}\"" CACHE PATH "Path to the widget icon") +set(WIDGET_CONFIG_TEMPLATE "${CMAKE_SOURCE_DIR}/conf.d/wgt/config.xml.in" CACHE PATH "Path to widget config file template (config.xml.in)") + +# Mandatory widget Mimetype specification of the main unit +# -------------------------------------------------------------------------- +# Choose between : +#- text/html : HTML application, +# content.src designates the home page of the application +# +#- application/vnd.agl.native : AGL compatible native, +# content.src designates the relative path of the binary. +# +# - application/vnd.agl.service: AGL service, content.src is not used. +# +#- ***application/x-executable***: Native application, +# content.src designates the relative path of the binary. +# For such application, only security setup is made. +# +set(WIDGET_TYPE application/vnd.agl.service) + +# Mandatory Widget entry point file of the main unit +# -------------------------------------------------------------- +# This is the file that will be executed, loaded, +# at launch time by the application framework. +# +set(WIDGET_ENTRY_POINT lib/afb-vshl.so) + +# Optional dependencies order +# --------------------------- +#set(EXTRA_DEPENDENCIES_ORDER) + +# Optional Extra global include path +# ----------------------------------- +#set(EXTRA_INCLUDE_DIRS) + +# Optional extra libraries +# ------------------------- +#set(EXTRA_LINK_LIBRARIES) + +# Optional force binding installation +# ------------------------------------ +# set(BINDINGS_INSTALL_PREFIX PrefixPath ) + +# Optional force binding Linking flag +# ------------------------------------ +# set(BINDINGS_LINK_FLAG LinkOptions ) + +# Optional force package prefix generation, like widget +# ----------------------------------------------------- +# set(PKG_PREFIX DestinationPath) + +# Optional Application Framework security token +# and port use for remote debugging. +#------------------------------------------------------------ +set(AFB_TOKEN "" CACHE PATH "Default binder security token") +set(AFB_REMPORT "1111" CACHE PATH "Default binder listening port") + +# Print a helper message when every thing is finished +# ---------------------------------------------------- +set(CLOSING_MESSAGE "Typical binding launch: \ +afb-daemon --port=${AFB_REMPORT} --name=afb-speech --workdir=${CMAKE_BINARY_DIR}/package \ +--ldpaths=lib --roothttp=htdocs --token=\"${AFB_TOKEN}\" -vvv") + +set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt") + +# Optional schema validator about now only XML, LUA and JSON +# are supported +#------------------------------------------------------------ +#set(LUA_CHECKER "luac" "-p" CACHE STRING "LUA compiler") +#set(XML_CHECKER "xmllint" CACHE STRING "XML linter") +#set(JSON_CHECKER "json_verify" CACHE STRING "JSON linter") + +# This include is mandatory and MUST happens at the end +# of this file, else you expose you to unexpected behavior +# +# This CMake module could be found at the following url: +# https://gerrit.automotivelinux.org/gerrit/#/admin/projects/src/cmake-apps-module +# ----------------------------------------------------------- +include(CMakeAfbTemplates)
\ No newline at end of file diff --git a/conf.d/project/CMakeLists.txt b/conf.d/project/CMakeLists.txt new file mode 100644 index 0000000..3beb009 --- /dev/null +++ b/conf.d/project/CMakeLists.txt @@ -0,0 +1,20 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <rfulup@iot.bzh> +# +# 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. +########################################################################### + +# This component should be included as a submodule and wont compile as standalone +project_subdirs_add() diff --git a/conf.d/project/etc/CMakeLists.txt b/conf.d/project/etc/CMakeLists.txt new file mode 100644 index 0000000..3aacae6 --- /dev/null +++ b/conf.d/project/etc/CMakeLists.txt @@ -0,0 +1,32 @@ +########################################################################### +# Copyright 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# +# 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. +########################################################################### + +################################################## +# Control Policy Config file +################################################## +PROJECT_TARGET_ADD(vshl-api-config) + + file(GLOB CONF_FILES "*.json") + + add_input_files("${CONF_FILES}") + + SET_TARGET_PROPERTIES( + ${TARGET_NAME} PROPERTIES + LABELS "BINDING-CONFIG" + OUTPUT_NAME ${TARGET_NAME} + )
\ No newline at end of file diff --git a/conf.d/project/etc/vshl-api.json b/conf.d/project/etc/vshl-api.json new file mode 100644 index 0000000..3f92f76 --- /dev/null +++ b/conf.d/project/etc/vshl-api.json @@ -0,0 +1,96 @@ +{ + "$schema": "http://iot.bzh/download/public/schema/json/ctl-schema.json", + "metadata": { + "uid": "vshl", + "version": "1.0", + "api": "vshl", + "info": "High Level Voice Service APIs" + }, + + "onload": [{ + "uid": "loadVoiceAgentsConfig", + "info": "Loading the information about voice agents managed by the high level voice service.", + "action": "plugin://vshl#loadVoiceAgentsConfig", + "args": { + "default": "VA-001", + "agents": [ + { + "id": "VA-001", + "active": true, + "name": "Alexa", + "api": "alexa-voiceagent", + "wakewords": [ + "alexa", + "computer", + "echo" + ], + "activewakeword": "alexa", + "description": "Alexa voice assistant by Amazon.", + "vendor": "Amazon.com Services Inc" + } + ] + } + }], + + "plugins": [{ + "uid": "vshl", + "info": "Plugin to handle high level voice service interface implementation", + "libs": [ + "vshl-api.ctlso" + ] + }], + + "events": [{ + "uid": "alexa-voiceagent/voice_authstate_event", + "action": "plugin://vshl#onAuthStateEvent" + },{ + "uid": "alexa-voiceagent/voice_connectionstate_event", + "action": "plugin://vshl#onConnectionStateEvent" + },{ + "uid": "alexa-voiceagent/voice_dialogstate_event", + "action": "plugin://vshl#onDialogStateEvent" + }], + + "controls": [{ + "uid": "startListening", + "action": "plugin://vshl#startListening" + }, { + "uid": "cancelListening", + "action": "plugin://vshl#cancelListening" + }, { + "uid": "subscribe", + "action": "plugin://vshl#subscribe" + }, { + "uid": "enumerateVoiceAgents", + "privileges": "urn:AGL:permission:vshl:voiceagents:public", + "action": "plugin://vshl#enumerateVoiceAgents" + }, { + "uid": "setDefaultVoiceAgent", + "privileges": "urn:AGL:permission:vshl:voiceagents:public", + "action": "plugin://vshl#setDefaultVoiceAgent" + }, { + "uid": "guiMetadata/publish", + "privileges": "urn:AGL:permission:vshl:guiMetadata:public", + "action": "plugin://vshl#guiMetadataPublish" + }, { + "uid": "guiMetadata/subscribe", + "privileges": "urn:AGL:permission:vshl:guiMetadata:public", + "action": "plugin://vshl#guiMetadataSubscribe" + }, { + "uid": "phonecontrol/publish", + "privileges": "urn:AGL:permission:vshl:phonecontrol:public", + "action": "plugin://vshl#phonecontrolPublish" + }, { + "uid": "phonecontrol/subscribe", + "privileges": "urn:AGL:permission:vshl:phonecontrol:public", + "action": "plugin://vshl#phonecontrolSubscribe" + }, { + "uid": "navigation/publish", + "privileges": "urn:AGL:permission:vshl:navigation:public", + "action": "plugin://vshl#navigationPublish" + }, { + "uid": "navigation/subscribe", + "privileges": "urn:AGL:permission:vshl:navigation:public", + "action": "plugin://vshl#navigationSubscribe" + }] +}
\ No newline at end of file diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in new file mode 100644 index 0000000..e1efb32 --- /dev/null +++ b/conf.d/wgt/config.xml.in @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<widget xmlns="http://www.w3.org/ns/widgets" id="@PROJECT_NAME@" version="@PROJECT_VERSION@"> + <name>@PROJECT_NAME@</name> + <icon src="@PROJECT_ICON@"/> + <content src="@WIDGET_ENTRY_POINT@" type="@WIDGET_TYPE@"/> + <description>@PROJECT_DESCRIPTION@</description> + <author>@PROJECT_AUTHOR@ <@PROJECT_AUTHOR_MAIL@></author> + <license>@PROJECT_LICENSE@</license> + <feature name="urn:AGL:widget:provided-api"> + <param name="vshl" value="ws" /> + </feature> + <feature name="urn:AGL:widget:required-binding"> + <param name="@WIDGET_ENTRY_POINT@" value="local" /> + </feature> +</widget> diff --git a/htdocs/AFB-websock.js b/htdocs/AFB-websock.js new file mode 100644 index 0000000..3d4831f --- /dev/null +++ b/htdocs/AFB-websock.js @@ -0,0 +1,177 @@ +var urlWS; +var urlhttp; + +AFB = function(base, initialtoken){ + +urlWS = "ws://"+window.location.host+"/"+base; +urlhttp = "http://"+window.location.host+"/"+base; + +/*********************************************/ +/**** ****/ +/**** AFB_context ****/ +/**** ****/ +/*********************************************/ +var AFB_context; +{ + var UUID = undefined; + var TOKEN = initialtoken; + + var context = function(token, uuid) { + this.token = token; + this.uuid = uuid; + } + + context.prototype = { + get token() {return TOKEN;}, + set token(tok) {if(tok) TOKEN=tok;}, + get uuid() {return UUID;}, + set uuid(id) {if(id) UUID=id;} + }; + + AFB_context = new context(); +} +/*********************************************/ +/**** ****/ +/**** AFB_websocket ****/ +/**** ****/ +/*********************************************/ +var AFB_websocket; +{ + var CALL = 2; + var RETOK = 3; + var RETERR = 4; + var EVENT = 5; + + var PROTO1 = "x-afb-ws-json1"; + + AFB_websocket = function(onopen, onabort) { + var u = urlWS; + if (AFB_context.token) { + u = u + '?x-afb-token=' + AFB_context.token; + if (AFB_context.uuid) + u = u + '&x-afb-uuid=' + AFB_context.uuid; + } + this.ws = new WebSocket(u, [ PROTO1 ]); + this.pendings = {}; + this.awaitens = {}; + this.counter = 0; + this.ws.onopen = onopen.bind(this); + this.ws.onerror = onerror.bind(this); + this.ws.onclose = onclose.bind(this); + this.ws.onmessage = onmessage.bind(this); + this.onopen = onopen; + this.onabort = onabort; + this.onclose = onabort; + } + + function onerror(event) { + var f = this.onabort; + if (f) { + delete this.onopen; + delete this.onabort; + f && f(this); + } + this.onerror && this.onerror(this); + } + + function onopen(event) { + var f = this.onopen; + delete this.onopen; + delete this.onabort; + f && f(this); + } + + function onclose(event) { + for (var id in this.pendings) { + var ferr = this.pendings[id].onerror; + ferr && ferr(null, this); + } + this.pendings = {}; + this.onclose && this.onclose(); + } + + function fire(awaitens, name, data) { + var a = awaitens[name]; + if (a) + a.forEach(function(handler){handler(data);}); + var i = name.indexOf("/"); + if (i >= 0) { + a = awaitens[name.substring(0,i)]; + if (a) + a.forEach(function(handler){handler(data);}); + } + a = awaitens["*"]; + if (a) + a.forEach(function(handler){handler(data);}); + } + + function reply(pendings, id, ans, offset) { + if (id in pendings) { + var p = pendings[id]; + delete pendings[id]; + var f = p[offset]; + f(ans); + } + } + + function onmessage(event) { + var obj = JSON.parse(event.data); + var code = obj[0]; + var id = obj[1]; + var ans = obj[2]; + AFB_context.token = obj[3]; + switch (code) { + case RETOK: + reply(this.pendings, id, ans, 0); + break; + case RETERR: + reply(this.pendings, id, ans, 1); + break; + case EVENT: + default: + fire(this.awaitens, id, ans); + break; + } + } + + function close() { + this.ws.close(); + this.onabort(); + } + + function call(method, request) { + return new Promise((function(resolve, reject){ + var id, arr; + do { + id = String(this.counter = 4095 & (this.counter + 1)); + } while (id in this.pendings); + this.pendings[id] = [ resolve, reject ]; + arr = [CALL, id, method, request ]; + if (AFB_context.token) arr.push(AFB_context.token); + this.ws.send(JSON.stringify(arr)); + }).bind(this)); + } + + function onevent(name, handler) { + var id = name; + var list = this.awaitens[id] || (this.awaitens[id] = []); + list.push(handler); + } + + AFB_websocket.prototype = { + close: close, + call: call, + onevent: onevent + }; +} +/*********************************************/ +/**** ****/ +/**** ****/ +/**** ****/ +/*********************************************/ +return { + context: AFB_context, + ws: AFB_websocket +}; +}; + diff --git a/htdocs/CMakeLists.txt b/htdocs/CMakeLists.txt new file mode 100644 index 0000000..7aa0ce1 --- /dev/null +++ b/htdocs/CMakeLists.txt @@ -0,0 +1,33 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# +# 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. +########################################################################### + + + +################################################## +# HTML Testing Files +################################################## +PROJECT_TARGET_ADD(htdocs) + + file(GLOB SOURCE_FILES "*.html" "*.js" "*.jpg" "*.css" "assets") + + add_input_files("${SOURCE_FILES}") + + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + LABELS "HTDOCS" + OUTPUT_NAME ${TARGET_NAME} + ) diff --git a/htdocs/binding.css b/htdocs/binding.css new file mode 100644 index 0000000..99f84b4 --- /dev/null +++ b/htdocs/binding.css @@ -0,0 +1,99 @@ +body.page-content { + height: 100%; + width: auto; + margin-top: 20px; + background-size: cover; + background-position: center; +} + +img { + float: right; +} + +ol { + display: flex; + flex-direction: column; +} + +#question, +#output, +#outevt { + white-space: pre-wrap; +} + +div.row { + display: flex; + flex-direction: row; +} + +div.col1 { + flex-basis: 0; + flex-grow: 1; + width: 100%; +} + +div.col2 { + flex-basis: 0; + flex-grow: 1; + width: 30ch; +} + +button { + margin-right: 10px; + padding: 6px 8px; + font-size: large; +} + +pre { + outline: 1px solid #ccc; + padding: 5px; + margin: 5px; + background-color: white; + opacity: 0.85; + min-height: 5pc; + max-height: 5pc; + overflow: auto; +} + +.string { + color: green; +} + +.number { + color: darkorange; +} + +.boolean { + color: blue; +} + +.null { + color: magenta; +} + +.key { + color: red; +} + +dialog { + padding: 0; + border: 0; + border-radius: 0.6rem; + box-shadow: 0 0 1em black; +} + +dialog::backdrop { + /* make the backdrop a semi-transparent black */ + background-color: rgba(0, 0, 0, 0.4); +} + +h3.dialogheader { + background-color: rgb(177, 177, 236); + padding: 1ch; +} + +footer { + display: flex; + align-items: center; + justify-content: center; +}
\ No newline at end of file diff --git a/htdocs/binding.js b/htdocs/binding.js new file mode 100644 index 0000000..c24d62e --- /dev/null +++ b/htdocs/binding.js @@ -0,0 +1,226 @@ +var afb = new AFB("api", "mysecret"); +var ws; +var evtIdx = 0; +var count = 0; + + +//********************************************** +// Logger +//********************************************** +var log = { + command: function (api, verb, query) { + console.log("subscribe api=" + api + " verb=" + verb + " query=", query); + var question = urlWS + "/" + api + "/" + verb + "?query=" + JSON.stringify(query); + log._write("question", count + ": " + log.syntaxHighlight(question)); + }, + + event: function (obj) { + console.log("gotevent:" + JSON.stringify(obj)); + log._write("outevt", (evtIdx++) + ": " + JSON.stringify(obj)); + }, + + reply: function (obj) { + console.log("replyok:" + JSON.stringify(obj)); + log._write("output", count + ": OK: " + log.syntaxHighlight(obj)); + }, + + error: function (obj) { + console.log("replyerr:" + JSON.stringify(obj)); + log._write("output", count + ": ERROR: " + log.syntaxHighlight(obj)); + }, + + _write: function (element, msg) { + var el = document.getElementById(element); + el.innerHTML += msg + '\n'; + + // auto scroll down + setTimeout(function () { + el.scrollTop = el.scrollHeight; + }, 100); + }, + + syntaxHighlight: function (json) { + if (typeof json !== 'string') { + json = JSON.stringify(json, undefined, 2); + } + json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + var cls = 'number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'key'; + } else { + cls = 'string'; + } + } else if (/true|false/.test(match)) { + cls = 'boolean'; + } else if (/null/.test(match)) { + cls = 'null'; + } + return '<span class="' + cls + '">' + match + '</span>'; + }); + }, +}; + +//********************************************** +// Generic function to call binder +//*********************************************** +function callbinder(api, verb, query) { + log.command(api, verb, query); + + // ws.call return a Promise + return ws.call(api + '/' + verb, query) + .then(function (res) { + log.reply(res); + count++; + return res; + }) + .catch(function (err) { + log.reply(err); + count++; + throw err; + }); +}; + +//********************************************** +// Init - establish Websocket connection +//********************************************** +function init(elemID, api, verb, query) { + + function onopen() { + document.getElementById("main").style.visibility = "visible"; + document.getElementById("connected").innerHTML = "Binder WS Active"; + document.getElementById("connected").style.background = "lightgreen"; + ws.onevent("*", log.event); + } + + function onabort() { + document.getElementById("main").style.visibility = "hidden"; + document.getElementById("connected").innerHTML = "Connected Closed"; + document.getElementById("connected").style.background = "red"; + } + + ws = new afb.ws(onopen, onabort); +} + +function clearPre(preId) { + const pre = document.getElementById(preId); + while (pre && pre.firstChild) { + pre.removeChild(pre.firstChild); + } +} + +function fetchAndRenderVoiceAgents() { + const agentsDiv = document.getElementById('agentsDiv'); + while (agentsDiv.firstChild) { + agentsDiv.removeChild(agentsDiv.firstChild); + } + + const api = 'vshl'; + const verb = 'enumerateVoiceAgents'; + const query = {}; + + log.command(api, verb, query); + + return ws.call(api + '/' + verb, query) + .then(function (res) { + log.reply(res); + for (let index = 0; index < res.response.agents.length; ++index) { + let voiceAgent = res.response.agents[index]; + addVoiceAgent(agentsDiv, voiceAgent, res.response.default == voiceAgent.id); + } + }) + .catch(function (err) { + log.reply(err); + console.log(JSON.stringify(err)); + }); +} + +function addVoiceAgent(containerDiv, voiceAgent, isDefault) { + const agentDiv = document.createElement("div"); + + const agentName = document.createElement("h2"); + agentName.innerHTML = voiceAgent.name; + agentDiv.appendChild(agentName); + + const agentDescription = document.createElement("p"); + agentDescription.innerHTML = voiceAgent.description; + agentDiv.appendChild(agentDescription); + + if (!isDefault) { + const setDefaultBtn = document.createElement("button"); + setDefaultBtn.addEventListener('click', (evt) => { + const query = {"id": voiceAgent.id}; + callbinder('vshl', 'setDefaultVoiceAgent', query); + fetchAndRenderVoiceAgents(); + }); + setDefaultBtn.innerHTML = 'SetDefault'; + agentDiv.appendChild(setDefaultBtn); + } + + const subscribeBtn = document.createElement("button"); + subscribeBtn.addEventListener('click', (evt) => { + showAgentEventChooserDialog(voiceAgent.id); + }); + subscribeBtn.innerHTML = 'Subscribe'; + agentDiv.appendChild(subscribeBtn); + + containerDiv.appendChild(agentDiv); +} + +function showAgentEventChooserDialog(voiceAgentId) { + const modal = document.getElementById('agent-event-chooser'); + const subscribeBtn = document.getElementById('agent-subscribe-btn'); + + subscribeBtn.addEventListener('click', (evt) => { + const authState = document.getElementById('authstate').checked; + const dialogState = document.getElementById('dialogstate').checked; + const connectionState = document.getElementById('connectionstate').checked; + + const query = { + "va_id": voiceAgentId, + "events":[] + }; + if (authState) + query.events.push('voice_authstate_event'); + if (dialogState) + query.events.push('voice_dialogstate_event'); + if (connectionState) + query.events.push('voice_connectionstate_event'); + + callbinder('vshl', 'subscribe', query); + modal.close(); + }); + + // makes modal appear (adds `open` attribute) + modal.showModal(); +} + +function showTemplateUIEventChooserDialog() { + const modal = document.getElementById('templateui-event-chooser'); + const subscribeBtn = document.getElementById('templateui-subscribe-btn'); + + subscribeBtn.addEventListener('click', (evt) => { + const renderTemplate = document.getElementById('render_template').checked; + const clearTemplate = document.getElementById('clear_template').checked; + const renderPlayerInfo = document.getElementById('render_player_info').checked; + const clearPlayerInfo = document.getElementById('clear_player_info').checked; + + const query = {"actions":[]}; + + if (renderTemplate) + query.actions.push('render_template'); + if (clearTemplate) + query.actions.push('clear_template'); + if (renderPlayerInfo) + query.actions.push('render_player_info'); + if (clearPlayerInfo) + query.actions.push('clear_player_info'); + + callbinder('vshl', 'guiMetadata/subscribe', query); + modal.close(); + }); + + // makes modal appear (adds `open` attribute) + modal.showModal(); +}
\ No newline at end of file diff --git a/htdocs/index.html b/htdocs/index.html new file mode 100644 index 0000000..5a84ee8 --- /dev/null +++ b/htdocs/index.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>VSHL API Test</title> + <link rel="stylesheet" href="binding.css"> + <script type="text/javascript" src="AFB-websock.js"></script> + <script type="text/javascript" src="binding.js"></script> +</head> + +<body class="page-content" onload="init()"> + + <h1>Voice Service High Level API Tester</h1> + + <button id="connected" onclick="init()">Binder WS Fail</button> + <button id="monitoring" onclick="window.open('/monitoring/monitor.html','_monitor_ctl')">Debug/Monitoring</a> + </button> + <button onclick="clearPre('question'); clearPre('output'); clearPre('outevt');">Clear</button> + + <br> + <br> + + <dialog id="agent-event-chooser"> + <h3 class="dialogheader">Subscribe to the following agent events</h3> + <div> + <ol> + <li> + <input type="checkbox" id="authstate" checked> + <label>voice_authstate_event</label> + </li> + <li> + <input type="checkbox" id="dialogstate" checked> + <label>voice_dialogstate_event</label> + </li> + <li> + <input type="checkbox" id="connectionstate" checked> + <label>voice_connectionstate_event</label> + </li> + </ol> + </div> + <footer> + <button id="agent-subscribe-btn" type="button" style="margin: 10px">Subscribe</button> + </footer> + </dialog> + + <dialog id="templateui-event-chooser"> + <h3 class="dialogheader">Subscribe to the following GUI Metadata Messages</h3> + <div> + <ol> + <li> + <input type="checkbox" id="render_template" checked> + <label>render_template</label> + </li> + <li> + <input type="checkbox" id="clear_template" checked> + <label>clear_template</label> + </li> + <li> + <input type="checkbox" id="render_player_info" checked> + <label>render_player_info</label> + </li> + <li> + <input type="checkbox" id="clear_player_info" checked> + <label>clear_player_info</label> + </li> + </ol> + </div> + <footer> + <button id="templateui-subscribe-btn" type="button" style="margin: 10px">Subscribe</button> + </footer> + </dialog> + + <div id="top" class="row"> + <div id='actions' class="col1"> + <div> + <h2>VSHL APIs</h2> + <p>APIs that are voiceagent agnostic</p> + <button onclick="callbinder('vshl','startListening',{});">startListening</button> + <button onclick="fetchAndRenderVoiceAgents();">enumerateAgents</button> + <button onclick="showTemplateUIEventChooserDialog();">Subscribe to GUI Metadata</button> + </div> + + <div id="agentsDiv"> + </div> + </div> + + <div id="main" style="visibility:hidden" class="col2"> + <ol> + <li>Question <pre id="question"></pre> + <li>Response <pre id="output"></pre> + <li>Events: <pre id="outevt"></pre> + </ol> + </div> + </div> + +</body>
\ No newline at end of file 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 <list> + +#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<vshl::utilities::logging::Logger> sLogger; +static std::shared_ptr<vshl::common::interfaces::IAFBApi> sAfbApi; +static std::unique_ptr<vshl::capabilities::CapabilitiesFactory> sCapabilitiesFactory; +static std::unique_ptr<vshl::capabilities::CapabilityMessagingService> sCapabilityMessagingService; +static std::unique_ptr<vshl::core::VRRequestProcessor> sVRRequestProcessor; +static std::unique_ptr<vshl::voiceagents::VoiceAgentsDataManager> sVoiceAgentsDataManager; +static std::unique_ptr<vshl::utilities::events::EventRouter> 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<string>()); + + 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<string>()); + + 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<string>()); + + 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<string>()); + std::string name(agentJson[VA_JSON_ATTR_NAME].get<string>()); + std::string api(agentJson[VA_JSON_ATTR_API].get<string>()); + std::string description(agentJson[VA_JSON_ATTR_DESCRIPTION].get<string>()); + std::string vendor(agentJson[VA_JSON_ATTR_VENDOR].get<string>()); + std::string activeWakeword(agentJson[VA_JSON_ATTR_ACTIVE_WW].get<string>()); + bool isActive(agentJson[VA_JSON_ATTR_ACTIVE].get<bool>()); + + shared_ptr<unordered_set<string>> wakewords = std::make_shared<unordered_set<string>>(); + json wakewordsJson = agentJson[VA_JSON_ATTR_WWS]; + for (auto wwIt = wakewordsJson.begin(); wwIt != wakewordsJson.end(); ++wwIt) { + wakewords->insert(wwIt->get<string>()); + } + + 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<string>()); + 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<string>()); + + 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<string> events(subscribeJson[EVENTS_JSON_ATTR_EVENTS].get<list<string>>()); + + // 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<string>()); + 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<vshl::common::interfaces::ICapability> 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<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>()); + + // 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<vshl::common::interfaces::ICapability> 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<string>()); + + 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<string>()); + + 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<vshl::common::interfaces::ICapability> 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<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>()); + + // 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<vshl::common::interfaces::ICapability> 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<string>()); + + 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<string>()); + + 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<vshl::common::interfaces::ICapability> 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<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>()); + + // 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<vshl::common::interfaces::ICapability> 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<string>()); + + 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<string>()); + + 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> AFBApiImpl::create(AFB_ApiT api) { + return std::unique_ptr<AFBApiImpl>(new AFBApiImpl(api)); +} + +AFBApiImpl::AFBApiImpl(AFB_ApiT api) : mApi(api), mLogger(Logger::create(api)) { +} + +AFBApiImpl::~AFBApiImpl() { +} + +std::shared_ptr<IAFBApi::IAFBEvent> 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 <memory> + +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<AFBApiImpl> create(AFB_ApiT api); + + ~AFBApiImpl(); + + std::shared_ptr<IAFBEvent> 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<vshl::common::interfaces::ILogger> 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> AFBRequestImpl::create(AFB_ReqT afbRequest) { + return std::unique_ptr<AFBRequestImpl>(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 <memory> + +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<AFBRequestImpl> 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 <memory> +#include <string> + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/logging/ILogger.h" + +extern "C" { +#include "ctl-plugin.h" +#include <json-c/json.h> +} + +using namespace std; + +namespace vshl { +namespace afb { +/* + * This class encapsulates AFB Event. + */ +class AFBEventImpl : public vshl::common::interfaces::IAFBApi::IAFBEvent { +public: + static unique_ptr<AFBEventImpl> + create(shared_ptr<vshl::common::interfaces::ILogger> 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<vshl::common::interfaces::ILogger> 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<vshl::common::interfaces::ILogger> 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> AFBEventImpl::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + AFB_ApiT api, + const string& eventName) { + return unique_ptr<AFBEventImpl>(new AFBEventImpl(logger, api, eventName)); +} + +AFBEventImpl::AFBEventImpl( + shared_ptr<vshl::common::interfaces::ILogger> 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<AFB_ReqT>(requestInterface.getNativeRequest()); + if (isValid() && afb_req_subscribe(request, mAfbEvent) == 0) { + return true; + } + + return false; +} + +bool AFBEventImpl::unsubscribe(IAFBRequest& requestInterface) { + makeEventIfNeccessary(); + auto request = static_cast<AFB_ReqT>(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> CapabilitiesFactory::create() { + auto capabilitiesFactory = std::unique_ptr<CapabilitiesFactory>(new CapabilitiesFactory()); + return capabilitiesFactory; +} + +std::shared_ptr<common::interfaces::ICapability> CapabilitiesFactory::getGuiMetadata() { + if (!mGuiMetadata) { + mGuiMetadata = vshl::capabilities::guimetadata::GuiMetadata::create(); + } + return mGuiMetadata; +} + +std::shared_ptr<common::interfaces::ICapability> CapabilitiesFactory::getPhoneControl() { + if (!mPhoneControl) { + mPhoneControl = vshl::capabilities::phonecontrol::PhoneControl::create(); + } + return mPhoneControl; +} + +std::shared_ptr<common::interfaces::ICapability> 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 <memory> + +#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<CapabilitiesFactory> create(); + + // GUI Metadata capability + std::shared_ptr<common::interfaces::ICapability> getGuiMetadata(); + + // Phone call control capability + std::shared_ptr<common::interfaces::ICapability> getPhoneControl(); + + // Navigation capability + std::shared_ptr<common::interfaces::ICapability> getNavigation(); + + // Destructor + ~CapabilitiesFactory() = default; + +private: + // Constructor + CapabilitiesFactory() = default; + + // Capabilities + shared_ptr<vshl::common::interfaces::ICapability> mGuiMetadata; + shared_ptr<vshl::common::interfaces::ICapability> mPhoneControl; + shared_ptr<vshl::common::interfaces::ICapability> mNavigation; + + // Logger + unique_ptr<vshl::utilities::logging::Logger> 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> CapabilityMessagingService::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> 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<CapabilityMessagingService>(new CapabilityMessagingService(logger, afbApi)); + return capabilityMessageService; +} + +CapabilityMessagingService::~CapabilityMessagingService() { + mMessageChannelsMap.clear(); +} + +CapabilityMessagingService::CapabilityMessagingService( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) : + mAfbApi(afbApi), + mLogger(logger) { +} + +// Subscribe to capability specific messages. +bool CapabilityMessagingService::subscribe( + vshl::common::interfaces::IAFBRequest& request, + shared_ptr<common::interfaces::ICapability> 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<common::interfaces::ICapability> 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<vshl::capabilities::core::MessageChannel> CapabilityMessagingService::getMessageChannel( + shared_ptr<common::interfaces::ICapability> 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 <memory> +#include <string> +#include <unordered_map> + +#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<CapabilityMessagingService> + create(shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + // Subscribe to capability specific messages. + bool subscribe(vshl::common::interfaces::IAFBRequest &request, + shared_ptr<common::interfaces::ICapability> capability, + const string action); + + // Publish capability messages. + bool publish(shared_ptr<common::interfaces::ICapability> capability, + const string action, const string payload); + + // Destructor + ~CapabilityMessagingService(); + +private: + // Constructor + CapabilityMessagingService( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + // Binding API reference + shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi; + + // Create a message channel for the capability. + shared_ptr<vshl::capabilities::core::MessageChannel> + getMessageChannel(shared_ptr<common::interfaces::ICapability> capability); + + // Map of capabilities to message channels. + unordered_map<string, shared_ptr<vshl::capabilities::core::MessageChannel>> + mMessageChannelsMap; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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 <memory> + +#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<PhoneControl> create(); + + ~PhoneControl() = default; + +protected: + string getName() const override; + + list<string> getUpstreamMessages() const override; + + list<string> 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 <list> +#include <string> + +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<string> PHONECONTROL_UPSTREAM_ACTIONS = { + PHONECONTROL_DIAL, +}; + +// List of actions that are delivered from Apps -> VA +static list<string> 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> PhoneControl::create() { + auto phonecontrol = std::shared_ptr<PhoneControl>(new PhoneControl()); + return phonecontrol; +} + +string PhoneControl::getName() const { + return NAME; +} + +list<string> PhoneControl::getUpstreamMessages() const { + return PHONECONTROL_UPSTREAM_ACTIONS; +} + +list<string> 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 <memory> + +#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<MessageChannel> + create(shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + shared_ptr<vshl::common::interfaces::ICapability> 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<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + shared_ptr<vshl::common::interfaces::ICapability> capability); + + // Forwarders + shared_ptr<PublisherForwarder> mPublisherForwarder; + shared_ptr<SubscriberForwarder> 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 <memory> + +#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<PublisherForwarder> create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::ICapability> capability); + + // Connect a subscriber forwarder to this publisher forwarder + void setSubscriberForwarder(shared_ptr<SubscriberForwarder> subscriberForwarder); + + // Forward message to the subscriber forwarder + bool forwardMessage(const string action, const string payload); + + // Destructor + ~PublisherForwarder(); + +private: + // Constructor + PublisherForwarder( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::ICapability> capability); + + // Subscriber forwarder connected to this publisher forwarder. + shared_ptr<SubscriberForwarder> mSubscriberForwarder; + + // Capability + shared_ptr<vshl::common::interfaces::ICapability> mCapability; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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 <memory> +#include <string> +#include <unordered_map> + +#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<SubscriberForwarder> + create(shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + shared_ptr<vshl::common::interfaces::ICapability> 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<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + shared_ptr<vshl::common::interfaces::ICapability> capability); + + // Creates both upstream and downstream events + void createEvents(); + + // Binding API reference + shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi; + + // Capability + shared_ptr<vshl::common::interfaces::ICapability> mCapability; + + // Maps of capability action events to its corresponding Event object. + // Event name maps to Action Name + unordered_map<string, shared_ptr<common::interfaces::IAFBApi::IAFBEvent>> + mUpstreamEventsMap; + unordered_map<string, shared_ptr<common::interfaces::IAFBApi::IAFBEvent>> + mDownstreamEventsMap; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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> MessageChannel::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> api, + shared_ptr<vshl::common::interfaces::ICapability> capability) { + auto messageChannel = std::shared_ptr<MessageChannel>(new MessageChannel(logger, api, capability)); + return messageChannel; +} + +MessageChannel::MessageChannel( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> api, + shared_ptr<vshl::common::interfaces::ICapability> 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> PublisherForwarder::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::ICapability> 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<PublisherForwarder>(new PublisherForwarder(logger, capability)); + return publisherForwarder; +} + +// Constructor +PublisherForwarder::PublisherForwarder( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::ICapability> capability) { + mCapability = capability; + mLogger = logger; +} + +// Destructor +PublisherForwarder::~PublisherForwarder() { +} + +void PublisherForwarder::setSubscriberForwarder(shared_ptr<SubscriberForwarder> 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> SubscriberForwarder::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + shared_ptr<vshl::common::interfaces::ICapability> 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<SubscriberForwarder>(new SubscriberForwarder(logger, afbApi, capability)); + return subscriberForwarder; +} + +SubscriberForwarder::SubscriberForwarder( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + shared_ptr<vshl::common::interfaces::ICapability> 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<common::interfaces::IAFBApi::IAFBEvent> 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<common::interfaces::IAFBApi::IAFBEvent> 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 <memory> + +#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<GuiMetadata> create(); + + ~GuiMetadata() = default; + +protected: + string getName() const override; + + list<string> getUpstreamMessages() const override; + + list<string> 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 <list> +#include <string> + +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<string> 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<string> 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> GuiMetadata::create() { + auto guiMetadata = std::shared_ptr<GuiMetadata>(new GuiMetadata()); + return guiMetadata; +} + +string GuiMetadata::getName() const { + return NAME; +} + +list<string> GuiMetadata::getUpstreamMessages() const { + return GUIMETADATA_UPSTREAM_ACTIONS; +} + +list<string> 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 <memory> + +#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<Navigation> create(); + + ~Navigation() = default; + +protected: + string getName() const override; + + list<string> getUpstreamMessages() const override; + + list<string> 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 <list> +#include <string> + +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<string> NAVIGATION_UPSTREAM_ACTIONS = { + NAVIGATION_SET_DESTINATION, + NAVIGATION_CANCEL, +}; + +// List of actions that are delivered from Apps -> VA +static list<string> 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> Navigation::create() { + auto navigation = std::shared_ptr<Navigation>(new Navigation()); + return navigation; +} + +string Navigation::getName() const { + return NAME; +} + +list<string> Navigation::getUpstreamMessages() const { + return NAVIGATION_UPSTREAM_ACTIONS; +} + +list<string> 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 <gtest/gtest.h> + +#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<ConsoleLogger>(); + mAfbApi = std::make_shared<::testing::NiceMock<AFBApiMock>>(); + } + + std::shared_ptr<::testing::NiceMock<AFBApiMock>> mAfbApi; + std::shared_ptr<ConsoleLogger> 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<CapabilityMock>>(); + std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> 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<AFBEventMock> mockEvent(new ::testing::NiceMock<AFBEventMock>()); + 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<IAFBApi::IAFBEvent> { + mockEvent->setName(eventName); + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + + auto request = std::make_shared<::testing::StrictMock<AFBRequestMock>>(); + 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 <gtest/gtest.h> + +#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<ConsoleLogger>(); + mAfbApi = std::make_shared<::testing::StrictMock<AFBApiMock>>(); + + mEventCreatorFn = [](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> { + std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>()); + mockEvent->setName(eventName); + return mockEvent; + }; + } + + std::shared_ptr<SubscriberForwarder> createSubscriberForwarder( + std::shared_ptr<::testing::StrictMock<CapabilityMock>> capability) { + EXPECT_CALL(*capability, getUpstreamMessages()).Times(1); + EXPECT_CALL(*capability, getDownstreamMessages()).Times(1); + + return SubscriberForwarder::create(mConsoleLogger, mAfbApi, capability); + } + + std::shared_ptr<PublisherForwarder> createPublisherForwarder( + std::shared_ptr<::testing::StrictMock<CapabilityMock>> capability) { + return PublisherForwarder::create(mConsoleLogger, capability); + } + + std::shared_ptr<::testing::StrictMock<AFBApiMock>> mAfbApi; + std::shared_ptr<ConsoleLogger> mConsoleLogger; + std::function<std::shared_ptr<IAFBApi::IAFBEvent>(const std::string&)> mEventCreatorFn; + std::shared_ptr<::testing::StrictMock<AFBEventMock>> mAfbEventMock; +}; + +TEST_F(PublisherForwarderTest, failsInitializationOnInvalidParams) { + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + + 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<CapabilityMock>>(); + auto forwarder = createPublisherForwarder(capability); + + ASSERT_NE(forwarder, nullptr); +} + +TEST_F(PublisherForwarderTest, canForwardMessages) { + std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>()); + 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<IAFBApi::IAFBEvent> { + 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<CapabilityMock>>(); + std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> 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 <gtest/gtest.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/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<ConsoleLogger>(); + mAfbApi = std::make_shared<::testing::StrictMock<AFBApiMock>>(); + + mEventCreatorFn = [](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> { + std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>()); + mockEvent->setName(eventName); + return mockEvent; + }; + } + + std::shared_ptr<SubscriberForwarder> createSubscriberForwarder( + std::shared_ptr<::testing::StrictMock<CapabilityMock>> capability) { + EXPECT_CALL(*capability, getUpstreamMessages()).Times(1); + EXPECT_CALL(*capability, getDownstreamMessages()).Times(1); + + return SubscriberForwarder::create(mConsoleLogger, mAfbApi, capability); + } + + std::shared_ptr<::testing::StrictMock<AFBApiMock>> mAfbApi; + std::shared_ptr<ConsoleLogger> mConsoleLogger; + std::function<std::shared_ptr<IAFBApi::IAFBEvent>(const std::string&)> mEventCreatorFn; + std::shared_ptr<::testing::StrictMock<AFBEventMock>> mAfbEventMock; +}; + +TEST_F(SubscriberForwarderTest, failsInitializationOnInvalidParams) { + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + + 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<CapabilityMock>>(); + auto forwarder = createSubscriberForwarder(capability); + + ASSERT_NE(forwarder, nullptr); +} + +TEST_F(SubscriberForwarderTest, createsEventsOnInitialization) { + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(mEventCreatorFn)); + + std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + 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<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + 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<std::string>({"non", "existent", "events"}); + auto itCapability = nonExistentEvents.begin(); + auto request = std::make_shared<::testing::StrictMock<AFBRequestMock>>(); + 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<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + 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<AFBRequestMock>>(); + 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<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>()); + 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<IAFBApi::IAFBEvent> { + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + 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<AFBRequestMock>>(); + 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<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>()); + EXPECT_CALL(*mockEvent, publishEvent(::testing::_)).Times(0); + auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> { + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + 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<std::string>({"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<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + 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<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>()); + 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<IAFBApi::IAFBEvent> { + return mockEvent; + }; + + ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator)); + EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4); + + std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"}); + std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"}); + + auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>(); + 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 <memory> +#include <unordered_map> + +#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<VRRequestProcessor> create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::core::VRRequestProcessorDelegate> 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<vshl::common::interfaces::IVoiceAgentsChangeObserver> getVoiceAgentsChangeObserver() const; + + // Destructor + ~VRRequestProcessor(); + +private: + // Constructor + VRRequestProcessor( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::core::VRRequestProcessorDelegate> delegate); + + // Voiceagents observer + shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> mVoiceAgentsChangeObserver; + + // Request Processor Delegate + shared_ptr<vshl::core::VRRequestProcessorDelegate> mDelegate; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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> VRRequestProcessor::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::core::VRRequestProcessorDelegate> delegate) { + auto processor = std::unique_ptr<VRRequestProcessor>(new VRRequestProcessor(logger, delegate)); + return processor; +} + +VRRequestProcessor::VRRequestProcessor( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::core::VRRequestProcessorDelegate> 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<vshl::common::interfaces::IVoiceAgent> 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<vshl::common::interfaces::IVoiceAgentsChangeObserver> 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 <memory> + +#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<VRAgentsObserver> + create(weak_ptr<VRRequestProcessorDelegate> delegate); + + ~VRAgentsObserver(); + +protected: + void OnDefaultVoiceAgentChanged( + shared_ptr<vshl::common::interfaces::IVoiceAgent> defaultVoiceAgent) + override; + void OnVoiceAgentAdded( + shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override; + void OnVoiceAgentRemoved( + shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override; + void OnVoiceAgentActiveWakeWordChanged( + shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override; + void OnVoiceAgentActivated( + shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override; + void OnVoiceAgentDeactivated( + shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override; + +private: + // Constructor + VRAgentsObserver(weak_ptr<VRRequestProcessorDelegate> delegate); + + // Delegate that needs to be informed of the voiceagent data changes. + weak_ptr<VRRequestProcessorDelegate> 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 <memory> + +#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<VRRequest> create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + string requestId, + shared_ptr<vshl::common::interfaces::IVoiceAgent> 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<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + const string requestId, + shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent); + + // Binding API reference. + shared_ptr<vshl::common::interfaces::IAFBApi> mApi; + + // Voice agent associated with this request + shared_ptr<vshl::common::interfaces::IVoiceAgent> mVoiceAgent; + + // Request ID + string mRequestId; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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 <memory> +#include <unordered_map> + +#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<VRRequestProcessorDelegate> create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + // Destructor + ~VRRequestProcessorDelegate(); + + // Set default voiceagent + void setDefaultVoiceAgent(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent); + + // Get the default voiceagent + shared_ptr<vshl::common::interfaces::IVoiceAgent> 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<vshl::common::interfaces::IVoiceAgent> voiceAgent); + + // Cancel all requests + void cancelAllRequests(); + + // Get All outstanding requests + // Used only by Test + unordered_map<string, shared_ptr<VRRequest>> getAllRequests(); + +private: + // Constructor + VRRequestProcessorDelegate( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + // Binding API reference + shared_ptr<vshl::common::interfaces::IAFBApi> mApi; + + // Default voiceagent + shared_ptr<vshl::common::interfaces::IVoiceAgent> mDefaultVoiceAgent; + + // A map of voiceagent IDs and their respective VR Request objects. + unordered_map<string, shared_ptr<VRRequest>> mVRRequests; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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> VRAgentsObserver::create(weak_ptr<VRRequestProcessorDelegate> delegate) { + auto observer = std::shared_ptr<VRAgentsObserver>(new VRAgentsObserver(delegate)); + return observer; +} + +VRAgentsObserver::VRAgentsObserver(weak_ptr<VRRequestProcessorDelegate> delegate) { + mWeakDelegate = delegate; +} + +VRAgentsObserver::~VRAgentsObserver() { +} + +void VRAgentsObserver::OnDefaultVoiceAgentChanged(shared_ptr<vshl::common::interfaces::IVoiceAgent> defaultVoiceAgent) { + if (auto delegate = mWeakDelegate.lock()) { + delegate->setDefaultVoiceAgent(defaultVoiceAgent); + } +} + +void VRAgentsObserver::OnVoiceAgentAdded(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) { +} + +void VRAgentsObserver::OnVoiceAgentRemoved(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) { +} + +void VRAgentsObserver::OnVoiceAgentActiveWakeWordChanged(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) { + // Not Implemented +} + +void VRAgentsObserver::OnVoiceAgentActivated(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) { +} + +void VRAgentsObserver::OnVoiceAgentDeactivated(shared_ptr<vshl::common::interfaces::IVoiceAgent> 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> VRRequest::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + const string requestId, + shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) { + if (logger == nullptr) { + return nullptr; + } + + if (afbApi == nullptr) { + logger->log(Level::ERROR, TAG, "Invalid AFB API"); + return nullptr; + } + + auto request = std::unique_ptr<VRRequest>(new VRRequest(logger, afbApi, requestId, voiceAgent)); + return request; +} + +VRRequest::VRRequest( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi, + string requestId, + shared_ptr<vshl::common::interfaces::IVoiceAgent> 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> VRRequestProcessorDelegate::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) { + auto delegate = std::shared_ptr<VRRequestProcessorDelegate>(new VRRequestProcessorDelegate(logger, afbApi)); + return delegate; +} + +VRRequestProcessorDelegate::VRRequestProcessorDelegate( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) : + mApi(afbApi), + mLogger(logger) { +} + +VRRequestProcessorDelegate::~VRRequestProcessorDelegate() { + mVRRequests.clear(); +} + +string VRRequestProcessorDelegate::startRequestForVoiceAgent( + shared_ptr<vshl::common::interfaces::IVoiceAgent> 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<VRRequest> 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<string, shared_ptr<VRRequest>> VRRequestProcessorDelegate::getAllRequests() { + return mVRRequests; +} + +void VRRequestProcessorDelegate::setDefaultVoiceAgent(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) { + mDefaultVoiceAgent = voiceAgent; +} + +shared_ptr<vshl::common::interfaces::IVoiceAgent> 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 <gtest/gtest.h> + +#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<ConsoleLogger>(); + mAfbApi = std::make_shared<::testing::StrictMock<AFBApiMock>>(); + + 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<AFBApiMock>> mAfbApi; + std::shared_ptr<ConsoleLogger> mConsoleLogger; + std::shared_ptr<VoiceAgent> mVoiceAgent; + + std::shared_ptr<VRRequestProcessorDelegate> mVRReqProcessorDelegate; + std::shared_ptr<VRRequestProcessor> 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 <gtest/gtest.h> + +#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<ConsoleLogger>(); + mAfbApi = std::make_shared<::testing::StrictMock<AFBApiMock>>(); + + 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<AFBApiMock>> mAfbApi; + std::shared_ptr<ConsoleLogger> mConsoleLogger; + std::shared_ptr<VoiceAgent> mVoiceAgent; + + std::shared_ptr<VRRequest> 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 <memory> +#include <string> + +#include <json-c/json_object.h> + +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<IAFBEvent> 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 <list> +#include <string> + +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<string> getUpstreamMessages() const = 0; + + /* + * Returns the list of downstream messages + */ + virtual list<string> 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 <string> + +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 <string> + +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 <memory> +#include <string> +#include <unordered_set> + +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<unordered_set<string>> 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 <memory> +#include <string> + +#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<IVoiceAgent> defaultVoiceAgent) = 0; + + /** + * This method notifies the observers that a new voiceagent has been added. + */ + virtual void OnVoiceAgentAdded(shared_ptr<IVoiceAgent> voiceAgent) = 0; + + /** + * This method notifies the observers that a voiceagent is removed. + */ + virtual void OnVoiceAgentRemoved(shared_ptr<IVoiceAgent> voiceAgent) = 0; + + /** + * This method notifies the observers that active wakeword for a voiceagent is + * updated. + */ + virtual void + OnVoiceAgentActiveWakeWordChanged(shared_ptr<IVoiceAgent> voiceAgent) = 0; + + /** + * This method notifies the observers that the voiceagent has been activated. + */ + virtual void OnVoiceAgentActivated(shared_ptr<IVoiceAgent> voiceAgent) = 0; + + /** + * This method notifies the observers that the voiceagent has been activated. + */ + virtual void OnVoiceAgentDeactivated(shared_ptr<IVoiceAgent> 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 <iostream> + +#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 <gmock/gmock.h> + +#include "interfaces/afb/IAFBApi.h" + +namespace vshl { +namespace test { + +class AFBApiMock : public vshl::common::interfaces::IAFBApi { +public: + MOCK_METHOD1(createEvent, std::shared_ptr<IAFBEvent>(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 <gmock/gmock.h> + +#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 <gmock/gmock.h> + +#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 <gmock/gmock.h> + +#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<std::string>()); + MOCK_CONST_METHOD0(getDownstreamMessages, std::list<std::string>()); +}; + +} // 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 <gmock/gmock.h> + +#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h" + +namespace vshl { +namespace test { + +class VoiceAgentsChangeObserverMock : public vshl::common::interfaces::IVoiceAgentsChangeObserver { +public: + MOCK_METHOD1(OnDefaultVoiceAgentChanged, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> defaultVoiceAgent)); + MOCK_METHOD1(OnVoiceAgentAdded, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent)); + MOCK_METHOD1(OnVoiceAgentRemoved, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent)); + MOCK_METHOD1(OnVoiceAgentActiveWakeWordChanged, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent)); + MOCK_METHOD1(OnVoiceAgentActivated, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent)); + MOCK_METHOD1(OnVoiceAgentDeactivated, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> 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> EventRouter::create(shared_ptr<vshl::common::interfaces::ILogger> logger) { + return std::unique_ptr<EventRouter>(new EventRouter(logger)); +} + +EventRouter::EventRouter(shared_ptr<vshl::common::interfaces::ILogger> 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<vshl::common::interfaces::IEventFilter> 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<vshl::common::interfaces::IEventFilter> 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 <memory> +#include <string> +#include <unordered_set> + +#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<EventRouter> create(shared_ptr<vshl::common::interfaces::ILogger> logger); + + // Destructor + ~EventRouter(); + + // Add event filter as listerner. + bool addEventFilter(shared_ptr<vshl::common::interfaces::IEventFilter> filter); + + // Remove event filter as listerner. + bool removeEventFilter(shared_ptr<vshl::common::interfaces::IEventFilter> 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<vshl::common::interfaces::ILogger> logger); + + // set of event filters. + unordered_set<shared_ptr<vshl::common::interfaces::IEventFilter>> mEventFilters; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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> Logger::create(AFB_ApiT api) { + auto logger = std::unique_ptr<Logger>(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 <memory> + +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<Logger> 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 <random> +#include <chrono> +#include <sstream> +#include <iomanip> +#include <mutex> +#include <climits> +#include <algorithm> +#include <functional> + +#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<std::default_random_engine, CHAR_BIT, uint8_t>& 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<uint8_t> 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<int>(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<std::default_random_engine, CHAR_BIT, uint8_t>& ibe, + unsigned int numDigits) { + return generateHexWithReplacement(ibe, numDigits, 0, 0); +} + +const std::string generateUUID() { + static bool seeded = false; + static std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t> ibe; + static std::mutex mutex; + std::unique_lock<std::mutex> lock(mutex); + if (!seeded) { + std::random_device rd; + ibe.seed( + rd() + + std::chrono::duration_cast<std::chrono::nanoseconds>(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 <string> + +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 <list> +#include <string> + +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<string> 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 <memory> +#include <set> +#include <unordered_map> +#include <unordered_set> + +#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<VoiceAgentsDataManager> create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + /** + * Activates the list of voiceagents. + * + * @return Number of activated agents + */ + uint32_t activateVoiceAgents(const unordered_set<string>& activeVoiceAgentIds); + + /** + * Deactivates the list of voiceagents. + * + * @return Number of de-activated agents + */ + uint32_t deactivateVoiceAgents(const unordered_set<string>& 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<unordered_set<string>> 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<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> getAllVoiceAgents(); + + // Returns the event filter that belongs to the core module. + shared_ptr<vshl::common::interfaces::IEventFilter> 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<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer); + + // Removes the voiceagent change observer from the list. + bool removeVoiceAgentsChangeObserver(shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer); + + // Destructor + ~VoiceAgentsDataManager(); + +private: + // Constructor + VoiceAgentsDataManager( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + // Binding API reference + shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi; + + // A list of all the voiceagent change observers + unordered_set<shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver>> mVoiceAgentChangeObservers; + + // A map of voiceagents grouped by ID + unordered_map<string, shared_ptr<VoiceAgent>> mVoiceAgents; + + // Voiceagent event handler. + shared_ptr<VoiceAgentEventsHandler> mVoiceAgentEventsHandler; + + // Default voiceagent + string mDefaultVoiceAgentId; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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> VoiceAgentsDataManager::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) { + return std::unique_ptr<VoiceAgentsDataManager>(new VoiceAgentsDataManager(logger, afbApi)); +} + +// Constructor +VoiceAgentsDataManager::VoiceAgentsDataManager( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> 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<string>& 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<string>& 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<unordered_set<string>> wakewords) { + shared_ptr<VoiceAgent> 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<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> VoiceAgentsDataManager::getAllVoiceAgents() { + std::set<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> voiceAgentsSet; + for (auto element : mVoiceAgents) { + voiceAgentsSet.insert(element.second); + } + + return voiceAgentsSet; +} + +// Returns the event filter that belongs to the core module. +shared_ptr<vshl::common::interfaces::IEventFilter> 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<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer) { + if (!observer) { + return false; + } + + mVoiceAgentChangeObservers.insert(observer); + return true; +} + +bool VoiceAgentsDataManager::removeVoiceAgentsChangeObserver( + shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> 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 <memory> +#include <unordered_set> + +#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<VoiceAgent> + create(shared_ptr<vshl::common::interfaces::ILogger> 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<unordered_set<string>> 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<unordered_set<string>> getWakeWords() const override; + bool isActive() const override; + string getActiveWakeword() const override; + +private: + // Constructor + VoiceAgent(shared_ptr<vshl::common::interfaces::ILogger> 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<unordered_set<string>> wakewords); + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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<unordered_set<string>> 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 <algorithm> +#include <memory> +#include <unordered_map> + +#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<VoiceAgentEventsHandler> create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> 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> 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<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> 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> voiceAgent); + + // Binding API reference + shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi; + + // A map of VSHL event ID to its Event object + unordered_map<string, shared_ptr<common::interfaces::IAFBApi::IAFBEvent>> mEventsMap; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> 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> VoiceAgentEventsHandler::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) { + auto eventFilter = std::shared_ptr<VoiceAgentEventsHandler>(new VoiceAgentEventsHandler(logger, afbApi)); + return eventFilter; +} + +VoiceAgentEventsHandler::VoiceAgentEventsHandler( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> 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<IAFBApi::IAFBEvent> 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> 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> 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 <sstream> + +#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> VoiceAgent::create( + shared_ptr<vshl::common::interfaces::ILogger> 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<unordered_set<string>> wakewords) { + if (wakewords == nullptr) { + logger->log(Level::ERROR, TAG, "Wakeword list null"); + return nullptr; + } + + auto voiceAgent = std::unique_ptr<VoiceAgent>( + new VoiceAgent(logger, id, name, description, api, vendor, activeWakeword, isActive, wakewords)); + if (!voiceAgent->setActiveWakeWord(activeWakeword)) { + return nullptr; + } + + return voiceAgent; +} + +VoiceAgent::VoiceAgent( + shared_ptr<vshl::common::interfaces::ILogger> 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<unordered_set<string>> 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<unordered_set<string>> 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 <gtest/gtest.h> + +#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<ConsoleLogger>(); + + 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<ConsoleLogger> mConsoleLogger; + std::shared_ptr<VoiceAgent> 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<std::string> 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 <gtest/gtest.h> + +#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<ConsoleLogger>(); + mAfbApi = std::make_shared<::testing::NiceMock<AFBApiMock>>(); + mVADataManager = VoiceAgentsDataManager::create(mConsoleLogger, mAfbApi); + + mAgentsChangeObserver = std::make_shared< + ::testing::StrictMock<VoiceAgentsChangeObserverMock>>(); + 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<IVoiceAgent> + findVoiceAgent(std::set<std::shared_ptr<IVoiceAgent>> &voiceAgents, + std::string &vaId) { + for (auto va : voiceAgents) { + if (va->getId() == vaId) + return va; + } + + return nullptr; + } + + std::shared_ptr<ConsoleLogger> 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<AFBApiMock>> mAfbApi; + + // It is a StrictMock because we want to fail the test for all non interesting + // calls. + std::shared_ptr<::testing::StrictMock<VoiceAgentsChangeObserverMock>> + mAgentsChangeObserver; + + std::vector<VoiceAgentTestData> mVoiceAgentsData; + std::unique_ptr<VoiceAgentsDataManager> 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 <memory> +#include <string> +#include <unordered_set> +#include <vector> + +namespace vshl { +namespace test { + +typedef std::shared_ptr<std::unordered_set<std::string>> 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<VoiceAgentTestData> getVoiceAgentsTestData() { + std::vector<VoiceAgentTestData> 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<std::unordered_set<std::string>>( + new std::unordered_set<std::string>{"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<std::unordered_set<std::string>>( + new std::unordered_set<std::string>{"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 <signal.h> + +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 <ctl-config.h> + +#ifndef ERROR +#define ERROR -1 +#endif + +#endif /* _CTL_BINDING_INCLUDE_ */
\ No newline at end of file diff --git a/tools/pre-commit b/tools/pre-commit new file mode 100755 index 0000000..87a9fc3 --- /dev/null +++ b/tools/pre-commit @@ -0,0 +1,140 @@ +#!/bin/bash + +# git pre-commit hook that runs an clang-format stylecheck. +# Features: +# - abort commit when commit does not comply with the style guidelines +# - create a patch of the proposed style changes + +# modifications for clang-format by rene.milk@wwu.de +# This file is part of a set of unofficial pre-commit hooks available +# at github. +# Link: https://gist.github.com/wangkuiyi/7379a242f0d4089eaa75 + + +################################################################## +# SETTINGS +# set path to clang-format binary +CLANG_FORMAT="`which clang-format`" + +# remove any older patches from previous commits. Set to true or false. +# DELETE_OLD_PATCHES=false +DELETE_OLD_PATCHES=false + +# only parse files with the extensions in FILE_EXTS. Set to true or false. +# if false every changed file in the commit will be parsed with clang-format. +# if true only files matching one of the extensions are parsed with clang-format. +# PARSE_EXTS=true +PARSE_EXTS=true + +# file types to parse. Only effective when PARSE_EXTS is true. +# FILE_EXTS=".c .h .cpp .hpp" +FILE_EXTS=".c .h .cpp .hpp .cc .hh .cxx .m" + +################################################################## +# There should be no need to change anything below this line. + +# Reference: http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac +canonicalize_filename () { + local target_file=$1 + local physical_directory="" + local result="" + + # Need to restore the working directory after work. + pushd `pwd` > /dev/null + + cd "$(dirname "$target_file")" + target_file=`basename $target_file` + + # Iterate down a (possible) chain of symlinks + while [ -L "$target_file" ] + do + target_file=$(readlink "$target_file") + cd "$(dirname "$target_file")" + target_file=$(basename "$target_file") + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + physical_directory=`pwd -P` + result="$physical_directory"/"$target_file" + + # restore the working directory after work. + popd > /dev/null + + echo "$result" +} + +# exit on error +set -e + +# check whether the given file matches any of the set extensions +matches_extension() { + local filename=$(basename "$1") + local extension=".${filename##*.}" + local ext + + for ext in $FILE_EXTS; do [[ "$ext" == "$extension" ]] && return 0; done + + return 1 +} + +# necessary check for initial commit +if git rev-parse --verify HEAD >/dev/null 2>&1 ; then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +if [ ! -x "$CLANG_FORMAT" ] ; then + printf "Error: clang-format executable not found.\n" + printf "Set the correct path in $(canonicalize_filename "$0").\n" + exit 1 +fi + +# create a random filename to store our generated patch +prefix="pre-commit-clang-format" +suffix="$(date +%s)" +patch="/tmp/$prefix-$suffix.patch" + +# clean up any older clang-format patches +$DELETE_OLD_PATCHES && rm -f /tmp/$prefix*.patch + +# create one patch containing all changes to the files +git diff-index --cached --diff-filter=ACMR --name-only $against -- | while read file; +do + # ignore file if we do check for file extensions and the file + # does not match any of the extensions specified in $FILE_EXTS + if $PARSE_EXTS && ! matches_extension "$file"; then + continue; + fi + + # clang-format our sourcefile, create a patch with diff and append it to our $patch + # The sed call is necessary to transform the patch from + # --- $file timestamp + # +++ - timestamp + # to both lines working on the same file and having a a/ and b/ prefix. + # Else it can not be applied with 'git apply'. + "$CLANG_FORMAT" -style=file "$file" | \ + diff -u "$file" - | \ + sed -e "1s|--- |--- a/|" -e "2s|+++ -|+++ b/$file|" >> "$patch" +done + +# if no patch has been generated all is ok, clean up the file stub and exit +if [ ! -s "$patch" ] ; then + printf "Files in this commit comply with the clang-format rules.\n" + rm -f "$patch" + exit 0 +fi + +# a patch has been created, notify the user and exit +printf "\nThe following differences were found between the code to commit " +printf "and the clang-format rules:\n\n" +cat "$patch" + +printf "\nYou can apply these changes with:\n git apply $patch\n" +printf "(may need to be called from the root directory of your repository)\n" +printf "Aborting commit. Apply changes and commit again or skip checking with" +printf " --no-verify (not recommended).\n" + +exit 1 |